A todo list app is the perfect project to practice CRUD operations, DOM manipulation, and localStorage persistence all in one. This version includes filtering, progress tracking, and task persistence across page reloads.

Features

  • ➕ Add tasks with Enter key or button
  • ✅ Mark as complete (strikethrough)
  • 🗑️ Delete individual tasks
  • 🔍 Filter: All / Active / Completed
  • 💾 Persists with localStorage
  • 📊 Progress bar + task counter

Data Structure

JAVASCRIPT CODE
// Each task is a plain object
const task = {
  id: Date.now(),        // unique timestamp ID
  text: 'Learn CSS Grid',
  completed: false,
  createdAt: new Date().toISOString(),
};

// All tasks stored as JSON in localStorage
let tasks = JSON.parse(localStorage.getItem('cc-tasks')) || [];

function save() {
  localStorage.setItem('cc-tasks', JSON.stringify(tasks));
}

Core CRUD Functions

JAVASCRIPT CODE
// CREATE
function addTask(text) {
  if (!text.trim()) return;
  tasks.unshift({ id: Date.now(), text: text.trim(), completed: false });
  save();
  render();
}

// UPDATE
function toggleTask(id) {
  const task = tasks.find(t => t.id === id);
  if (task) { task.completed = !task.completed; save(); render(); }
}

// DELETE
function deleteTask(id) {
  tasks = tasks.filter(t => t.id !== id);
  save();
  render();
}

// READ + FILTER
let filter = 'all';
function getFiltered() {
  return tasks.filter(t =>
    filter === 'active'    ? !t.completed :
    filter === 'completed' ?  t.completed :
    true
  );
}

Render Function

JAVASCRIPT CODE
function render() {
  const list    = document.getElementById('taskList');
  const filtered = getFiltered();
  const done    = tasks.filter(t => t.completed).length;
  const pct     = tasks.length ? Math.round((done / tasks.length) * 100) : 0;

  // Update stats
  document.getElementById('taskCount').textContent =
    `${tasks.filter(t => !t.completed).length} task(s) remaining`;
  document.getElementById('progressFill').style.width = pct + '%';
  document.getElementById('progressPct').textContent  = pct + '%';

  // Render items
  list.innerHTML = filtered.length === 0
    ? `<li class="empty-state">No ${filter} tasks! 🎉</li>`
    : filtered.map(t =&gt; `
      <li class="task-item ${t.completed ? 'done' : ''}" id="task-${t.id}">
        <button class="check-btn" onclick="toggleTask(${t.id})">
          ${t.completed ? '✅' : '⬜'}
        </button>
        ${escapeHtml(t.text)}
        <button class="del-btn" onclick="deleteTask(${t.id})" aria-label="Delete">🗑️</button>
      </li>
    `).join('');
}

function escapeHtml(str) {
  return str.replace(/&amp;/g,'&amp;').replace(/&lt;/g,'&lt;').replace(/&gt;/g,'&gt;');
}

Input Handling

JAVASCRIPT CODE
const input = document.getElementById('taskInput');
document.getElementById('addBtn').addEventListener('click', () =&gt; {
  addTask(input.value);
  input.value = '';
  input.focus();
});
input.addEventListener('keydown', e =&gt; {
  if (e.key === 'Enter') { addTask(input.value); input.value = ''; }
});

// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn =&gt; {
  btn.addEventListener('click', () =&gt; {
    filter = btn.dataset.filter;
    document.querySelectorAll('.filter-btn').forEach(b =&gt; b.classList.remove('active'));
    btn.classList.add('active');
    render();
  });
});

render(); // Initial render

Pro Tip: You can extend this by adding due dates, priorities, or drag-and-drop reordering!