Create a Todo List in JavaScript in Five Steps
Introduction
Todo lists are very common exercises that include the requirements of adding, deleting, reading, and modifying data, effectively simulating various scenarios and problems encountered when manipulating data. It can be said that various types of software are a form of customized todo lists.
Problem Solving
Step 1: Create the UI
The actions to be completed in this step are as follows:
- Provide a user input interface
- Use JavaScript to select and listen to interface events
<!-- Provide a user input interface --><div class="todo"> <form data-todo-form class="todo__form"> <input name="userInput" type="text" placeholder="Please enter a task" required /> <input type="submit" value="Submit" /> </form> <ul data-todo-list></ul></div>
Students often directly bind listeners to the <input>
, which doesn’t cause any issues, but the I prefers to use the existing <form>
to allow users to submit data. The reason is that HTML natively supports client-side data validation, making it easier to check the correctness of user input, and it can more semantically describe “this is a form waiting for user input” rather than “this is just any input field on the screen.”
<!-- Use validation to require user input --><input type="text" required />
Next, use JavaScript to select and listen to interface events (form submission and task list clicks).
// Use JavaScript to select and listen to interface eventsconst todoForm = document.querySelector('[data-todo-form]');const todoList = document.querySelector('[data-todo-list]');
// When the form is submitted……todoForm.addEventListener('submit', (e) => { // Prevent the browser's default behavior (page redirection) e.preventDefault();});
// When the task list is clicked……todoList.addEventListener('click', (e) => {});
Step 2: Display Todo Items
After the interface is structured, we need to display the data from JavaScript on the screen during the first page load. Therefore, we first define a piece of todo item data (dummy data is filled in here for easier understanding):
// id = unique identifier for the task// name = name of the task// isCompleted = completion status of the task
let tasks = [ { id: '1', name: 'Learn HTML', isCompleted: false, }, { id: '2', name: 'Learn CSS', isCompleted: true, },];
Next, we consider how to display this data. Since this is an action that will be executed frequently, we create a function to encapsulate the functionality for easier reference later:
function renderTask(tasks) { // Get task data and use map to iterate through it, extracting data and returning it in HTML const tasksHTML = tasks.map( (task) => ` <li task-id=${task.id}> <input id=${task.id} data-task-toggle type="checkbox" ${task.isCompleted ? 'checked' : ''}> <label for=${task.id}>${task.name}</label> <button data-task-delete>Delete</button> </li> `, ); todoList.innerHTML = tasksHTML.join('');}
renderTask(tasks);
It is particularly noted that existing data is used for judgment. When “isCompleted
is true
”, the input will be checked; otherwise, it will not be checked.
<input ${task.isCompleted ? "checked" : ""}>
Step 3: Add a Task
The subsequent steps are much simpler. The process for adding a task is as follows:
- Detect when the form is submitted.
- Pass the new task name to the
addTask
function for processing. - Add a new entry to
tasks
. - Call
renderTask
to re-render.
If you don’t want the entire task list to re-render just because of one task change, you can make subtle manipulations to edit the DOM. However, for this problem, we will take the simplest and most intuitive approach: refresh the entire display after updating the data.
// 01. Detect when the form is submittedtodoForm.addEventListener('submit', (e) => { e.preventDefault(); // 02. Pass the submitted task name to the addTask function addTask(e.target.userInput.value); // Reset the form content e.target.reset();});
function addTask(taskName) { // 03. Add new data to tasks, tasks equals (old tasks + new task) tasks = [ ...tasks, { id: Date.now().toString(), name: taskName, isCompleted: false, }, ]; // 04. Re-render renderTask(tasks);}
Here, Date.now() is used to get the number of milliseconds since 1970/01/01 00:00:00 UTC
as the ID.
Date.now().toString();
Step 4: Delete a Task
To delete a task, you need to know “which task’s delete button was clicked.” Then, compare the task ID with the current tasks
data. If they match, remove that task and re-render all tasks.
The process for deleting a task is as follows:
- Detect if the task list was clicked.
- Confirm that the clicked element is the delete button.
- Retrieve the
task-id
attribute. - Pass it to the
removeTask
function. - In the
removeTask
function, compare the tasks for the sametask-id
. - Delete target task from
tasks
. - Call
renderTask
to re-render.
// Listen for clicks on the task listtodoList.addEventListener('click', (e) => { // When clicked, confirm that the clicked task has the "data-task-delete" attribute if (e.target.hasAttribute('data-task-delete')) { // If it does, retrieve the task-id attribute from its parent element and pass it to the removeTasks function const taskId = e.target.parentElement.getAttribute('task-id'); removeTask(taskId); }});
Here, I uses the array method reduce
to quickly remove unnecessary data (curr.id === targetId
).
function removeTask(targetId) { // Compare tasks with the same task-id, // the final tasks equal (filtered out deleted tasks) tasks = tasks.reduce((prev, curr) => { if (curr.id === targetId) { return prev; } return [...prev, curr]; }, []); renderTask(tasks);}
Step 5: Toggle Todo Items
The idea of toggling the status of an item is very similar to deleting an item. The process for toggling an item is:
- Detect if the todo list has been clicked
- Confirm that the clicked element is a checkbox
- Retrieve the
task-id
attribute - Pass it into the
toggleTask
function - In the
toggleTask
function, compare the items intasks
with the sametask-id
- Toggle the status of that item in tasks
// Listen for clicks on the todo listtodoList.addEventListener('click', (e) => { // When clicked, confirm that the clicked item has the "data-task-toggle" attribute if (e.target.hasAttribute('data-task-toggle')) { // If it does, retrieve the task-id attribute from its parent element and pass it to the toggleTask function const taskId = e.target.parentElement.getAttribute('task-id'); toggleTask(taskId); }});
function toggleTask(targetId) { // Iterate through each item in tasks, if the id matches, toggle the completion status of that item tasks.forEach((task) => { if (task.id === targetId) { task.isCompleted = !task.isCompleted; } });}
Conclusion
See the Pen Todo vanilla JS by Riceball (
@riecball) on CodePen.
The above is the thought process of the teaching assistant in creating the entire todo item project. Students can refer to it but should not memorize or directly copy it; practical operation is the best way to learn!