Progressive interface foundations
Connect behavior with data attributes
Use data attributes and one delegated event handler to connect repeated task controls to small native behaviors.
A project board repeats the same actions on many cards. You do not need one click listener per button. Put application-specific metadata in data-* attributes and let one listener on the stable board find the activated control.
Event delegation works because click events bubble. closest() handles clicks on an icon or span inside the button, while contains() confirms the matched button belongs to this board. The DOM continues to describe state, and newly inserted cards work without another initialization pass.
<section id="board">
<article data-task-id="task-17">
<h2>Write homepage</h2>
<button type="button" data-action="complete">Complete</button>
</article>
</section>
<script>
const board = document.querySelector("#board")
board.addEventListener("click", event => {
const button = event.target.closest("button[data-action]")
if (!button || !board.contains(button)) return
const card = button.closest("[data-task-id]")
if (button.dataset.action === "complete") card.toggleAttribute("data-complete")
})
</script>
Use data attributes for local identifiers, modes, and hooks that have no standard HTML attribute. Do not encode a large hidden state store in strings. The task title remains text, completion can be a present or absent attribute, and richer records can stay in normal JavaScript data.
A delegated handler should still respond to semantic controls. Do not make the entire card clickable if the card also contains links and buttons; nested interaction becomes ambiguous and accidental activation becomes easy.
Validate any action name before using it. A switch or explicit handler map is clearer than constructing a function name or selector from untrusted attribute text. The markup is an interface between your HTML and JavaScript, so keep that contract small.
Duplicate the article three times with unique task IDs, then add a fourth card after page load. Complete every card with the same listener and inspect its data-complete attribute. Use the keyboard too, because delegation should receive a native button activation regardless of whether it began with a pointer or key. If clicking the button’s inner icon does nothing, replace direct event.target.matches() logic with closest() as shown.
Lesson completed