JavaScript Todo List: Trash Icon Click Event Not Deleting List Item

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

Answers (2)

Mister Jojo
Mister Jojo

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

Mr. Polywhirl
Mr. Polywhirl

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>

A more streamlined approach

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

Related Questions