Simple To-Do List App from Scratch

Introduction

In this tutorial, we’ll create a fully functional To-Do List application using HTML and JavaScript. This project is perfect for learning fundamental web development concepts including array manipulation, DOM operations, event handling, and local data management.

What we’ll build:

  • Add new tasks to the list
  • Mark tasks as completed
  • Delete individual tasks
  • Clear all completed tasks
  • Display task counter

Prerequisites: Basic understanding of HTML and JavaScript

Core Concepts We’ll Learn:

• Array methods (push, splice, filter)

• DOM manipulation and creation

• Event handling (click, keypress)

• Conditional rendering

• Function organization


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple To-Do List</title>
</head>
<body>
    <h1>My To-Do List</h1>
    <input type="text" id="taskInput" placeholder="Enter a task..." onkeypress="if(event.key==='Enter') addTask()">
    <button onclick="addTask()">Add Task</button>
    <ul id="taskList"></ul>

    <script>
        let tasks = [];
        let taskId = 1;

        function addTask() {
            const input = document.getElementById('taskInput');
            const text = input.value.trim();
            if (!text) return;
            
            tasks.push({id: taskId++, text: text, done: false});
            input.value = '';
            renderTasks();
        }

        function toggleTask(id) {
            const task = tasks.find(t => t.id === id);
            if (task) task.done = !task.done;
            renderTasks();
        }

        function deleteTask(id) {
            tasks = tasks.filter(t => t.id !== id);
            renderTasks();
        }

        function renderTasks() {
            const list = document.getElementById('taskList');
            list.innerHTML = tasks.map(task => 
                `<li style="${task.done ? 'text-decoration: line-through; opacity: 0.6;' : ''}">
                    <span onclick="toggleTask(${task.id})" style="cursor: pointer;">
                        ${task.done ? '✅' : '⭕'} ${task.text}
                    </span>
                    <button onclick="deleteTask(${task.id})" style="margin-left: 10px;">Delete</button>
                </li>`
            ).join('');
        }
    </script>
</body>
</html>

Step 1: Setting Up the Basic HTML Structure

Let’s start by creating the foundation of our To-Do List app.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple To-Do List</title>
</head>
<body>
    <div class="container">
        <h1>My To-Do List</h1>
        
        <!-- Input section for adding new tasks -->
        <div class="input-section">
            <input type="text" id="taskInput" placeholder="Enter a new task...">
            <button onclick="addTask()">Add Task</button>
        </div>
        
        <!-- Task display area -->
        <div id="taskList"></div>
        
        <!-- Task management controls -->
        <div class="controls">
            <button onclick="clearCompleted()">Clear Completed</button>
            <span id="taskCounter">0 tasks remaining</span>
        </div>
    </div>
</body>
</html>

What we created:

Container div: Wraps all content for better organization

Input section: Text field and button for adding new tasks

Task display area: Empty div where tasks will be dynamically created

Controls section: Button to clear completed tasks and a counter display

Semantic structure: Clear separation of different app sections


Step 2: Initialize JavaScript Variables and Data Structure

Now let’s set up the JavaScript foundation for managing our tasks.

<script>
    // Array to store all tasks
    let tasks = [];
    
    // Task counter for unique IDs
    let taskIdCounter = 1;
    
    // Get references to frequently used DOM elements
    const taskInput = document.getElementById('taskInput');
    const taskList = document.getElementById('taskList');
    const taskCounter = document.getElementById('taskCounter');
</script>

Variables explained:

tasks array: Stores all task objects with their properties (id, text, completed status)

taskIdCounter: Ensures each task has a unique identifier for easy management

DOM references: Cached references to avoid repeated getElementById calls

Global scope: Variables accessible from all functions in our app


Step 3: Creating the Add Task Function

Let’s build the core functionality to add new tasks to our list.

