One listener can serve changing rows
When a row is rebuilt, its old DOM element is gone. Creating a new listener callback and registering it on a long-lived container on every render accumulates handlers. Re-registering the exact same function with the same event type and capture setting is ignored, but a newly created arrow function is a different listener. A useful pattern is event delegation: register one listener on the list and identify the clicked row’s action.
Click events normally bubble from the target up through its ancestors. event.target identifies the originating node; event.currentTarget is the element whose listener is currently running. closest("button[data-id]") finds a matching button at or above a nested target, such as a span inside the button. A data-id HTML attribute is read as button.dataset.id, always text.
Guard the target before calling element methods: event.target instanceof Element asks whether it is an element. instanceof checks an object’s relationship to a constructor, not a string type name. Our capstone uses this on DOM events and Error values; it does not replace validation of unknown JSON.
Use the data id to look up a validated record in state. Do not read a title out of the row and treat it as the record. Titles may repeat, text may change, and displayed values are formatted. On removal, move focus to a stable control so keyboard users are not left on a disappeared button. Keep user text in textContent.
Predict and trace
Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.
function findAction(books, dataset) {
return books.find(book => book.id === dataset.id);
}
const books = [
{ id: "js", title: "Notes" },
{ id: "web", title: "Notes" }
];
console.log(findAction(books, { id: "web" }).id);
console.log(findAction(books, { id: "missing" }) === undefined);Check your prediction
web
trueTwo books have the same title, so the id determines which one the action belongs to.
Practise on a changing list
Press Run webpage to preview. Each run starts fresh. Edits are not saved automatically.
Press Run webpage. Click the nested Remove text on the first row, then remove the second row using Tab and Enter. Reset the list, then remove again. The same list listener should still work after rows are rebuilt. The heading and status are outside the list; clicking them must not remove anything.
button[data-id] is a selector for a button carrying a data-id attribute. closest searches the clicked element and its ancestors for that match. The HTML contains a span inside each button so you can see why the click target is not always the button itself. event.currentTarget remains the list that owns the listener.
The render function creates nodes and assigns textContent, as the beginner planner did. Keep the removal rule from your console exercise separate from those DOM operations. As a variation, add the restored-row count to the reset message and verify repeated resets do not multiply the number of handled actions.
Build it yourself
Event Delegation: practise
Write removeById(books, id), preserving the input. Print the remaining id and original length. Treat an unknown id as a no-op in value.
const books = [{ id: "js" }, { id: "web" }];Hint 1: the idea
Filter based on identity, not title or position.
Hint 2: a first step
Keep records whose id differs from the requested id.
Show one solution
function removeById(books, id) {
return books.filter(book => book.id !== id);
}
const books = [{ id: "js" }, { id: "web" }];
console.log(removeById(books, "js")[0].id);
console.log(books.length);web
2Remove from an empty array, then try a missing id. In the final lab, click Save and Remove using Tab and Enter, and inspect where focus lands.
Test a changed requirement
Remove from an empty array, then try a missing id. In the final lab, click Save and Remove using Tab and Enter, and inspect where focus lands.
Explain before moving on
Where should a delegated listener be registered? Why does closest handle a nested button label better than reading target.dataset directly?
Completion is saved in this browser only. It does not measure mastery.