skip to Main Content

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

2

Answers


  1. 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>
    Login or Signup to reply.
  2. 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>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search