待办事项列表(To-Do List)功能添加方法及过滤按钮失效问题排查求助
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 updatelocalStorage:// 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 tolocalStorage: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

