How to Build a Simple To-Do App Using Pure JavaScript

How to Build a Simple To-Do App Using Pure JavaScript

The Big Idea: Build Your Own To-Do App with Pure JavaScript!

Ever felt that satisfying "aha!" moment when you finally cross an item off your to-do list? Imagine having that power right at your fingertips, in an app you built yourself! It’s not just about getting things done; it’s about understanding the magic behind the web. Today, we’re going to embark on an exciting coding adventure: creating a simple yet powerful To-Do List application using nothing but pure JavaScript.

No fancy frameworks, no complex libraries – just good old vanilla JS, HTML, and a sprinkle of CSS. This project is a fantastic way to grasp core web development concepts, build confidence, and have a super useful app at the end. Ready to dive in?

Why Pure JavaScript? (And Why You Should Care!)

You might be thinking, "Why not use React or Vue or Angular?" And those are great questions! While frameworks offer speed and powerful features, starting with pure JavaScript (often called vanilla JS) is like learning to drive a stick shift before an automatic. It gives you a profound understanding of how things work under the hood.

Here’s why pure JavaScript is your best friend for this project:

  • Fundamental Understanding: You'll learn how the browser’s Document Object Model (DOM) works directly, how events are handled, and how to manipulate content on a web page without abstraction.
  • Lightweight & Fast: Your app will be lean, mean, and fast because it’s not loading extra framework code.
  • Problem-Solving Skills: You’ll tackle challenges head-on, building your problem-solving muscle. It’s incredibly rewarding!
  • Zero Dependencies: Just a browser and a text editor – that’s all you need.

Think of it like learning the basic building blocks of LEGO before trying to construct a pre-designed castle. It might take a little longer to assemble, but you'll know every piece and how it connects!

Step 1: The Foundation – HTML Structure

Every great building needs a solid foundation, and our To-Do app is no different. We start with HTML, which provides the structure or "skeleton" of our web page. Open your favorite text editor (VS Code is popular!) and create an `index.html` file. Here's what we'll put inside:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Pure JS To-Do App</title>
    <!-- We'll link our CSS here later -->
    <link rel="stylesheet" href="style.css"> 
</head>
<body>
    <div class="container">
        <h1>My Daily To-Dos</h1>
        <div class="input-section">
            <input type="text" id="taskInput" placeholder="Add a new task...">
            <button id="addTaskBtn">Add Task</button>
        </div>
        <ul id="taskList">
            <!-- Tasks will be added here by JavaScript -->
        </ul>
    </div>

    <script src="script.js"></script> <!-- Link our JavaScript file -->
</body>
</html>

Let's break down these crucial HTML pieces:

  • `<div class="container">`: This is just a wrapper, like a box, to hold all our app's content. It helps us style everything neatly later.
  • `<h1>My Daily To-Dos</h1>`: A simple heading to tell users what this app is all about.
  • `<input type="text" id="taskInput">`: This is where you'll type your new task. The `id="taskInput"` is super important because it's how JavaScript will find this specific input field.
  • `<button id="addTaskBtn">`: The button you'll click to add your task. Again, the `id="addTaskBtn"` is our JavaScript hook.
  • `<ul id="taskList">`: This unordered list will be the home for all your tasks. JavaScript will dynamically create `<li>` (list item) elements and put them inside here.
  • `<script src="script.js"></script>`: This is arguably the most important line! It links our HTML to our JavaScript file, which we’ll create next. We place it right before the closing `</body>` tag so that all our HTML content is loaded before JavaScript tries to manipulate it.

See? Simple and clear. Now, let’s make it look a little less plain!

Step 2: A Touch of Style – CSS (Keep it Simple!)

While our focus is JavaScript, a little bit of CSS makes our app much more user-friendly. Create a file named `style.css` in the same folder as your HTML. Here's a basic styling to get us started. Feel free to get creative with this later!

body {
    font-family: 'Arial', sans-serif;
    background-color: #f4f4f4;
    display: flex;
    justify-content: center;
    align-items: flex-start; /* Align to top instead of center */
    min-height: 100vh;
    margin: 0;
    padding-top: 50px; /* Add some space from the top */
}

.container {
    background-color: #ffffff;
    padding: 30px;
    border-radius: 10px;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
    width: 100%;
    max-width: 500px;
}

h1 {
    text-align: center;
    color: #333;
    margin-bottom: 25px;
}

.input-section {
    display: flex;
    gap: 10px;
    margin-bottom: 25px;
}

#taskInput {
    flex-grow: 1;
    padding: 12px;
    border: 1px solid #ddd;
    border-radius: 5px;
    font-size: 16px;
}

#addTaskBtn {
    padding: 12px 20px;
    background-color: #28a745;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 16px;
    transition: background-color 0.3s ease;
}