// Function to add a new task
function addTask() {
    // Get the task text and remove extra whitespace
    const taskText = taskInput.value.trim();
    
    // Validate input - don't add empty tasks
    if (taskText === '') {
        alert('Please enter a task!');
        return;
    }
    
    // Create new task object
    const newTask = {
        id: taskIdCounter++,
        text: taskText,
        completed: false
    };
    
    // Add task to array
    tasks.push(newTask);
    
    // Clear input field
    taskInput.value = '';
    
    // Update the display
    renderTasks();
    updateTaskCounter();
}

Function breakdown:

Input validation: Checks for empty or whitespace-only input

trim(): Removes leading/trailing whitespace from user input

Task object: Contains id, text, and completion status

Array.push(): Adds the new task to the end of tasks array

UI updates: Clears input and refreshes the display

Separation of concerns: One function handles adding, other functions handle display


Step 4: Building the Task Rendering Function

This function creates the visual representation of all tasks.

// Function to render all tasks in the DOM
function renderTasks() {
    // Clear existing task display
    taskList.innerHTML = '';
    
    // Loop through each task and create HTML elements
    tasks.forEach(task => {
        // Create main task container
        const taskItem = document.createElement('div');
        taskItem.className = 'task-item';
        taskItem.setAttribute('data-id', task.id);
        
        // Apply completed styling if task is done
        if (task.completed) {
            taskItem.style.textDecoration = 'line-through';
            taskItem.style.opacity = '0.6';
        }
        
        // Create task content HTML
        taskItem.innerHTML = `
            <span onclick="toggleTask(${task.id})" style="cursor: pointer;">
                ${task.completed ? '✅' : '⭕'} ${task.text}
            </span>
            <button onclick="deleteTask(${task.id})" style="margin-left: 10px; background: red; color: white;">
                Delete
            </button>
        `;
        
        // Add the task item to the task list
        taskList.appendChild(taskItem);
    });
}

Rendering logic:

Clear existing content: Prevents duplicate displays

forEach loop: Iterates through every task in the array

createElement(): Dynamically creates HTML elements

data-id attribute: Links DOM element to task data

Conditional styling: Visual feedback for completed tasks

Event handlers: Click functions embedded in HTML

appendChild(): Adds new elements to the DOM


Step 5: Implementing Task Toggle Functionality

Let’s add the ability to mark tasks as completed or incomplete.

// Function to toggle task completion status
function toggleTask(taskId) {
    // Find the task in the array by ID
    const task = tasks.find(t => t.id === taskId);
    
    // Toggle the completed status
    if (task) {
        task.completed = !task.completed;
        
        // Update the display
        renderTasks();
        updateTaskCounter();
    }
}

Toggle functionality:

Array.find(): Locates the specific task object by its ID

Boolean toggle: Uses NOT operator (!) to flip true/false values

Conditional check: Ensures task exists before modifying

State update: Changes the task’s completed property

Visual refresh: Re-renders tasks to show new status


Step 6: Adding Task Deletion Feature

Now let’s implement the ability to remove individual tasks.

// Function to delete a specific task
function deleteTask(taskId) {
    // Find the index of the task to delete
    const taskIndex = tasks.findIndex(t => t.id === taskId);
    
    // Remove task from array if found
    if (taskIndex !== -1) {
        tasks.splice(taskIndex, 1);
        
        // Update the display
        renderTasks();
        updateTaskCounter();
    }
}

Deletion process:

Array.findIndex(): Finds the position of the task in the array

splice() method: Removes elements from array (1 element at the found index)

Index validation: Checks if task was found (-1 means not found)

Array modification: Permanently removes the task from data structure

UI synchronization: Updates display to reflect changes


Step 7: Creating Clear Completed Tasks Function

Let’s add functionality to remove all completed tasks at once.

// Function to clear all completed tasks
function clearCompleted() {
    // Filter out completed tasks, keep only incomplete ones
    tasks = tasks.filter(task => !task.completed);
    
    // Update the display
    renderTasks();
    updateTaskCounter();
}

