Старые события добавляются каждый раз, когда при отправке происходит аналогичное событие

#javascript #html #dom-events

#javascript #HTML #dom-события

Вопрос:

Я пытался составить список дел в ванильном JS. Для добавления элементов в список есть кнопка Добавить. Когда для определенного элемента выбран параметр редактировать, кнопка добавить изменяется на редактировать, а поле ввода принимает отредактированное значение.

Когда я редактирую первый элемент, он работает нормально. Но когда я редактирую второй элемент, значение первого и второго элементов изменяется. То же самое происходит при редактировании третьего элемента, значение второго и первого элементов также изменяется. Я не уверен, почему это происходит.

 const todoForm = document.querySelector('.todo-form');
// select the input box

const todoInput = document.querySelector('.todo-input');
// select the <ul> with class="todo-items"

const todoItemsList = document.querySelector('.todo-items');
const butt = document.querySelector('.add-button');

let todos = [];
todoForm.addEventListener('submit', (e) => {
    if(butt.innerHTML === "Add"){
        console.log("Add");
    e.preventDefault();
    AddTo(todoInput.value);
    console.log(todoInput.value);}
});

function AddTo(item){
    console.log(item);
    if(item!= ''){
        const todo = {
            id : Date.now(),
            name : item,
            completed : false
        }

        todos.push(todo);
        addToLocal();
        todoInput.value = '';
    }
}

function renderTo(){
    todoItemsList.innerHTML = '';
    todos.forEach((item) => {
        const checked = item.completed ? 'checked' : null;

        const li = document.createElement('li');
        li.setAttribute('class','item');
        li.setAttribute('data-key', item.id);

        if(item.completed == true){
            li.classList.add('checked');
        }

        li.innerHTML = `<input type = "checkbox" class = "checkbox" ${checked}> ${item.name} <button class = "delete-button">X</button> <button class = "edit-button">E</button>`;
        todoItemsList.append(li);

    })

}

function addToLocal(){
    localStorage.setItem('todos', JSON.stringify(todos));
    renderTo();
}

function getFromLocal(){
    const reference = localStorage.getItem('todos');

    if(reference){
        todos = JSON.parse(reference);
        renderTo()
    }


}

getFromLocal();

todoItemsList.addEventListener('click', (e) => {
    if(e.target.type === 'checkbox'){
        toggle(e.target.parentElement.getAttribute('data-key'));
    }

    if(e.target.classList.contains('delete-button')){
        del(e.target.parentElement.getAttribute('data-key'));
    }
    if(e.target.classList.contains('edit-button')){
        edit(e.target.parentElement.getAttribute('data-key'));
    }
})

function toggle(id){
    todos.forEach((item) => {
        if(item.id == id){
            item.completed = !item.completed;
        }
    });
    addToLocal();
}

function del(id){
    todos = todos.filter((item) => {
        return item.id != id;
    });
    addToLocal();
}

