Event delegation & closest()
One listener for a whole list
Lesson 7 of 20 · 12 min · Module 2: Respond to people
A list whose items come and go needs buttons that work even for items added later. Instead of attaching a listener to every button, you attach one to the list and let events bubble up to it.
Start this lesson →Free. No installation: you write and run the code in your browser, and it is checked automatically.
By the end, you’ll be able to…
- Explain event bubbling
- Handle many elements with one listener
- Find the relevant element with closest()
The idea
Most events bubble: a click on a button also reaches its list item, the list, and the whole document. One listener on the list can check where the click started with event.target.closest('.delete'), which returns the nearest matching ancestor, or null when the click was somewhere else.
list.addEventListener('click', event => {
const button = event.target.closest('button');
if (!button) return; // the click was not on a button
console.log('Clicked', button.textContent);
});
01Events travel upwards
A click starts at the innermost element and then bubbles through every ancestor. That is why a listener on the list hears clicks on buttons inside it, including buttons that did not exist when the listener was added.
02closest() finds the relevant element
event.target may be the button, or an icon or text inside it. closest('.delete') walks up from the target until it finds a match, so both cases work, and it returns null for clicks elsewhere.
// <button class="delete"><svg>…</svg></button>
event.target; // maybe the <svg>
event.target.closest('.delete'); // always the button
03Fewer listeners, simpler code
One delegated listener replaces hundreds of individual ones, uses less memory, and never needs to be re-attached when the list is re-rendered.
Your turn
#tasks. When the click came from a .delete button, remove that button’s li. 2) Clicking #add appends a new li with the trimmed text from #new-task and its own <button class="delete" type="button">Delete</button>, then clears the input. Ignore empty text. Deleting must also work for new tasks, and for tasks added by any other code.What the checks look for
- Delete removes the right task
- New tasks are added and the input cleared
- Empty text adds nothing
- New tasks can be deleted too
- Tasks added by other code can be deleted
- Clicking a task’s text deletes nothing
Take this with you
Listen on the container and use closest() to find what was clicked.
Go a little further
Add an Edit button to each task that replaces the text with an input, using the same delegated listener.