Build an Interactive Task Manager

Introduction

In this article, we will guide you through the process of building a simple and functional To-Do List application using HTML, CSS, and JavaScript. A To-Do List is a task management tool that helps you keep track of things you need to do. We will make it interactive, allowing users to add, edit, delete, and mark tasks as completed. By the end of this tutorial, you'll understand how to use HTML to structure a webpage, CSS to style it, and JavaScript to make it interactive.

HTML: Building the Structure

The HTML is used to create the basic layout of the To-Do List. Here’s how we structure the page.

<body>
  <div class="container">
    <h1>To-Do List</h1>
    <div class="input-container">
      <input type="text" id="task" placeholder="Enter task" />
      <input type="datetime-local" id="time" />
      <button onclick="addTask()">Add Task</button>
    </div>
    <ul id="taskList"></ul>
  </div>
</body>

CSS: Styling the Page

We use CSS to make the app look good. Here is the CSS that controls the appearance of the page.

body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      background: linear-gradient(to right, #18a9cd, #9248db);
      margin: 0;
      font-family: 'Arial', sans-serif;
    }
    .container {
      width: 350px;
      padding: 20px;
      background: #ffffff;
      box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
      border-radius: 12px;
      text-align: center;
    }
    h1 {
      font-size: 24px;
      color: #333;
      margin-bottom: 20px;
    }
    .input-container {
      display: flex;
      flex-direction: column;
      gap: 12px;
      margin-bottom: 20px;
    }
    input[type="text"], input[type="datetime-local"] {
      width: 100%;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 6px;
      font-size: 14px;
      outline: none;
    }
    button {
      padding: 10px;
      background-color: #5c6bc0;
      color: #fff;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      transition: background 0.3s;
    }
    button:hover {
      background-color: #3949ab;
    }
    ul {
      list-style: none;
      padding: 0;
    }
    li {
      background: #f1f3f4;
      margin-bottom: 8px;
      padding: 12px;
      border-radius: 6px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      transition: background 0.3s;
    }
    li.done .task-info::before {
      content: '✔️';
      margin-right: 8px;
      color: #4caf50;
      font-size: 16px;
    }

    .done{
      background-color: #baf3bd;
    } 

    .task-info {
      display: flex;
      align-items: center;
      gap: 8px;
      flex: 1;
    }
    .task-info span {
      font-size: 14px;
      color: #333;
    }
    .task-info small {
      font-size: 12px;
      color: #666;
    }
    .action-buttons button {
      margin-left: 5px;
      padding: 6px;
      border-radius: 4px;
      font-size: 12px;
    }
    .done-button {
      background-color: #4caf50;
    }
    .done-button:hover {
      background-color: #388e3c;
    }
    .edit-button {
      background-color: #f44336;
    }
    .edit-button:hover {
      background-color: #d32f2f;
    }
    .delete-button {
      background-color: #e53935;
      color: #fff;
    }
    .delete-button:hover {
      background-color: #c62828;
    }

JavaScript: Making the To-Do List Interactive

JavaScript brings the To-Do List to life by allowing us to add, edit, delete, and mark tasks as done. Here's the full JavaScript code that makes everything work.

