Reputation: 13
I'm working on a simple JavaScript project where I'm building a todo list. The goal is to allow users to add new todo items and delete them by clicking a trash icon. However, I'm having trouble getting the delete functionality to work as expected.
What I Tried:
Adding Event Listener: I attached an event listener directly to the trash icon (<i>
) inside the addNewTodo()
function. This listener is supposed to trigger when the icon is clicked and then remove the corresponding <li>
element from the DOM.
Logging the Click Event: I added a console.log statement inside the event listener to verify that the click event is firing. This log message isn't working because of the click event isn't firing.
Removing the Todo Item: Within the same event listener, I called the remove()
method on the parent <li>
element (newTodoLi()
) to remove the todo item from the list. My expectation was that clicking the trash icon would delete the corresponding todo item.
What I Expected:
I expected that each time I clicked on a trash icon next to a todo item, the item would be immediately removed from the list without any issues. Since the event listener seemed to be firing correctly, I anticipated that calling remove()
on the parent <li>
would reliably delete the item.
let inputElem = document.querySelector("input");
let addTodoForm = document.querySelector(".add");
let todoUlElem = document.querySelector(".todos");
function addNewTodo(newTodoValue) {
// Create the new li element
let newTodoLi = document.createElement("li");
newTodoLi.className =
"list-group-item d-flex justify-content-between align-items-center";
// Create the span for the todo title
let newTodoTitleSpan = document.createElement("span");
newTodoTitleSpan.innerHTML = newTodoValue;
// Create the trash icon element
let newTodoTrash = document.createElement("i");
newTodoTrash.className = "fa fa-trash delete";
// Append the title span and trash icon to the new li element
newTodoLi.append(newTodoTitleSpan, newTodoTrash);
// Append the new li element to the ul element
todoUlElem.append(newTodoLi);
// Add an event listener to the trash icon
newTodoTrash.addEventListener("click", (e) => {
console.log("clicked delete icon");
// You can add more functionality here, like removing the todo item
// newTodoLi.remove();
});
}
// Prevent the default form submission
addTodoForm.addEventListener("submit", (e) => {
e.preventDefault();
});
// Add a new todo item when Enter key is pressed
inputElem.addEventListener("keydown", (e) => {
let newTodoValue = e.target.value.trim();
if (e.keyCode === 13) {
inputElem.value = "";
if (newTodoValue) {
addNewTodo(newTodoValue);
}
}
});
body {
background: #353f5b;
}
.container {
max-width: 400px;
margin-top: 90px;
}
.search i {
position: absolute;
}
.search {
position: relative;
}
.icon {
padding: 10px;
left: 0;
}
.input-field {
text-align: center;
}
input[type="text"],
input[type="text"]:focus {
color: #fff;
border: none;
background: rgba(0, 0, 0, 0.3);
max-width: 400px;
}
.todos li {
background: #423a6f;
}
.delete {
cursor: pointer;
}
.filtered {
display: none !important;
}
span {
color: #fff;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- originally=> <script src="FontAwesome5/all.min.js"></script> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/js/all.min.js" integrity="sha512-LW9+kKj/cBGHqnI4ok24dUWNR/e8sUD8RLzak1mNw5Ja2JYCmTXJTF5VpgFSw+VoBfpMvPScCo2DnKTIUjrzYw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div class="container">
<ul class="list-group todos mx-auto text-light"></ul>
<form class="add text-center my-4" autocomplete="off">
<label class="text-light"> Add New Todo... </label>
<input type="text" class="form-control m-auto" name="add" />
</form>
</div>
I have bring all the codes to make easier the problem for you
Upvotes: 1
Views: 104
Reputation: 22265
First of All -> remove
<script src="FontAwesome5/all.min.js"></script>
because it replace your:
<i class="fa fa-trash delete"></i>
by a <svg>
element.
Also there some JS facility you should have to know:
HTML templates : https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
Event delegation = ( Event_bubbling + .matches()
method )
--> What is event delegation, and how does it work?
on you code:
const
inputElem = document.querySelector('input')
, addTodoForm = document.querySelector('.add')
, todoList = document.querySelector('.todos')
, todoElmT = document.querySelector("#todo-list-elm-template")
;
addTodoForm.addEventListener("submit", (e) => { e.preventDefault(); })
;
inputElem.addEventListener("keydown", (e) =>
{ // Add a new todo item when Enter key is pressed
if (e.key === 'Enter' )
{
let newTodoValue = inputElem.value.trim();
inputElem.value = '';
if (newTodoValue.length > 0 )
addNewTodo( newTodoValue );
}
});
todoList.addEventListener("click", (e) => // use event delegation for this
{
if (!e.target.matches('.delete') ) return; // ignore click event
todoList.removeChild(e.target.closest('li'));
});
function addNewTodo( newTodoValue ) // HTML template is usefull
{
const todoElm = todoElmT.content.cloneNode(true);
todoElm.querySelector('span')
.textContent = newTodoValue;
todoList.append(todoElm);
}
body {
background: #353f5b !important;
}
.container {
max-width : 400px;
margin-top : 90px;
}
.search i {
position : absolute;
}
.search {
position : relative;
}
.icon {
padding : 10px;
left : 0;
}
.input-field {
text-align: center;
}
input[type="text"],
input[type="text"]:focus {
color : #fff;
border : none;
background : rgba(0, 0, 0, 0.3);
max-width : 400px;
}
.todos li {
background : #423a6f;
}
.delete {
cursor : pointer;
color : orange
}
.filtered {
display : none !important;
}
span {
color : #fff;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<div class="container">
<ul class="list-group todos mx-auto text-light"></ul>
<form class="add text-center my-4" autocomplete="off">
<label class="text-light"> Add New Todo... </label>
<input type="text" class="form-control m-auto" name="add" />
</form>
</div>
<template id="todo-list-elm-template">
<li class="list-group-item d-flex justify-content-between align-items-center">
<span></span>
<i class="fa fa-trash delete"></i>
</li>
</template>
Upvotes: 0
Reputation: 48580
Your issue is that the <i class="fa fa-trash delete">
delete button is being replaced with an <svg>
element in the DOM. The original handler is no longer applicable.
Instead, you should attach a delegation listener to the <ul>
and check to see if you clicked inside the <svg>
.
todoUlElem.addEventListener("click", (e) => {
const svg = e.target.closest(".delete"); // Find the SVG
if (svg) {
e.target.closest('li').remove();
}
});
let inputElem = document.querySelector("input");
let addTodoForm = document.querySelector(".add");
let todoUlElem = document.querySelector(".todos");
function addNewTodo(newTodoValue) {
// Create the new li element
let newTodoLi = document.createElement("li");
newTodoLi.className =
"list-group-item d-flex justify-content-between align-items-center";
// Create the span for the todo title
let newTodoTitleSpan = document.createElement("span");
newTodoTitleSpan.innerHTML = newTodoValue;
// Create the trash icon element
let newTodoTrash = document.createElement("i");
newTodoTrash.className = "fa fa-trash delete";
// Append the title span and trash icon to the new li element
newTodoLi.append(newTodoTitleSpan, newTodoTrash);
// Append the new li element to the ul element
todoUlElem.append(newTodoLi);
}
// Prevent the default form submission
addTodoForm.addEventListener("submit", (e) => {
e.preventDefault();
});
// Add a new todo item when Enter key is pressed
inputElem.addEventListener("keydown", (e) => {
let newTodoValue = e.target.value.trim();
if (e.keyCode === 13) {
inputElem.value = "";
if (newTodoValue) {
addNewTodo(newTodoValue);
}
}
});
todoUlElem.addEventListener("click", (e) => {
const svg = e.target.closest(".delete"); // Find the SVG
if (svg) {
e.target.closest('li').remove();
}
});
body {
background: #353f5b !important;
}
.container {
max-width: 400px;
margin-top: 90px;
}
.search i {
position: absolute;
}
.search {
position: relative;
}
.icon {
padding: 10px;
left: 0;
}
.input-field {
text-align: center;
}
input[type="text"],
input[type="text"]:focus {
color: #fff;
border: none;
background: rgba(0, 0, 0, 0.3);
max-width: 400px;
}
.todos li {
background: #423a6f;
}
.delete {
cursor: pointer;
}
.filtered {
display: none !important;
}
span {
color: #fff;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- originally=> <script src="FontAwesome5/all.min.js"></script> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/js/all.min.js" integrity="sha512-LW9+kKj/cBGHqnI4ok24dUWNR/e8sUD8RLzak1mNw5Ja2JYCmTXJTF5VpgFSw+VoBfpMvPScCo2DnKTIUjrzYw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div class="container">
<ul class="list-group todos mx-auto text-light"></ul>
<form class="add text-center my-4" autocomplete="off">
<label class="text-light"> Add New Todo... </label>
<input type="text" class="form-control m-auto" name="add" />
</form>
</div>
You can add a click
handler to the todo list element, and delegate clicks down to the removal button.
// Where `ref` is the todo list component
ref.addEventListener('click', delegateClick);
// Where `.todo-remove` is a button for removing the list item
function delegateClick(event) {
if (event.target.matches('.todo-remove')) {
event.target.closest('li').remove();
}
}
See the example below:
const initialTasks = ['Take out the trash', 'Wash car', 'Clean the house'];
TodoList(document.querySelector('#my-todo'));
function TodoList(ref) {
ref.classList.add('todo');
ref.innerHTML = `
<form name="todo-form">
<input name="task" type="text" placeholder="Enter a task" autocomplete="off" required>
<button type="submit">Add</button>
</form>
<ul class="todo-list"></ul>
`;
const form = ref.querySelector('[name="todo-form"]');
form.addEventListener('submit', handleSubmit);
ref.addEventListener('click', delegateClick);
initialTasks.forEach(task => addItem(form, task));
function handleSubmit(event) {
event.preventDefault();
addItem(event.target, event.target.elements.task.value.trim());
}
function addItem(form, value) {
if (!value) return;
const todo = form.closest('.todo');
const items = todo.querySelector('.todo-list');
items.insertAdjacentHTML('beforeend', `
<li data-id="${'todo-' + crypto.randomUUID()}">
<span>${value}</span>
<button type="button" class="todo-remove" title="Remove"></button>
</li>
`);
form.reset();
}
function delegateClick(event) {
if (event.target.matches('.todo-remove')) {
event.target.closest('li').remove();
}
}
};
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
align-items: flex-start;
justify-content: center;
padding: 1rem;
}
.todo {
display: flex;
flex-direction: column;
gap: 1rem;
border: thin solid grey;
padding: 0.5rem;
}
.todo form {
display: flex;
gap: 1rem;
}
.todo input[name="task"] {
border: none;
border-bottom: thin solid grey;
}
.todo-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin: 0;
padding: 0;
}
.todo-list li {
display: flex;
margin: 0;
padding: 0;
}
.todo-list span {
flex: 1;
}
.todo-remove {
background: red;
border: black;
color: white;
font-weight: bold;
}
.todo-remove:hover {
background: #F77;
cursor: pointer;
}
.todo-remove:after {
content: "x"
}
<div id="my-todo"></div>
Upvotes: 0