#addTaskBtn:hover {
    background-color: #218838;
}

#taskList {
    list-style: none;
    padding: 0;
}

#taskList li {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: #f8f8f8;
    padding: 12px 15px;
    margin-bottom: 10px;
    border-radius: 5px;
    border: 1px solid #eee;
    transition: background-color 0.3s ease;
}

#taskList li.completed {
    text-decoration: line-through;
    color: #888;
    background-color: #e0e0e0;
}

.task-text {
    flex-grow: 1;
    cursor: pointer; /* Indicates it's clickable */
}

.delete-btn {
    background-color: #dc3545;
    color: white;
    border: none;
    padding: 6px 10px;
    border-radius: 4px;
    cursor: pointer;
    margin-left: 10px;
    transition: background-color 0.3s ease;
}

.delete-btn:hover {
    background-color: #c82333;
}

This CSS makes our app look much cleaner, centers it on the page, and gives our buttons and input fields a friendly appearance. You can play around with colors and fonts later, but for now, this sets the stage perfectly for our JavaScript.

Step 3: Bringing it to Life – The JavaScript Magic!

Now for the main event! Create a file named `script.js` in the same folder. This is where our app will come alive. We'll break it down into a few logical steps.

Getting Our Hands on Elements (The DOM)

First, JavaScript needs to know about the HTML elements we want to interact with. We do this by selecting them using their `id`s. Think of it like giving a command to a specific robot in your house – you need to call it by its name!

// Select the HTML elements we'll work with
const taskInput = document.getElementById('taskInput');
const addTaskBtn = document.getElementById('addTaskBtn');
const taskList = document.getElementById('taskList');

Here, `document.getElementById()` is a built-in JavaScript method that searches the entire HTML document for an element with the specified ID and returns a reference to it. We store these references in constants so we can easily use them later.

Adding New Tasks: The Core Logic

Now, let's make our "Add Task" button actually add tasks! We need to listen for a click event on the button (or an 'Enter' key press in the input field).

// Function to add a new task
function addTask() {
    const taskText = taskInput.value.trim(); // .trim() removes leading/trailing whitespace

    if (taskText === "") {
        alert("Please enter a task!"); // Basic validation
        return;
    }

    // Create a new list item (li) for the task
    const listItem = document.createElement('li');

    // Create a span for the task text
    const taskSpan = document.createElement('span');
    taskSpan.textContent = taskText;
    taskSpan.classList.add('task-text'); // Add class for styling and click detection

    // Create a delete button
    const deleteBtn = document.createElement('button');
    deleteBtn.textContent = 'Delete';
    deleteBtn.classList.add('delete-btn'); // Add class for styling and click detection

    // Append the span and button to the list item
    listItem.appendChild(taskSpan);
    listItem.appendChild(deleteBtn);

    // Append the new list item to our task list (ul)
    taskList.appendChild(listItem);

    // Clear the input field after adding the task
    taskInput.value = '';
    
    // Save tasks to local storage (we'll implement this function later)
    saveTasks();
}

// Add an event listener to the "Add Task" button
addTaskBtn.addEventListener('click', addTask);

// Allow adding tasks by pressing Enter in the input field
taskInput.addEventListener('keypress', function(event) {
    if (event.key === 'Enter') {
        addTask();
    }
});

What's happening here?

  • `taskInput.value.trim()`: We grab the text from the input field and remove any accidental spaces.
  • `document.createElement('li')`: This is powerful! We're telling JavaScript to conjure up a new HTML `<li>` element out of thin air.
  • `listItem.appendChild(taskSpan)`: We then place our task text `span` (and the delete button) inside this new `<li>`. Think of `appendChild` as putting one LEGO brick onto another.
  • `taskList.appendChild(listItem)`: Finally, we attach our fully constructed task `<li>` to the `<ul>` (our `taskList`). Poof! It appears on the page.
  • `addEventListener('click', addTask)`: This tells the button, "Hey, when someone clicks you, run the `addTask` function!"
  • `saveTasks()`: We'll define this function soon to make our tasks persist even after closing the browser.

Marking Tasks Complete & Deleting: Event Delegation to the Rescue!

How do we handle clicking on a task to mark it complete, or clicking a delete button? If we added an event listener to *every* new task `<li>` or `<button>`, it would be inefficient, especially with many tasks. Instead, we use a clever technique called event delegation.

We listen for clicks on the parent `<ul id="taskList">` and then figure out *which specific child* was clicked. It's like having one security guard at the entrance of a building instead of one at every door inside.