<script>
  let editMode = false;
  let currentEditIndex = null;

  document.addEventListener("DOMContentLoaded", () => {
    const tasks = JSON.parse(localStorage.getItem("tasks")) || [];
    tasks.forEach((task, index) => renderTask(task, index));
  });

  function addTask() {
    const taskInput = document.getElementById("task");
    const timeInput = document.getElementById("time");
    const task = taskInput.value.trim();
    const time = timeInput.value;

    if (!task || !time) {
      alert("Please enter both task and time.");
      return;
    }

    const taskData = { task, time, done: false };

    if (editMode) {
      updateTask(taskData);
    } else {
      const tasks = JSON.parse(localStorage.getItem("tasks")) || [];
      tasks.push(taskData);
      localStorage.setItem("tasks", JSON.stringify(tasks));
      renderTask(taskData, tasks.length - 1);
    }

    taskInput.value = "";
    timeInput.value = "";
    exitEditMode();
  }

  function renderTask({ task, time, done }, index) {
    const taskList = document.getElementById("taskList");
    const li = document.createElement("li");
    li.dataset.index = index;
    if (done) li.classList.add("done");

    const taskInfo = document.createElement("div");
    taskInfo.className = "task-info";
    taskInfo.innerHTML = `<span>${task}</span><small>Due: ${new Date(time).toLocaleString()}</small>`;

    const actionButtons = document.createElement("div");
    actionButtons.className = "action-buttons";

    // "Done" button (always present)
    actionButtons.innerHTML = `<button class="done-button" onclick="markTaskDone(${index})">Done</button>`;

    // Conditionally add "Edit" button only if the task is not done
    if (!done) {
      actionButtons.innerHTML += `<button class="edit-button" onclick="editTask(${index})">Edit</button>`;
    }

    // "Delete" button (always present)
    actionButtons.innerHTML += `<button class="delete-button" onclick="deleteTask(${index})">Delete</button>`;

    li.appendChild(taskInfo);
    li.appendChild(actionButtons);
    taskList.appendChild(li);
  }

  function markTaskDone(index) {
    let tasks = JSON.parse(localStorage.getItem("tasks")) || [];
    tasks[index].done = !tasks[index].done;
    localStorage.setItem("tasks", JSON.stringify(tasks));
    reloadTasks();
  }

  function editTask(index) {
    const tasks = JSON.parse(localStorage.getItem("tasks")) || [];
    const taskData = tasks[index];

    document.getElementById("task").value = taskData.task;
    document.getElementById("time").value = taskData.time;

    currentEditIndex = index;
    editMode = true;
    document.querySelector("button[onclick='addTask()']").textContent = "Update Task";
  }

  function updateTask(updatedTask) {
    const tasks = JSON.parse(localStorage.getItem("tasks")) || [];
    updatedTask.done = tasks[currentEditIndex].done;
    tasks[currentEditIndex] = updatedTask;
    localStorage.setItem("tasks", JSON.stringify(tasks));
    reloadTasks();
  }

  function deleteTask(index) {
    let tasks = JSON.parse(localStorage.getItem("tasks")) || [];
    tasks.splice(index, 1);
    localStorage.setItem("tasks", JSON.stringify(tasks));
    reloadTasks();
  }

  function reloadTasks() {
    document.getElementById("taskList").innerHTML = "";
    const tasks = JSON.parse(localStorage.getItem("tasks")) || [];
    tasks.forEach((task, index) => renderTask(task, index));
  }

  function exitEditMode() {
    editMode = false;
    currentEditIndex = null;
    document.querySelector("button[onclick='addTask()']").textContent = "Add Task";
  }
</script>

Here's an overview of how the code works.

Initialization

When the page loads, the code listens for the DOMContentLoaded event. It retrieves tasks stored in localStorage and displays them on the page. If there are no tasks saved, it starts with an empty list.

Adding a Task

  • The addTask function is called when the user submits a task. It collects the task details (task name and time) from the input fields. If either field is empty, an alert prompts the user to enter the missing information.
  • If the task is being added in edit mode, it updates the task in localStorage with the new details. Otherwise, it creates a new task and stores it.

Rendering Tasks

The render task function creates a list item (<li>) for each task. Each task displays the task description and due time.

The list item includes action buttons for "Done," "Edit," and "Delete."

  • The "Done" button marks the task as completed, toggling its done status.
  • The "Edit" button appears only for incomplete tasks and allows the user to modify the task.
  • The "Delete" button lets the user remove the task.

Marking a Task as Done

Clicking the "Done" button triggers the markTaskDone function, which toggles the task's done status. This change is saved to localStorage, and the task list is updated.

Editing a Task

  • When the user clicks "Edit," the editTask function populates the input fields with the selected task's details.
  • The app switches to edit mode, and the submit button's label changes to "Update Task." After updating the task, it’s saved back to localStorage, and the list is re-rendered.

Deleting a Task

Clicking "Delete" triggers the delete task function, which removes the task from both the displayed list and local storage. The task list is then reloaded to reflect the change.

Reloading Tasks

After any changes (adding, editing, or deleting tasks), the reloadTasks function clears the task list from the page and retrieves the updated tasks from localStorage to display them.

Exiting Edit Mode

Once a task is updated, the exitEditMode function resets the interface back to add mode, allowing users to add new tasks instead of editing existing ones. The submit button text changes back to "Add Task."

Output

To-Do List

Conclusion

Creating a To-Do List with HTML, CSS, and JavaScript is an excellent way to practice essential web development skills. This project teaches you how to structure a webpage, style it, and add interactivity through JavaScript.It's a great starting point to build more complex applications in the future, reinforcing your understanding of both front-end development and JavaScript fundamentals.