在原版 javascript 待办事项列表中过滤待办事项

Filter todo items in vanilla javascript todo list

我正在构建一个待办事项列表,现在我一直在尝试实现 HTML-select 下拉列表以在已完成和未完成的待办事项之间进行过滤。 我正在使用 switch 语句 select 要执行的各个块,但是我不断收到以下错误“Uncaught TypeError: Cannot set 属性 'display' of undefined”,但我不明白什么元素未定义。

const input = document.getElementById('input')
const addTodoButton = document.getElementById('addTodoButton')
const todoUL = document.getElementById('todoUL')
const filterOptions = document.querySelector('.filter-todos')

addTodoButton.addEventListener('click', addTodo)
todoUL.addEventListener('click', remove);
filterOptions.addEventListener('click', filterTodos);

// Add todo
function addTodo(e) {
    e.preventDefault()
    const todoText = input.value
    const todoEl = `<li><span>${todoText}</span> <button class="delete" id="deleteTodoButton"><i class="far fa-trash-alt"></i>Delete</button> <button class="complete" id="completeTodoButton"><i class="fas fa-check"></i>Completed</button></li>`
    input.value = ""
    input.focus()

    if (!todoText) {
        alert('You must type a todo')
    } else {
        todoUL.insertAdjacentHTML("beforeend", todoEl)
    }
}

// Remove/Complete todo
function remove(e) {
    if (e.target.id == 'deleteTodoButton') {
        e.target.parentElement.remove()
        input.focus()
    } else {
        e.target.previousElementSibling.previousElementSibling.classList.toggle('completed')
        input.focus()
    }
}

function filterTodos(e) {
    const todos = todoUL.childNodes
    todos.forEach(function (todoEl) {
        switch (e.target.value) {
            case "all":
                todoEl.style.display = "flex"
                break;

            case "completed":
                if (todoEl.classList.contains("completed")) {
                    todoEl.style.display = "flex"
                } else {
                    todoEl.style.display = "none"
                }
                break;
        }
    })
}
ul {
  list-style: none
}

.completed {
  text-decoration: line-through
}
<div class="form-container">
    <h1>Todo List App</h1>
    <form id="form">
      <input type="text" id="input" autocomplete="off" placeholder="Enter your todo">
      <button type="submit" class="add-todo" id="addTodoButton">Add</button>
      <select name="todos" class="filter-todos">
        <option value="all">All</option>
        <option value="completed">Completed</option>
        <option value="uncompleted">Uncompleted</option>
      </select>
    </form>
    <ul id="todoUL">
    </ul>
  </div>

const input = document.getElementById('input')
const addTodoButton = document.getElementById('addTodoButton')
const todoUL = document.getElementById('todoUL')
const filterOptions = document.querySelector('.filter-todos')

addTodoButton.addEventListener('click', addTodo)
todoUL.addEventListener('click', remove);
filterOptions.addEventListener('click', filterTodos);

// Add todo
function addTodo(e) {
    e.preventDefault()
    const todoText = input.value
    const todoEl = `<li><span>${todoText}</span> <button class="delete" id="deleteTodoButton"><i class="far fa-trash-alt"></i>Delete</button> <button class="complete" id="completeTodoButton"><i class="fas fa-check"></i>Completed</button></li>`
    input.value = ""
    input.focus()

    if (!todoText) {
        alert('You must type a todo')
    } else {
        todoUL.insertAdjacentHTML("beforeend", todoEl)
    }
}

// Remove/Complete todo
function remove(e) {
    if (e.target.id == 'deleteTodoButton') {
        e.target.parentElement.remove()
        input.focus()
    } else {
        e.target.previousElementSibling.previousElementSibling.classList.toggle('completed')
        input.focus()
    }
}

function filterTodos(e) {
    const todos = todoUL.querySelectorAll('li > span');
    todos.forEach(function (todoEl) {
        const tgt = todoEl.parentElement;
        switch (e.target.value) {
            case "all":
                tgt.style.display = "flex"
                break;

            case "completed":
                if (todoEl.classList.contains("completed")) {
                    tgt.style.display = "flex"
                } else {
                    tgt.style.display = "none"
                }
                break;
            case "uncompleted":
                if (todoEl.classList.contains("completed")) {
                    tgt.style.display = "none"
                } else {
                    tgt.style.display = "flex"
                }
                break;
        }
    })
}
ul {
  list-style: none
}