// Add event listener to the task list for managing task completion and deletion
taskList.addEventListener('click', function(event) {
    const clickedElement = event.target; // event.target is the specific element that was clicked

    // Check if the clicked element is the task text (span)
    if (clickedElement.classList.contains('task-text')) {
        clickedElement.parentNode.classList.toggle('completed'); // Toggle 'completed' class on the parent li
        saveTasks();
    } 
    // Check if the clicked element is a delete button
    else if (clickedElement.classList.contains('delete-btn')) {
        clickedElement.parentNode.remove(); // Remove the parent li element
        saveTasks();
    }
});

In this code:

  • `event.target`: This magic property tells us exactly *which* element within our `taskList` was clicked.
  • `clickedElement.classList.contains('task-text')`: We check if the clicked element has the class `task-text`. If it does, it's our task description!
  • `clickedElement.parentNode.classList.toggle('completed')`: We then find its parent (`<li>`) and toggle the `completed` CSS class. This makes the task appear strikethrough.
  • `clickedElement.classList.contains('delete-btn')`: If the clicked element is our delete button...
  • `clickedElement.parentNode.remove()`: ...we tell its parent (`<li>`) to disappear!

Making it Sticky: Local Storage (Bonus Round!)

Our app works great, but what happens if you close your browser? All your tasks vanish! To fix this, we'll use `localStorage`, a simple way for web pages to store data directly in the user's browser.

// Function to save tasks to local storage
function saveTasks() {
    const tasks = [];
    taskList.querySelectorAll('li').forEach(listItem => {
        tasks.push({
            text: listItem.querySelector('.task-text').textContent,
            completed: listItem.classList.contains('completed')
        });
    });
    localStorage.setItem('tasks', JSON.stringify(tasks)); // Convert array to JSON string
}

// Function to load tasks from local storage
function loadTasks() {
    const savedTasks = JSON.parse(localStorage.getItem('tasks')); // Convert JSON string back to array

    if (savedTasks) {
        savedTasks.forEach(task => {
            const listItem = document.createElement('li');

            const taskSpan = document.createElement('span');
            taskSpan.textContent = task.text;
            taskSpan.classList.add('task-text');
            if (task.completed) {
                listItem.classList.add('completed');
            }

            const deleteBtn = document.createElement('button');
            deleteBtn.textContent = 'Delete';
            deleteBtn.classList.add('delete-btn');

            listItem.appendChild(taskSpan);
            listItem.appendChild(deleteBtn);
            taskList.appendChild(listItem);
        });
    }
}

// Load tasks when the page first loads
document.addEventListener('DOMContentLoaded', loadTasks);

Let's unpack `localStorage`:

  • `saveTasks()`:
    • We loop through all `<li>` elements in our `taskList`.
    • For each task, we grab its text and whether it's marked `completed`.
    • We store this info as an array of objects.
    • `JSON.stringify(tasks)`: `localStorage` can only store strings, so we convert our JavaScript array of objects into a JSON string.
    • `localStorage.setItem('tasks', ...)`: This saves our stringified tasks under the key 'tasks'.
  • `loadTasks()`:
    • `localStorage.getItem('tasks')`: We retrieve the string from `localStorage`.
    • `JSON.parse(...)`: We convert that JSON string back into a usable JavaScript array of objects.
    • If tasks exist, we loop through them and re-create each `<li>` element just like we did in `addTask()`, ensuring they have the correct text and completion status.
  • `document.addEventListener('DOMContentLoaded', loadTasks)`: This ensures `loadTasks` runs as soon as the HTML is fully loaded, populating our list with any saved tasks.

Putting It All Together & What's Next?

Congratulations! You've just built a fully functional, simple To-Do application using pure JavaScript! Open your `index.html` file in your browser, and behold your creation. You can add tasks, mark them complete, delete them, and even close your browser – your tasks will still be there!

This project is a fantastic stepping stone. But the journey doesn't end here. Here are some ideas to enhance your app and further your learning:

  • Edit Tasks: Add a feature to edit existing tasks by double-clicking them.
  • Filtering: Implement buttons or a dropdown to show "All," "Active," or "Completed" tasks.
  • Drag and Drop: Use JavaScript to allow users to reorder their tasks.
  • Clear All Completed: Add a button to wipe out all tasks that are marked as done.
  • Better UI/UX: Get more creative with your CSS! Add animations, different fonts, or a dark mode.

Each of these additions will challenge you to apply the same core JavaScript concepts in new and exciting ways, deepening your understanding of web development.

Your Journey Starts Here!

Building this To-Do app is more than just creating a tool; it's about gaining the confidence to build *anything* on the web. You've seen how HTML provides structure, CSS adds style, and pure JavaScript brings interactivity and life to your projects. This fundamental knowledge is invaluable, no matter what frameworks or libraries you might explore in the future.

So, take a moment to appreciate what you’ve accomplished. Then, open up your code editor and start experimenting. Break things, fix them, and most importantly, keep building! Happy coding!

No comments: