Project: To-Do App with localStorage
Build a complete to-do app using everything you've learned — DOM manipulation, events, arrays, closures, and localStorage. Tasks persist between page reloads.
HTML Structure
HTML
<div class="todo-app"> <h1>My Tasks</h1> <form id="todo-form"> <input type="text" id="todo-input" placeholder="Add a task..." required> <button type="submit">Add</button> </form> <div class="filters"> <button class="filter-btn active" data-filter="all">All</button> <button class="filter-btn" data-filter="active">Active</button> <button class="filter-btn" data-filter="done">Done</button> </div> <ul id="todo-list"></ul> <p id="task-count"></p> </div>
JavaScript — Full App
JavaScript
// State let tasks = JSON.parse(localStorage.getItem("tasks")) || []; let filter = "all"; // Save to localStorage function save() { localStorage.setItem("tasks", JSON.stringify(tasks)); } // Add task function addTask(text) { tasks.push({ id: Date.now(), text, done: false }); save(); render(); } // Toggle done function toggleTask(id) { const task = tasks.find(t => t.id === id); if (task) task.done = !task.done; save(); render(); } // Delete task function deleteTask(id) { tasks = tasks.filter(t => t.id !== id); save(); render(); }
Render Function
JavaScript
function render() { const list = document.querySelector("#todo-list"); const count = document.querySelector("#task-count"); const filtered = tasks.filter(t => { if (filter === "active") return !t.done; if (filter === "done") return t.done; return true; }); list.innerHTML = filtered .map(task => ` <li class="${task.done ? 'done' : ''}" data-id="${task.id}"> <input type="checkbox" ${task.done ? 'checked' : ''}> <span>${task.text}</span> <button class="del-btn">×</button> </li> `) .join(""); const remaining = tasks.filter(t => !t.done).length; count.textContent = `${remaining} task${remaining !== 1 ? 's' : ''} remaining`; }
Event Listeners — Event Delegation
JavaScript
// Form submit document.querySelector("#todo-form").addEventListener("submit", e => { e.preventDefault(); const input = document.querySelector("#todo-input"); if (input.value.trim()) { addTask(input.value.trim()); input.value = ""; } }); // List clicks — delegation (one listener handles all items) document.querySelector("#todo-list").addEventListener("click", e => { const li = e.target.closest("li"); if (!li) return; const id = Number(li.dataset.id); if (e.target.type === "checkbox") toggleTask(id); if (e.target.matches(".del-btn")) deleteTask(id); }); // Filter buttons document.querySelector(".filters").addEventListener("click", e => { if (!e.target.matches(".filter-btn")) return; document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active")); e.target.classList.add("active"); filter = e.target.dataset.filter; render(); }); // Initial render render();