开发者问题收集

如何在事件监听器中传递项目 ID 来更新列表输入项?

2021-07-14
194

我已经设法自行构建了待办事项列表,并实现了 addTodo 和添加到本地存储/从本地存储获取的功能。我现在需要添加更新功能来更新待办事项列表项。我知道我首先需要将事件侦听器附加到 ul 并找到最接近的 li 项 id 或类似的东西。然后我需要将更新函数传递给事件侦听器以更新列表中的输入值。这是我目前所拥有的。我将如何创建事件侦听器和更新函数来更新列表项?

所需输出:用户单击文本并更改项目的文本后,添加项目的文本需要更新(到本地存储)。

注意:该代码片段不起作用,因为不允许本地存储(据我所知)。您需要查看 codepen

// select DOM elements
const todoForm = document.querySelector('.todo-form');
const todoInput = document.querySelector('.todo-input');
const todoItemsList = document.querySelector('.todo-items');

// array which stores every todo item
// each item will be an object with id, name, completed boollean
let todos = [];

// add an event listener on form
todoForm.addEventListener('submit', function(e) {
  e.preventDefault();
  // input param from addTodo is todoInput.value
  addTodo(todoInput.value);
});

document.addEventListener('click', (e) => {
  if (e.target.classList.contains('checkbox')) {
    if (e.target.checked) e.target.closest('.item').classList.add('checked');
    else e.target.closest('.item').classList.remove('checked');
  }
});

// todoItemsList.addEventListener('input', (e) => {
//  const taskId = e.target.item.id;
//  console.log(taskId);
// });

// todoItemsList.addEventListener('input', (e) => {
//  const taskId = e.target.closest('li').id
//  console.log(taskId)
//  // updateTask(taskId, e.target)
// })



function addTodo(input) {
  if (input !== '') {
    const todo = {
      id: Date.now(),
      name: input,
      completed: false,
    };
    todos.push(todo);
    addToLocalStorage(todos);
    todoInput.value = '';
  }
}

// todos is passed as parameter because it exists outside function environment
function renderTodos(todos) {
  // clear list
  todoItemsList.innerHTML = '';
  // run through each item inside todos
  todos.forEach((item) => {
    // checkbox
    let cb = document.createElement('input');
    cb.type = 'checkbox';
    cb.classList.add('checkbox');
    cb.checked = false;
    // delete button
    const deleteButton = document.createElement('button');
    deleteButton.className = 'delete-button';
    deleteButton.innerText = 'x';
    // lists
    const li = document.createElement('li');
    li.classList.add('item');
    li.setAttribute('data-key', item.id);
    li.appendChild(cb);
    li.append(item.name);
    li.append(deleteButton);
    todoItemsList.append(li);
  });
}

function addToLocalStorage(todos) {
  // name key 'todos" and value todos array
  // convert array to string and store it
  localStorage.setItem('todos', JSON.stringify(todos));
  renderTodos(todos);
}

function getFromLocalStorage() {
  const reference = localStorage.getItem('todos');
  if (reference) {
    // converts string back to an array and store in todos array
    todos = JSON.parse(reference);
    renderTodos(todos);
  }
}

getFromLocalStorage();
* {
  padding: 0;
  margin: 0;
}

body {
  width: 100vw;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  background: hsl(194, 100%, 70%);
  font-family: 'Roboto', sans-serif;
}

button {
  display: inline-block;
  padding: 0.35em 1.2em;
  border: 0.1em solid hsl(0, 0%, 0%);
  margin: 0 0.3em 0.3em 0;
  background: hsl(0, 0%, 0%);
  border-radius: 0.12em;
  box-sizing: border-box;
  text-decoration: none;
  font-family: 'Roboto', sans-serif;
  font-weight: 300;
  color: hsl(0, 0%, 100%);
  text-align: center;
  transition: all 0.2s;
}

button:hover {
  cursor: pointer;
  background-color: hsl(0, 0%, 100%);
  color: hsl(214, 11%, 13%);
}

ul {
  list-style-type: none;
}

.container {
  min-width: 700px;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
}

h1 {
  color: hsl(0, 0%, 100%);
  font-size: 3rem;
}


/* To Do 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;
}

.todo-items {
  min-width: 350px;
  height: auto;
}


/* Item style */

.item {
  height: auto;
  background-color: #fff;
  margin: 1em;
  padding: 10px;
  font-size: 1.2rem;
  vertical-align: middle;
  border-radius: 5px;
}

.checkbox {
  width: 1.5em;
  height: 1.5em;
  margin-right: 20px;
  vertical-align: middle;
}

.delete-button {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  float: right;
  width: 25px;
  height: 25px;
  background-color: hsl(348, 93%, 56%);
  border: none;
  outline: none;
  /* border-radius: 7px; */
  /* padding: 2px 5px; */
  /* margin-left: 10px; */
  font-size: 0.8rem;
  font-weight: 550;
  text-align: center;
  vertical-align: middle;
}

.delete-button:hover {
  background: pink;
}

.checked {
  text-decoration: line-through;
}

.text-container {
  min-width: 100px;
  height: 50px;
  background: red;
}
<div class="container">
  <h1>To Do</h1>

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

  <ul class="todo-items">
  </ul>
</div>
2个回答

实际上,这里不需要 ID 。您可以继续使用元素的索引来确定它属于哪个待办事项。查看以下代码