Bulk deletion logic:

Array.filter(): Creates new array with only tasks that meet the condition

Negation operator (!): Keeps tasks where completed is false

Array reassignment: Replaces the tasks array with filtered results

Efficient operation: Removes multiple items in one operation

Clean interface: Users can quickly clean up finished tasks


Step 8: Building the Task Counter Function

Let’s add a counter to show how many tasks remain incomplete.

// Function to update task counter display
function updateTaskCounter() {
    // Count incomplete tasks
    const remainingTasks = tasks.filter(task => !task.completed).length;
    
    // Update counter text with proper grammar
    const counterText = remainingTasks === 1 ? 
        '1 task remaining' : 
        `${remainingTasks} tasks remaining`;
    
    taskCounter.textContent = counterText;
}

Counter functionality:

Array.filter(): Gets array of incomplete tasks

length property: Counts how many tasks remain

Ternary operator: Handles singular vs plural grammar correctly

Template literals: Embeds the count number in the text

textContent: Updates the DOM element’s text safely


Step 9: Adding Keyboard Support

Let’s make the app more user-friendly by supporting the Enter key.

// Allow Enter key to add tasks
taskInput.addEventListener('keypress', function(event) {
    if (event.key === 'Enter') {
        addTask();
    }
});

Keyboard enhancement:

addEventListener(): Attaches event listener to input field

keypress event: Detects when keys are pressed in the input

event.key: Identifies which specific key was pressed

Enter key detection: Calls addTask() when Enter is pressed

User experience: Allows rapid task entry without mouse clicks


Step 10: Initialize the Application

Finally, let’s set up the initial state of our app.

// Initialize the app when page loads
function initializeApp() {
    // Clear any existing content
    renderTasks();
    updateTaskCounter();
    
    // Focus on input field for immediate use
    taskInput.focus();
}

// Run initialization when page loads
window.addEventListener('load', initializeApp);

Initialization process:

Clear state: Ensures clean starting point

Initial render: Sets up empty task list display

Counter setup: Shows “0 tasks remaining”

Input focus: Cursor ready for immediate typing

load event: Runs initialization after page fully loads


Complete Code

