🧾

    Task Manager App

    Intermediate-Advanced Project — Master DOM, Events, localStorage & OOP

    🧠 Deep Project Overview

    A Task Manager looks simple, but it secretly trains you on 95% of what front-end devs do every day:

    • • Reading user input
    • • Updating the DOM based on app state
    • • Saving data so it survives page refreshes
    • • Organizing logic in classes instead of spaghetti code
    • • Handling edge cases (empty tasks, filters, invalid input)

    Apps like Todoist, Microsoft To Do, ClickUp, Trello all start from this same idea: a list of items with state (done/not done, priority, tags, etc.). If you can build this project cleanly, you're already thinking like a real JS dev.

    🧱 Step 1 – Understand the Structure

    Your HTML is split into three main zones:

    1. Input Area (.input-group)

    • • Text box for task title
    • • <select> for priority (low, medium, high)
    • • "Add Task" button that calls addTask()

    2. Filter Bar (.filters)

    • • 4 filter buttons: All, Active, Completed, High Priority
    • • They call filterTasks('...')

    3. Task List (#taskList)

    • • An empty <ul> where JavaScript injects <li> items

    💡 Tip: When you build other projects (Notes app, Budget tracker, Habit tracker), reuse this same 3-zone layout: inputs → controls/filters → results list.

    🧠 Step 2 – The Brain: TaskManager Class

    All the logic lives inside one class:

    class TaskManager {
      constructor() {
        this.tasks = this.loadTasks();
        this.currentFilter = 'all';
        this.render();
      }
    }

    Every instance has:

    • tasks: an array of all task objects
    • currentFilter: which view is active (all, active, completed, high)

    Each task looks like:

    {
      id: 1732012345678,
      text: "Finish JavaScript lesson",
      priority: "high",
      completed: false,
      createdAt: "2025-11-19T11:30:00.000Z"
    }

    That's already proper data modeling – which is what you'll do with databases later.

    💾 Step 3 – Saving and Loading with localStorage

    Two key methods:

    loadTasks() {
      const saved = localStorage.getItem('tasks');
      return saved ? JSON.parse(saved) : [];
    }
    
    saveTasks() {
      localStorage.setItem('tasks', JSON.stringify(this.tasks));
    }

    localStorage only stores strings, so you convert:

    • Save → JSON.stringify(this.tasks)
    • Load → JSON.parse(saved)

    💡 Debug tip:

    If the app acts weird, open DevTools → Application tab → Local Storage → clear the 'tasks' key, then refresh.

    💡 Reuse:

    This pattern (load → modify → save) is identical for: Notes apps, Shopping carts, Watch-later lists, "Recent code snippets" feature

    ➕ Step 4 – Adding Tasks

    addTask(text, priority) {
      if (!text.trim()) {
        alert('Please enter a task!');
        return;
      }
    
      const task = {
        id: Date.now(),
        text: text.trim(),
        priority,
        completed: false,
        createdAt: new Date().toISOString()
      };
    
      this.tasks.unshift(task);
      this.saveTasks();
      this.render();
    }

    What this teaches you:

    • Input validation – don't accept empty strings
    • Unique IDs – Date.now() is an easy, unique-enough ID for small apps
    • Unshift vs push – unshift() puts new tasks at the top of the list
    • Refresh UI – every change calls saveTasks() then render()

    💡 Upgrade ideas:

    • • Limit task length (e.g. max 120 characters) and show a little warning
    • • Store a dueDate property
    • • Store isStarred: true/false for favourites

    ✅ Step 5 – Completing and Deleting Tasks

    Toggle complete:

    toggleTask(id) {
      const task = this.tasks.find(t => t.id === id);
      if (task) {
        task.completed = !task.completed;
        this.saveTasks();
        this.render();
      }
    }

    Delete task:

    deleteTask(id) {
      this.tasks = this.tasks.filter(t => t.id !== id);
      this.saveTasks();
      this.render();
    }

    Patterns you're learning:

    • find() to get one item from an array
    • filter() to remove items by creating a new array
    • • Always treat the array like state → modify → save → re-render

    💡 Safer delete UX:

    • • Show confirm("Are you sure?") before deleting
    • • Or add a "Trash" filter where deleted tasks go instead of fully disappearing

    🔍 Step 6 – Filters and UI State

    getFilteredTasks() {
      switch (this.currentFilter) {
        case 'active':
          return this.tasks.filter(t => !t.completed);
        case 'completed':
          return this.tasks.filter(t => t.completed);
        case 'high':
          return this.tasks.filter(t => t.priority === 'high');
        default:
          return this.tasks;
      }
    }

    Then in setFilter:

    setFilter(filter) {
      this.currentFilter = filter;
    
      document.querySelectorAll('.filter-btn').forEach(btn => {
        btn.classList.remove('active');
      });
      event.target.classList.add('active');
    
      this.render();
    }

    What you're doing here:

    • • Keeping one source of truth: currentFilter
    • • Styling the active button with a CSS class
    • • Using .filter() to generate the current view

    💡 Improvement (cleaner events):

    Instead of using onclick in HTML, you could attach event listeners in JS and use e.target directly. This is called event delegation – super useful for dynamic content.

    🖼️ Step 7 – Rendering HTML from Data

    render() {
      const taskList = document.getElementById('taskList');
      const filtered = this.getFilteredTasks();
    
      if (filtered.length === 0) {
        taskList.innerHTML =
          '<li style="text-align: center; color: #999; padding: 20px;">No tasks to show</li>';
        return;
      }
    
      taskList.innerHTML = filtered.map(task => `
        <li class="task-item ${task.completed ? 'completed' : ''}">
          <div class="task-content">
            <span>${task.text}</span>
            <span class="priority priority-${task.priority}">
              ${task.priority.toUpperCase()}
            </span>
          </div>
          <div class="task-actions">
            <button class="complete-btn" onclick="taskManager.toggleTask(${task.id})">
              ${task.completed ? '↺' : '✓'}
            </button>
            <button class="delete-btn" onclick="taskManager.deleteTask(${task.id})">✕</button>
          </div>
        </li>
      `).join('');
    }

    Key patterns:

    • Template literals for clean HTML
    • • Using dynamic classes (completed, priority-high, etc.)
    • • One .innerHTML = ... per render → avoids many DOM mutations

    💡 Debug tip:

    If nothing shows:

    • 1. console.log(filtered) before you map
    • 2. Check for JS errors in DevTools console
    • 3. Make sure id="taskList" exists in the HTML

    ⌨️ Step 8 – Global Functions & Keyboard UX

    Global helpers:

    function addTask() {
      const input = document.getElementById('taskInput');
      const priority = document.getElementById('prioritySelect').value;
      taskManager.addTask(input.value, priority);
      input.value = '';
    }
    
    function filterTasks(filter) {
      taskManager.setFilter(filter);
    }

    Enter key support:

    document.getElementById('taskInput').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') addTask();
    });

    This is already nice UX – users can quickly hit Enter instead of clicking.

    💡 Upgrade:

    • • Disable the Add button when input is empty
    • • Focus the text input on page load

    💻 Try It Yourself

    Test the complete Task Manager app below. Study the code, modify it, and experiment with the features!

    Task Manager Starter Code

    Build a full task manager with localStorage persistence

    Try it Yourself »
    JavaScript
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Task Manager</title>
      <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
          font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          min-height: 100vh;
          padding: 20px;
        }
        .container { 
          max-width: 600px; 
          margin: 0 auto;
    ...

    🧪 What to Practice / Extend

    Here's how to use this project to become scary-good at JavaScript:

    1. Add due dates

    • • Add an <input type="date">
    • • Store dueDate in the task object
    • • Sort by dueDate in render() or create a "Due soon" filter

    2. Inline editing

    • • On click of task text, turn it into an <input>
    • • On blur / Enter, update task.text, save, re-render

    3. Search bar

    • • Add <input id="search">
    • • On input event, filter by both currentFilter + search query

    4. Animations

    • • Use CSS transitions on .task-item (opacity, transform)
    • • Add a quick fade-in class when a task is created

    5. Dark Mode

    • • Toggle data-theme="dark" on <body>
    • • Use CSS variables for colours and swap them per theme

    🧾 Final Checklist for "Job-Ready" Level

    Before you call this project done, check:

    • No errors in DevTools console
    • Works on mobile (try Chrome's responsive mode)
    • Handles 100+ tasks without feeling laggy
    • All filters work correctly
    • Data survives refresh and browser close
    • Code is split into logical methods, not one giant function
    • Important parts have comments explaining why, not just what

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service