NelsonLabs
JavaScript Basics/Project: Interactive App

Project: Interactive App

You'll build a fully interactive task management app — entirely in vanilla JavaScript, no frameworks. Add tasks, mark them complete, filter by status, and persist data with localStorage.

What you'll build

A tasks app where users can add tasks, click to mark them complete (strikethrough), filter by All/Active/Completed, and delete tasks. State persists across page reloads via localStorage. No libraries, no frameworks — just DOM manipulation and events.

index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Tasks</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <main>
    <h1>Tasks</h1>

    <form id="task-form">
      <input type="text" id="task-input" placeholder="Add a task..." required />
      <button type="submit">Add</button>
    </form>

    <div class="filters">
      <button class="filter active" data-filter="all">All</button>
      <button class="filter" data-filter="active">Active</button>
      <button class="filter" data-filter="completed">Completed</button>
    </div>

    <ul id="task-list"></ul>
    <p id="task-count"></p>
  </main>
  <script src="app.js"></script>
</body>
</html>
app.js
javascript
// ── State ─────────────────────────────────────────────────────────
let tasks       = JSON.parse(localStorage.getItem("tasks")) || [];
let activeFilter = "all";

// ── Save to localStorage ───────────────────────────────────────────
function saveTasks() {
  localStorage.setItem("tasks", JSON.stringify(tasks));
}

// ── Render ─────────────────────────────────────────────────────────
function render() {
  const list  = document.querySelector("#task-list");
  const count = document.querySelector("#task-count");

  // Filter tasks based on active filter
  const visible = tasks.filter(task => {
    if (activeFilter === "active")    return !task.completed;
    if (activeFilter === "completed") return task.completed;
    return true;
  });

  // Render list items
  list.innerHTML = visible.map(task => `
    <li class="${task.completed ? "done" : ""}" data-id="${task.id}">
      <button class="toggle" aria-label="Toggle complete">
        ${task.completed ? "✓" : "○"}
      </button>
      <span class="task-text">${task.text}</span>
      <button class="delete" aria-label="Delete task">✕</button>
    </li>
  `).join("");

  // Count
  const remaining = tasks.filter(t => !t.completed).length;
  count.textContent = `${remaining} task${remaining !== 1 ? "s" : ""} remaining`;
}

// ── Add task ────────────────────────────────────────────────────────
document.querySelector("#task-form").addEventListener("submit", (e) => {
  e.preventDefault();
  const input = document.querySelector("#task-input");
  const text  = input.value.trim();

  if (!text) return;

  tasks.push({ id: Date.now(), text, completed: false });
  saveTasks();
  render();
  input.value = "";
  input.focus();
});

// ── Toggle and delete via event delegation ──────────────────────────
document.querySelector("#task-list").addEventListener("click", (e) => {
  const li = e.target.closest("li");
  if (!li) return;

  const id = Number(li.dataset.id);

  if (e.target.matches(".toggle")) {
    tasks = tasks.map(t => t.id === id ? { ...t, completed: !t.completed } : t);
    saveTasks();
    render();
  }

  if (e.target.matches(".delete")) {
    tasks = tasks.filter(t => t.id !== id);
    saveTasks();
    render();
  }
});

// ── Filters ─────────────────────────────────────────────────────────
document.querySelectorAll(".filter").forEach(btn => {
  btn.addEventListener("click", () => {
    activeFilter = btn.dataset.filter;
    document.querySelectorAll(".filter").forEach(b => b.classList.remove("active"));
    btn.classList.add("active");
    render();
  });
});

// ── Initial render ──────────────────────────────────────────────────
render();

What you learned in this course

  • Variables with let and const, and all JavaScript data types
  • Operators — arithmetic, comparison, and logical
  • Conditionals — if/else, ternary, and switch
  • Loops — for, for...of, while, break, continue
  • Functions — declarations, expressions, arrow functions
  • Arrays — creation, methods (map, filter, reduce)
  • Objects — literals, methods, destructuring
  • The DOM — selecting, reading, and modifying elements
  • Events — addEventListener and event delegation
  • Async/await and fetching data from APIs
  • Modern ES6+ features used in every codebase
  • Error handling with try/catch