.completed {
  text-decoration: line-through
}
<div class="form-container">
    <h1>Todo List App</h1>
    <form id="form">
      <input type="text" id="input" autocomplete="off" placeholder="Enter your todo">
      <button type="submit" class="add-todo" id="addTodoButton">Add</button>
      <select name="todos" class="filter-todos">
        <option value="all">All</option>
        <option value="completed">Completed</option>
        <option value="uncompleted">Uncompleted</option>
      </select>
    </form>
    <ul id="todoUL">
    </ul>
  </div>

我已经在我的解释方式中做到了这一点。希望这会在某些方面对您有所帮助。

我发现您编写的代码存在一些问题

首先,select 的 click 事件应该更改为 change 事件

其次,在 remove 函数的情况下,您必须在将其标记为已完成之前检查 e.target.id == "completeTodoButton",否则我们的节点 selection 将无法工作。

第三,如果是 filterTodos 函数,您必须在切换显示之前确保节点类型是 li。我为此使用了 todoEl.nodeName === "LI" 比较。

在 switch case 中,你不能使用 todoEl.classList.contains("completed") 因为完成的 class 没有被绑定到 li 而是绑定到 span 标签里面 li。您可以将 class 移动到 li 节点并相应地删除元素,或者如果您想继续使用当前结构,您应该使用 todoEl.children[0].classList.contains("completed") 因为 [= 的第一个子节点17=] 是 class completed.

的跨度

你还漏掉了 switch 中的 case uncompleted

ul {
    list-style: none;
}

.completed {
    text-decoration: line-through;
}
<div class="form-container">
    <h1>Todo List App</h1>
    <form id="form">
    <input
        type="text"
        id="input"
        autocomplete="off"
        placeholder="Enter your todo"
    />
    <button type="submit" class="add-todo" id="addTodoButton">Add</button>
    <select name="todos" class="filter-todos">
        <option value="all">All</option>
        <option value="completed">Completed</option>
        <option value="uncompleted">Uncompleted</option>
    </select>
    </form>
    <ul id="todoUL"></ul>
</div>

<script>
    const input = document.getElementById("input");
    const addTodoButton = document.getElementById("addTodoButton");
    const todoUL = document.getElementById("todoUL");
    const filterOptions = document.querySelector(".filter-todos");

    addTodoButton.addEventListener("click", addTodo);
    todoUL.addEventListener("click", remove);
    filterOptions.addEventListener("change", filterTodos);

    // Add todo
    function addTodo(e) {
      e.preventDefault();
      const todoText = input.value;
      const todoEl = `<li><span>${todoText}</span>
        <button class="delete" id="deleteTodoButton"><i class="far fa-trash-alt"></i>Delete</button>
        <button class="complete" id="completeTodoButton"><i class="fas fa-check"></i>Completed</button></li>`;
      input.value = "";
      input.focus();

      if (!todoText) {
        alert("You must type a todo");
      } else {
        todoUL.insertAdjacentHTML("beforeend", todoEl);
      }
    }

    // Remove/Complete todo
    function remove(e) {
      if (e.target.id == "deleteTodoButton") {
        e.target.parentElement.remove();
        input.focus();
      } else if (e.target.id == "completeTodoButton") {
        e.target.previousElementSibling.previousElementSibling.classList.toggle(
          "completed"
        );
        input.focus();
      }
    }

    function filterTodos(e) {
      const todos = todoUL.childNodes;
      todos.forEach(function(todoEl) {
        if (todoEl.nodeName === "LI") {
          switch (e.target.value) {
            case "all":
              todoEl.style.display = "flex";
              break;

            case "completed":
              if (todoEl.children[0].classList.contains("completed")) {
                todoEl.style.display = "flex";
              } else {
                todoEl.style.display = "none";
              }
              break;

            case "uncompleted":
              if (todoEl.children[0].classList.contains("completed")) {
                todoEl.style.display = "none";
              } else {
                todoEl.style.display = "flex";
              }
              break;
          }
        }
      });
    }
  </script>