Project 1: Persistent Todo App
In this project, we will build a complete Todo application from scratch. We will cover DOM operations, form submission handling, event delegation for item removal, and state persistence using localStorage.
1. Project Features
- Add new tasks.
- Toggle task completion states (strike-through).
- Delete tasks.
- Auto-save tasks to
localStorage(persists on page reloads).
2. The HTML Structure
Create a basic layout with an input form and an empty list:
<!-- index.html -->
<div class="todo-card">
<h2>Task Manager</h2>
<form id="todo-form">
<input type="text" id="todo-input" placeholder="Add a new task..." required autocomplete="off">
<button type="submit">Add</button>
</form>
<ul id="todo-list">
<!-- Tasks will be injected dynamically -->
</ul>
</div>3. The JavaScript Implementation
Here is the step-by-step logic written in Vanilla JS:
// app.js
// 1. Core DOM Element Selection
const form = document.querySelector('#todo-form');
const input = document.querySelector('#todo-input');
const list = document.querySelector('#todo-list');
// 2. Load Existing Tasks or Initialize Empty Array
let tasks = JSON.parse(localStorage.getItem('todos')) || [];
// 3. Render Tasks to DOM
function renderTodos() {
list.innerHTML = ""; // Clear existing list to prevent duplication
tasks.forEach((task, index) => {
const li = document.createElement('li');
li.className = `todo-item ${task.completed ? 'completed' : ''}`;
// Inject HTML layout containing checkboxes and delete buttons
li.innerHTML = `
<label>
<input type="checkbox" data-index="${index}" ${task.completed ? 'checked' : ''}>
<span>${task.text}</span>
</label>
<button class="delete-btn" data-index="${index}">Delete</button>
`;
list.appendChild(li);
});
}
// 4. Save Task Array to LocalStorage
function saveTodos() {
localStorage.setItem('todos', JSON.stringify(tasks));
}
// 5. Handling Form Submission (Adding Task)
form.addEventListener('submit', (e) => {
e.preventDefault(); // Stop standard page reload
const text = input.value.trim();
if (text) {
tasks.push({ text: text, completed: false });
input.value = ""; // Clear input text
saveTodos();
renderTodos();
}
});
// 6. Interactive Actions via Event Delegation
list.addEventListener('click', (e) => {
// A. Handle Completion Checkbox clicks
if (e.target.type === 'checkbox') {
const index = e.target.getAttribute('data-index');
tasks[index].completed = e.target.checked;
saveTodos();
renderTodos();
}
// B. Handle Delete Button clicks
if (e.target.classList.contains('delete-btn')) {
const index = e.target.getAttribute('data-index');
tasks.splice(index, 1); // Remove item from array
saveTodos();
renderTodos();
}
});
// 7. Initial Render on Load
renderTodos();4. Basic CSS Styling
To make it look clean:
#todo-list {
list-style: none;
padding: 0;
}
.todo-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #ddd;
}
.todo-item.completed span {
text-decoration: line-through;
color: #888;
}
.delete-btn {
background-color: #ef4444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
padding: 4px 8px;
}In the next project, we will build an interactive Weather Dashboard using APIs and fetch().
Published on Last updated: