Список задач JavaScript: событие щелчка по значку корзины не удаляет элемент списка

Я работаю над простым проектом JavaScript, в котором составляю список дел. Цель состоит в том, чтобы позволить пользователям добавлять новые элементы задач и удалять их, щелкнув значок корзины. Однако у меня возникли проблемы с тем, чтобы функция удаления работала должным образом.

Что я пробовал:

Добавление прослушивателя событий: я прикрепил прослушиватель событий непосредственно к значку корзины (<i>) внутри функции addNewTodo(). Предполагается, что этот прослушиватель сработает при нажатии на значок, а затем удалит соответствующий элемент <li> из DOM.

Регистрация события клика: я добавил оператор console.info в прослушиватель событий, чтобы убедиться, что событие клика срабатывает. Это сообщение журнала не работает, поскольку событие щелчка не срабатывает.

Удаление элемента задачи. В том же прослушивателе событий я вызвал метод remove() родительского элемента <li> (newTodoLi()), чтобы удалить элемент задачи из списка. Я ожидал, что щелчок по значку корзины приведет к удалению соответствующего элемента задачи.

Чего я ожидал:

Я ожидал, что каждый раз, когда я нажимаю на значок корзины рядом с элементом задачи, этот элемент будет немедленно удален из списка без каких-либо проблем. Поскольку прослушиватель событий, казалось, сработал правильно, я ожидал, что вызов remove() родительского элемента <li> надежно удалит элемент.

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.info("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>

Я принес все коды, чтобы облегчить вам задачу.

Пожалуйста, покажите код, а не описывайте его. Мы не можем отладить код, который вы нам не показали.

Rory McCrossan 09.08.2024 12:39

удалить <script src = "FontAwesome5/all.min.js"></script> оптимизатор JS FontAwesome5

Mister Jojo 09.08.2024 13:24

Помимо того, что писали другие: семантически плохая идея прикреплять событие щелчка к элементу значка. Для обеспечения доступности события кликов должны быть прикреплены к элементам, которые обычно их принимают, например к ссылкам (<a>) или, в данном случае, к button, и помещать значок внутрь этой кнопки.

RoToRa 09.08.2024 14:18
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
3
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ваша проблема в том, что кнопка удаления <i class = "fa fa-trash delete"> заменяется элементом <svg> в DOM. Исходный обработчик больше не применим.

Вместо этого вам следует прикрепить прослушиватель делегирования к <ul> и проверить, нажали ли вы внутри <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>

Более рациональный подход

Вы можете добавить обработчик click к элементу списка дел, и делегат нажмет кнопку удаления.

// 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();
  }
}

См. пример ниже:

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>

Прежде всего -> удалить
<script src = "FontAwesome5/all.min.js"></script>
потому что оно заменяет ваше:
<i class = "fa fa-trash delete"></i>
элементом <svg>.

Также вам следует знать некоторые возможности JS:
HTML-шаблоны: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template

Делегирование события = (Event_bubbling + метод .matches())

--> Что такое делегирование мероприятий и как оно работает?

по вашему коду:

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>

Другие вопросы по теме