Build a to-do list that handles every task with a single click listener.
What to build
A list where tasks can be added, finished and deleted.
Requirements
- Submitting the form adds an
lito#todosholding aspanwith the classtextand abuttonwith the classdeleteand the wordDelete. Trim the text, ignore a blank task and empty the box. - Clicking a task's text switches the class
doneon itsli. - Clicking its delete button removes its
li. #leftalways shows how many tasks are not done:2 tasks left, or1 task leftfor exactly one.- Use one click listener on
#todos, not one for each task.
Constraints
Work out the count from the page, not from a separate variable, so that the two can never disagree. Use textContent for the task's words.
Example
<li><span class="text">Buy rice</span><button class="delete">Delete</button></li>
Why it works
Events bubble, so the list hears every click inside it, even on tasks that did not exist when the listener was added. closest finds out which part of which task was clicked.
Hints
Hint 1
Put one click listener on #todos, and use event.target.closest(...) to see what was clicked.
Hint 2
Check for .delete first, then for the task text.
Hint 3
Count with querySelectorAll("#todos li:not(.done)").length, so that the count is always right.
Solution
Show one correct solution (try it yourself first)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>To-do</title>
<style>
li.done .text { text-decoration: line-through; }
</style>
</head>
<body>
<form id="add">
<label for="task">New task</label>
<input id="task" autocomplete="off">
<button>Add</button>
</form>
<ul id="todos"></ul>
<p id="left">0 tasks left</p>
<script>
const form = document.querySelector("#add");
const input = document.querySelector("#task");
const list = document.querySelector("#todos");
const left = document.querySelector("#left");
function update() {
const n = list.querySelectorAll("li:not(.done)").length;
left.textContent = n + (n === 1 ? " task left" : " tasks left");
}
form.addEventListener("submit", (event) => {
event.preventDefault();
const words = input.value.trim();
if (words === "") return;
const li = document.createElement("li");
const text = document.createElement("span");
text.className = "text";
text.textContent = words;
const remove = document.createElement("button");
remove.className = "delete";
remove.textContent = "Delete";
li.append(text, remove);
list.append(li);
input.value = "";
update();
});
list.addEventListener("click", (event) => {
const li = event.target.closest("li");
if (!li) return;
if (event.target.closest(".delete")) {
li.remove();
} else if (event.target.closest(".text")) {
li.classList.toggle("done");
}
update();
});
</script>
</body>
</html>