function edit(id){
    butt.innerHTML = "Edit";
    todos.forEach((item) => {
        if(item.id == id){
            todoInput.value = item.name;
            console.log(item);
            todoForm.addEventListener('submit', (e) => {
                e.preventDefault();
                console.log(item);
                item.name = todoInput.value;
                addToLocal();
            })
        }
    })
}  
 * {
    padding: 0;
    margin: 0;
  }
  
  body {
    width: 100vw;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    background: linear-gradient(#F00000, #DC281E);
    font-family: sans-serif;
  }
  
  button:hover {
    cursor: pointer;
    background-color: #73E831;
  }
  
  ul {
    list-style-type: none; 
  /* get rid of bullet points on side of list items */
  
  }
  
  /* common style ends */
  
  
  /* container */
  
  .container {
    min-width: 700px;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
  }
  
  h1 {
    color: #fff;
    font-size: 3rem;
  }
  
  /* todo-form */
  
  
  .todo-form {
    margin: 40px 0px;
  }
  
  .todo-input {
    width: 250px;
    border: none;
    outline: none;
    border-radius: 5px;
    padding: 10px;
    margin-right: 10px;
    font-size: 1rem;
  }
  
  .add-button {
    background-color: #0000ff;
    color: #fff;
    border: none;
    outline: none;
    border-radius: 5px;
    padding: 7px;
    font-size: 1.2rem;
  }
  
  /* todo-form style ends */
  
  
  /* todo-items */
  
  .todo-items {
    min-width: 350px;
  }
  
  /* each li with class="item" */
  
  .item {
    background-color: #fff;
    padding: 10px;
    font-size: 1.1rem;
  }
  
  .item:first-child {
    border-top-left-radius: 7px;
    border-top-right-radius: 7px;
  }
  
  .item:last-child {
    border-bottom-left-radius: 7px;
    border-bottom-right-radius: 7px;
  }
  
  /* item style end */
  
  
  .checkbox {
    margin-right: 10px;
  }
  
  .delete-button {
    float: right;
    background-color: #dc143c;
    border: none;
    outline: none;
    border-radius: 7px;
    padding: 2px 5px;
    margin-left: 10px;
    font-size: 1.1rem;
    font-weight: 550;
  }
  .edit-button {
    float: right;
    background-color: blue;
    border: none;
    outline: none;
    border-radius: 7px;
    padding: 2px 5px;
    margin-left: 3px;
    font-size: 1.1rem;
    font-weight: 550;
  }
  
  /* applied when the todo item is checked */
  
  .checked { 
    text-decoration: line-through;
  }
    
 <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" type="text/css" href="style.css">
  <title>Todo List</title>
</head>
<body>

  <div class="container">
    <h1>Todo</h1>

    <form class="todo-form">
      <input type="text" class="todo-input" placeholder="Add a Todo...">
      <button type="submit" class="add-button">Add</button>
    </form>

    <ul class="todo-items">
      
<!-- dummy item -->

      <li class="item" data-key="1594003133171">
        <input class="checkbox" type="checkbox">
        Go to Gym
        <button class="delete-button">X</button>
      </li>
    </ul>
  </div>
  
  <script type="text/javascript" src="script.js"></script>
</body>
</html>  

Ответ №1:

Вы создаете новый прослушиватель событий «отправить» для каждого выбора и никогда не очищаете их.

Каждый выбор добавляет новый прослушиватель событий к существующим

См. Изменение в методе редактирования или см. jsfiddle

 const todoForm = document.querySelector('.todo-form');
// select the input box

const todoInput = document.querySelector('.todo-input');
// select the <ul> with class="todo-items"

const todoItemsList = document.querySelector('.todo-items');
const butt = document.querySelector('.add-button');

let todos = [];
todoForm.addEventListener('submit', (e) => {
    if(butt.innerHTML === "Add"){
        console.log("Add");
    e.preventDefault();
    AddTo(todoInput.value);
    console.log(todoInput.value);}
});

function AddTo(item){
    console.log(item);
    if(item!= ''){
        const todo = {
            id : Date.now(),
            name : item,
            completed : false
        }

        todos.push(todo);
        addToLocal();
        todoInput.value = '';
    }
}

function renderTo(){
    todoItemsList.innerHTML = '';
    todos.forEach((item) => {
        const checked = item.completed ? 'checked' : null;

        const li = document.createElement('li');
        li.setAttribute('class','item');
        li.setAttribute('data-key', item.id);

        if(item.completed == true){
            li.classList.add('checked');
        }

        li.innerHTML = `<input type = "checkbox" class = "checkbox" ${checked}> ${item.name} <button class = "delete-button">X</button> <button class = "edit-button">E</button>`;
        todoItemsList.append(li);

    })

}

function addToLocal(){
    localStorage.setItem('todos', JSON.stringify(todos));
    renderTo();
}

function getFromLocal(){
    const reference = localStorage.getItem('todos');

    if(reference){
        todos = JSON.parse(reference);
        renderTo()
    }


}

getFromLocal();

todoItemsList.addEventListener('click', (e) => {
    if(e.target.type === 'checkbox'){
        toggle(e.target.parentElement.getAttribute('data-key'));
    }

    if(e.target.classList.contains('delete-button')){
        del(e.target.parentElement.getAttribute('data-key'));
    }
    if(e.target.classList.contains('edit-button')){
        edit(e.target.parentElement.getAttribute('data-key'));
    }
})

function toggle(id){
    todos.forEach((item) => {
        if(item.id == id){
            item.completed = !item.completed;
        }
    });
    addToLocal();
}

function del(id){
    todos = todos.filter((item) => {
        return item.id != id;
    });
    addToLocal();
}

function edit(id){
        console.log(id)
    butt.innerHTML = "Edit";
    todos.forEach((item) => {
        if(item.id == id){
                
            todoInput.value = item.name;
            console.log('in for', item);
            const listener = (e) => {
                e.preventDefault();
                console.log('submit', item);
                item.name = todoInput.value;
                addToLocal();
                todoForm.removeEventListener('submit', listener) //<-- clear listener when its done
            }
            todoForm.addEventListener('submit', listener)
        }
    })
}  
 * {
    padding: 0;
    margin: 0;
  }
  
  body {
    width: 100vw;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    background: linear-gradient(#F00000, #DC281E);
    font-family: sans-serif;
  }
  
  button:hover {
    cursor: pointer;
    background-color: #73E831;
  }
  
  ul {
    list-style-type: none; 
  /* get rid of bullet points on side of list items */
  
  }
  
  /* common style ends */
  
  
  /* container */
  
  .container {
    min-width: 700px;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
  }
  
  h1 {
    color: #fff;
    font-size: 3rem;
  }
  
  /* todo-form */
  
  
  .todo-form {
    margin: 40px 0px;
  }
  
  .todo-input {
    width: 250px;
    border: none;
    outline: none;
    border-radius: 5px;
    padding: 10px;
    margin-right: 10px;
    font-size: 1rem;
  }
  
  .add-button {
    background-color: #0000ff;
    color: #fff;
    border: none;
    outline: none;
    border-radius: 5px;
    padding: 7px;
    font-size: 1.2rem;
  }
  
  /* todo-form style ends */
  
  
  /* todo-items */
  
  .todo-items {
    min-width: 350px;
  }
  
  /* each li with class="item" */
  
  .item {
    background-color: #fff;
    padding: 10px;
    font-size: 1.1rem;
  }
  
  .item:first-child {
    border-top-left-radius: 7px;
    border-top-right-radius: 7px;
  }
  
  .item:last-child {
    border-bottom-left-radius: 7px;
    border-bottom-right-radius: 7px;
  }
  
  /* item style end */
  
  
  .checkbox {
    margin-right: 10px;
  }
  
  .delete-button {
    float: right;
    background-color: #dc143c;
    border: none;
    outline: none;
    border-radius: 7px;
    padding: 2px 5px;
    margin-left: 10px;
    font-size: 1.1rem;
    font-weight: 550;
  }
  .edit-button {
    float: right;
    background-color: blue;
    border: none;
    outline: none;
    border-radius: 7px;
    padding: 2px 5px;
    margin-left: 3px;
    font-size: 1.1rem;
    font-weight: 550;
  }
  
  /* applied when the todo item is checked */
  
  .checked { 
    text-decoration: line-through;
  }
    
 <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" type="text/css" href="style.css">
  <title>Todo List</title>
</head>
<body>

  <div class="container">
    <h1>Todo</h1>

    <form class="todo-form">
      <input type="text" class="todo-input" placeholder="Add a Todo...">
      <button type="submit" class="add-button">Add</button>
    </form>

    <ul class="todo-items">
      
<!-- dummy item -->

      <li class="item" data-key="1594003133171">
        <input class="checkbox" type="checkbox">
        Go to Gym
        <button class="delete-button">X</button>
      </li>
    </ul>
  </div>
  
  <script type="text/javascript" src="script.js"></script>
</body>
</html>  

Комментарии:

1. Мне было интересно, почему мы не удаляем прослушиватель при добавлении новых элементов?

2. чтобы удалить всех слушателей при любом добавлении, вам нужно отслеживать обратный вызов слушателя. это будет самый безопасный способ