todoItemsList.addEventListener('click', (e) => {
    // Anywhere inside UL clicked

    let clickedElement = e.target;
    if (clickedElement.classList.contains('delete-button') || clickedElement.classList.contains('checkbox')) {
        // Either checkbox or delete button is clicked

        let listElement = clickedElement.parentNode;
        let listElementsArray = todoItemsList.querySelectorAll('li');
        let index = Array.prototype.indexOf.call(listElementsArray, listElement);

        if (clickedElement.classList.contains('delete-button')) {
            //Delete button clicked
            listElement.remove();
            todos.splice(index, 1);
        } else if (clickedElement.classList.contains('checkbox')) {
            //Checkbox clicked
            todos[index].completed = e.target.checked;
        }

        console.log(todos);
        // Update in localstorage
        localStorage.setItem('todos', JSON.stringify(todos));
    }
});

注意:这里我使用了 Array.prototype.indexOf.call 而不是 listElementsArray.indexOf ,因为 listElementsArray 是一个节点列表,它本身不支持 indexOf

Caleb Hillary
2021-07-14

我重新编写了你的​​代码,因为我在其中迷失了方向...

你感兴趣的部分在最后,其中:

todoList.onclick = ({target}) =>
  {
  if (!target.matches('li input[type=checkbox], button.delete-button')) return

  let li  = target.closest('li')
    , idx = todos.findIndex(x=>x.id==li.dataset.key)
    ;    
  switch (target.tagName.toLowerCase())
    {
    case 'input':
                  todos[idx].completed = target.checked
                  break;
    case 'button':
                  todos.splice(idx, 1)
                  todoList.removeChild(li)
                  break;
    }
  todos.update_LocalStorage()
  }

它是一个事件委托,用于管理复选框或删除按钮

完整代码

const 
  todoForm = document.querySelector('#todo-form')
, todoList = document.querySelector('#todo-items')
, todos    = JSON.parse( localStorage.getItem('todos') || '[]')
  ;
todos.UI_add = item =>
  {
  let li = document.createElement('li')
  li.dataset.key = item.id
  li.innerHTML   = `
    <input type="checkbox" ${item.completed ? 'checked':''} >
    <span>${item.name}</span>
    <button class="delete-button">x</button>`
  todoList.appendChild(li)
  }
todos.update_LocalStorage = _ =>
  {
  localStorage.setItem('todos', JSON.stringify(todos))
  }
todoForm.onsubmit = e =>
  {
  e.preventDefault()

  let str = todoForm['todo-text'].value.trim()
  if (str !== '')
    {
    let item = { id: Date.now(), name:str, completed: false }
    todos.push( item ) 
    todos.UI_add( item )
    todos.update_LocalStorage()
    todoForm['todo-text'].value = ''
    todoForm['todo-text'].focus()
    }
  }
todoList.onclick = ({target}) =>
  {
  if (!target.matches('li input[type=checkbox], button.delete-button')) return

  let li  = target.closest('li')
    , idx = todos.findIndex(x=>x.id==li.dataset.key)
    ;    
  switch (target.tagName.toLowerCase())
    {
    case 'input':
                  todos[idx].completed = target.checked
                  break;
    case 'button':
                  todos.splice(idx, 1)
                  todoList.removeChild(li)
                  break;
    }
  todos.update_LocalStorage()
  }

// init user interface list
todos.forEach(todos.UI_add)
* {
  padding: 0;
  margin: 0;
  }

body {
  width: 100vw;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  background: hsl(194, 100%, 70%);
  font-family: 'Roboto', sans-serif;
  }

    button  {
      display: inline-block;
      padding: 0.35em 1.2em;
      border: 0.1em solid hsl(0, 0%, 0%);
      margin: 0 0.3em 0.3em 0;
      background: hsl(0, 0%, 0%);
      border-radius: 0.12em;
      box-sizing: border-box;
      text-decoration: none;
      font-family: 'Roboto', sans-serif;
      font-weight: 300;
      color: hsl(0, 0%, 100%);
      text-align: center;
      transition: all 0.2s;
    }

    button:hover {
      cursor: pointer;
      background-color: hsl(0, 0%, 100%);
      color: hsl(214, 11%, 13%);
    }
 

.container {
  min-width      : 700px;
  display        : flex;
  flex-direction : column;
  align-items    : center;
  padding        : 20px;
  }

h1 {
  color     : hsl(0, 0%, 100%);
  font-size : 3rem;
}

form#todo-form {
  margin : 40px 0;
  }
form#todo-form input {
  width         : 250px;
  border        : none;
  outline       : none;
  border-radius :  5px;
  padding       : 10px;
  margin-right  : 10px;
  font-size     : 1rem;
  }

ul#todo-items {
  min-width  : 350px;
  height     : auto;
  list-style : none;
  }
ul#todo-items li {
  height           : auto;
  background-color : #fff;
  margin           : 1em;
  padding          : 10px;
  font-size        : 1.2rem;
  vertical-align   : middle;
  border-radius    : 5px;
  }

ul#todo-items li input[type=checkbox] {
  width          : 1.5em;
  height         : 1.5em;
  margin-right   : 20px;
  vertical-align : middle;
  }
.delete-button {
  display          : inline-flex;
  justify-content  : center;
  align-items      : center;
  float            : right;
  width            : 25px;
  height           : 25px;
  background-color : hsl(348, 93%, 56%);
  border           : none;
  outline          : none;
  font-size        : 0.8rem;
  font-weight      : 550;
  text-align       : center;
  vertical-align   : middle;
  }
.delete-button:hover {
  background: pink;
  }
ul#todo-items li input:checked + span {
  text-decoration: line-through;
}
<div class="container">
  <h1>To Do</h1>

  <form id="todo-form">
    <input type="text" name="todo-text" placeholder="add a todo item">
    <button type="submit"> Add </button>
  </form>

  <ul id="todo-items"> </ul>
</div>
Mister Jojo
2021-07-14