Here’s the final, complete code for our To-Do List application:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple To-Do List</title>
</head>
<body>
    <div class="container">
        <h1>My To-Do List</h1>
        
        <!-- Input section for adding new tasks -->
        <div class="input-section">
            <input type="text" id="taskInput" placeholder="Enter a new task...">
            <button onclick="addTask()">Add Task</button>
        </div>
        
        <!-- Task display area -->
        <div id="taskList"></div>
        
        <!-- Task management controls -->
        <div class="controls">
            <button onclick="clearCompleted()">Clear Completed</button>
            <span id="taskCounter">0 tasks remaining</span>
        </div>
    </div>

    <script>
        // Array to store all tasks
        let tasks = [];
        
        // Task counter for unique IDs
        let taskIdCounter = 1;
        
        // Get references to frequently used DOM elements
        const taskInput = document.getElementById('taskInput');
        const taskList = document.getElementById('taskList');
        const taskCounter = document.getElementById('taskCounter');

        // Function to add a new task
        function addTask() {
            // Get the task text and remove extra whitespace
            const taskText = taskInput.value.trim();
            
            // Validate input - don't add empty tasks
            if (taskText === '') {
                alert('Please enter a task!');
                return;
            }
            
            // Create new task object
            const newTask = {
                id: taskIdCounter++,
                text: taskText,
                completed: false
            };
            
            // Add task to array
            tasks.push(newTask);
            
            // Clear input field
            taskInput.value = '';
            
            // Update the display
            renderTasks();
            updateTaskCounter();
        }

        // Function to render all tasks in the DOM
        function renderTasks() {
            // Clear existing task display
            taskList.innerHTML = '';
            
            // Loop through each task and create HTML elements
            tasks.forEach(task => {
                // Create main task container
                const taskItem = document.createElement('div');
                taskItem.className = 'task-item';
                taskItem.setAttribute('data-id', task.id);
                
                // Apply completed styling if task is done
                if (task.completed) {
                    taskItem.style.textDecoration = 'line-through';
                    taskItem.style.opacity = '0.6';
                }
                
                // Create task content HTML
                taskItem.innerHTML = `
                    <span onclick="toggleTask(${task.id})" style="cursor: pointer;">
                        ${task.completed ? '✅' : '⭕'} ${task.text}
                    </span>
                    <button onclick="deleteTask(${task.id})" style="margin-left: 10px; background: red; color: white;">
                        Delete
                    </button>
                `;
                
                // Add the task item to the task list
                taskList.appendChild(taskItem);
            });
        }

        // Function to toggle task completion status
        function toggleTask(taskId) {
            // Find the task in the array by ID
            const task = tasks.find(t => t.id === taskId);
            
            // Toggle the completed status
            if (task) {
                task.completed = !task.completed;
                
                // Update the display
                renderTasks();
                updateTaskCounter();
            }
        }

        // Function to delete a specific task
        function deleteTask(taskId) {
            // Find the index of the task to delete
            const taskIndex = tasks.findIndex(t => t.id === taskId);
            
            // Remove task from array if found
            if (taskIndex !== -1) {
                tasks.splice(taskIndex, 1);
                
                // Update the display
                renderTasks();
                updateTaskCounter();
            }
        }

        // Function to clear all completed tasks
        function clearCompleted() {
            // Filter out completed tasks, keep only incomplete ones
            tasks = tasks.filter(task => !task.completed);
            
            // Update the display
            renderTasks();
            updateTaskCounter();
        }

        // Function to update task counter display
        function updateTaskCounter() {
            // Count incomplete tasks
            const remainingTasks = tasks.filter(task => !task.completed).length;
            
            // Update counter text with proper grammar
            const counterText = remainingTasks === 1 ? 
                '1 task remaining' : 
                `${remainingTasks} tasks remaining`;
            
            taskCounter.textContent = counterText;
        }

        // Allow Enter key to add tasks
        taskInput.addEventListener('keypress', function(event) {
            if (event.key === 'Enter') {
                addTask();
            }
        });

        // Initialize the app when page loads
        function initializeApp() {
            // Clear any existing content
            renderTasks();
            updateTaskCounter();
            
            // Focus on input field for immediate use
            taskInput.focus();
        }

        // Run initialization when page loads
        window.addEventListener('load', initializeApp);
    </script>
</body>
</html>

Key Programming Concepts Learned

Array Methods:push(): Adding new elements to the end of an array • find(): Locating specific elements based on conditions • findIndex(): Getting the position of elements in arrays • filter(): Creating new arrays with elements that meet criteria • splice(): Removing elements from specific positions • forEach(): Iterating through all array elements

DOM Manipulation:getElementById(): Selecting HTML elements by their ID • createElement(): Creating new HTML elements dynamically • appendChild(): Adding elements to the DOM tree • innerHTML: Setting the HTML content of elements • textContent: Safely setting text content of elements

Event Handling:onclick attributes: Inline event handlers in HTML • addEventListener(): Programmatic event listener attachment • Event object: Accessing information about user interactions • Keyboard events: Responding to key presses

Data Management:Object creation: Storing related data in structured objects • Unique IDs: Managing individual items in collections • State synchronization: Keeping data and display in sync • Input validation: Ensuring data quality before processing

Testing Your To-Do List

  1. Save the code as an HTML file (e.g., todo-list.html)
  2. Open in browser by double-clicking the file
  3. Test core features: Add tasks, mark complete, delete tasks
  4. Try keyboard shortcuts: Use Enter to add tasks
  5. Test edge cases: Empty input, clearing completed tasks
  6. Verify counter: Check that remaining tasks count updates correctly

Leave a Reply

Your email address will not be published. Required fields are marked *