You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

待办事项列表(To-Do List)功能添加方法及过滤按钮失效问题排查求助

To-Do List: Filter Fixes & Feature Expansion Ideas

Hey there! Let's break down your problems and solutions step by step.

一、Why Your Filter Isn't Working (And How to Fix It)

Looking at your code, the main issues causing the filter to fail until refresh are:

1. Stale DOM Element References

You initialize todosEl = document.querySelectorAll('li') once when the page loads. But when you add new todos, this variable doesn't update—it still only points to the original list items. So your filter functions ignore any new todos you add until you refresh (which re-fetches all lis).

2. Duplicate Event Listeners

Your btnClicked() function adds a new click listener to the button group every time a filter button is clicked. After a few clicks, this creates overlapping listeners that mess up the active button state.

3. Minor Filter Logic Redundancy

The filterAll() function has an unnecessary condition (if (el || el.classList.contains('completed')))—you just need to show all items regardless of their state.

Fixed Code

Let's rewrite the filter-related code to fix these issues:

First, Update Filter Functions to Fetch Fresh Todos

// Helper to always get the latest list of todo items
function getCurrentTodos() {
  return document.querySelectorAll('li');
}

// Filter completed todos
function filterCompleted() {
  const todosEl = getCurrentTodos();
  todosEl.forEach(el => {
    el.style.display = el.classList.contains('completed') ? 'block' : 'none';
  });
}

// Filter active (uncompleted) todos
function filterActive() {
  const todosEl = getCurrentTodos();
  todosEl.forEach(el => {
    el.style.display = el.classList.contains('completed') ? 'none' : 'block';
  });
}

// Show all todos
function filterAll() {
  const todosEl = getCurrentTodos();
  todosEl.forEach(el => {
    el.style.display = 'block';
  });
}

Fix Event Binding for Filter Buttons

Instead of adding listeners inside btnClicked(), bind the active state handler once when the page loads. Also, remove the redundant btnClicked() call from your HTML buttons:

// Set up filter button state handling once on page load
document.addEventListener('DOMContentLoaded', () => {
  const buttons = document.querySelector('.btn-grup');
  const filters = document.querySelectorAll('.button--filter');

  buttons.addEventListener('click', (e) => {
    // Make sure we're targeting a filter button
    const clickedBtn = e.target.closest('.button--filter');
    if (!clickedBtn) return;

    // Update active button state
    filters.forEach(filter => filter.classList.remove('active'));
    clickedBtn.classList.add('active');
  });
});

Update Your HTML Buttons

Remove btnClicked() from the onclick attributes:

<button class="button--filter active" onclick="filterAll()" id="filter-all" type="button">
  All
</button>
<button class="button--filter" onclick="filterActive()" id="filter-active" type="button">
  Active
</button>
<button class="button--filter" onclick="filterCompleted()" id="filter-completed" type="button">
  Completed
</button>

Bonus: Keep Filter State When Updating Todos

To make sure filters stay working when you mark a todo as completed or add a new one, add this helper to reapply the current filter after any todo change:

// Get the currently active filter
function getActiveFilter() {
  return document.querySelector('.button--filter.active').id;
}

// Reapply the current filter
function applyCurrentFilter() {
  switch(getActiveFilter()) {
    case 'filter-all':
      filterAll();
      break;
    case 'filter-active':
      filterActive();
      break;
    case 'filter-completed':
      filterCompleted();
      break;
  }
}

Call applyCurrentFilter() whenever you add a new todo, toggle a todo's completion state, or delete a todo.

二、Useful Features to Add to Your To-Do List

Here are some practical, easy-to-implement features to level up your app:

  • Individual Todo Deletion
    Add a delete button to each todo item. When clicked, remove the item from the DOM and update localStorage:

    // Example: Add delete button when creating a todo element
    function createTodo(text, isCompleted) {
      const li = document.createElement('li');
      li.innerHTML = `
        <span class="todo-text">${text}</span>
        <button class="delete-btn">×</button>
      `;
      if (isCompleted) li.classList.add('completed');
    
      // Delete handler
      li.querySelector('.delete-btn').addEventListener('click', () => {
        li.remove();
        // Update localStorage
        const savedTodos = JSON.parse(localStorage.getItem('todos')) || [];
        const updatedTodos = savedTodos.filter(todo => todo.text !== text);
        localStorage.setItem('todos', JSON.stringify(updatedTodos));
        applyCurrentFilter();
        updateItemCounter(); // Update your "items left" count
      });
    
      // Toggle completion handler
      li.addEventListener('click', e => {
        if (!e.target.classList.contains('delete-btn')) {
          li.classList.toggle('completed');
          // Update localStorage for completion state
          const savedTodos = JSON.parse(localStorage.getItem('todos')) || [];
          savedTodos.forEach(todo => {
            if (todo.text === text) todo.completed = !todo.completed;
          });
          localStorage.setItem('todos', JSON.stringify(savedTodos));
          applyCurrentFilter();
          updateItemCounter();
        }
      });
    
      return li;
    }
    
  • Real-Time "Items Left" Counter
    Keep your counter updated whenever todos change:

    function updateItemCounter() {
      const activeTodos = document.querySelectorAll('li:not(.completed)').length;
      counterItem.textContent = activeTodos;
    }
    

    Call this function after adding, deleting, or toggling todos.

  • Todo Editing
    Let users double-click todo text to edit it:

    // Add this to your createTodo function
    const todoText = li.querySelector('.todo-text');
    todoText.addEventListener('dblclick', () => {
      const input = document.createElement('input');
      input.type = 'text';
      input.value = todoText.textContent;
      li.replaceChild(input, todoText);
    
      input.addEventListener('blur', () => {
        const newText = input.value.trim();
        if (newText) {
          // Update DOM and localStorage
          todoText.textContent = newText;
          li.replaceChild(todoText, input);
          const savedTodos = JSON.parse(localStorage.getItem('todos')) || [];
          savedTodos.forEach(todo => {
            if (todo.text === text) todo.text = newText;
          });
          localStorage.setItem('todos', JSON.stringify(savedTodos));
        } else {
          li.replaceChild(todoText, input);
        }
      });
      input.focus();
    });
    
  • Selective Clear Completed
    Instead of clearing all localStorage, only remove completed todos:

    clearBtn.addEventListener('click', () => {
      const savedTodos = JSON.parse(localStorage.getItem('todos')) || [];
      const activeTodos = savedTodos.filter(todo => !todo.completed);
      localStorage.setItem('todos', JSON.stringify(activeTodos));
      // Re-render the todo list with active todos
      todosUl.innerHTML = '';
      activeTodos.forEach(todo => todosUl.appendChild(createTodo(todo.text, todo.completed)));
      updateItemCounter();
      applyCurrentFilter();
    });
    
  • Dark/Light Mode Persistence
    Save the user's theme preference to localStorage:

    light.addEventListener('click', () => {
      body.classList.toggle('dark-mode');
      icon.classList.toggle('dark-icon');
      // Save preference
      localStorage.setItem('theme', body.classList.contains('dark-mode') ? 'dark' : 'light');
    });
    
    // Load saved theme on page load
    document.addEventListener('DOMContentLoaded', () => {
      const savedTheme = localStorage.getItem('theme');
      if (savedTheme === 'dark') {
        body.classList.add('dark-mode');
        icon.classList.add('dark-icon');
      }
    });
    

内容的提问来源于stack exchange,提问作者Nikola Maricic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 08:57:42