JavaScript Curriculum
Bubbling & Delegation
mediumThe menu now loads cards dynamically from a server. Attaching a click listener to every card would break whenever new cards arrive. Event delegation lets one listener on the container handle all cards — past and future.
Bubbling & Delegation
When you click a button inside a card inside a section, the click event fires on the button — then bubbles up to the card, then the section, then main, then body, then document. This is event bubbling.
Event Propagation Visualizer
See exactly how events travel through the DOM — click any element and watch the path:
Click an element above to see the bubble path
// Default: events bubble UP
el.addEventListener('click', handler)
// fires: target → parent → ... → documentstopPropagation & preventDefault
Two methods that control event behaviour — they are independent and do different things:
Cancels the browser's default action for the event. Does NOT stop bubbling — the event still propagates.
form.addEventListener('submit', (e) => {
e.preventDefault() // stops page reload
// now handle with JS:
const data = new FormData(e.target)
sendToServer(data)
})
link.addEventListener('click', (e) => {
e.preventDefault() // stops navigation
openModal(link.href)
})// Without preventDefault — page reloads on submit:
form.addEventListener('submit', (e) => {
sendToServer(new FormData(e.target))
// page reloads before fetch completes!
})Event Delegation — One Listener for All
Instead of attaching listeners to every child, attach one to the parent and use e.target to figure out what was clicked:
// ✓ Delegation: one listener on parent
container.addEventListener('click', (e) => {
const card = e.target.closest('[data-card-id]')
if (!card) return
if (e.target.matches('.order-action'))
handleOrder(card.dataset.cardId)
if (e.target.matches('.remove-action'))
card.remove()
// Works for ALL cards — even future ones!
})// Click buttons inside any card
Your Challenge
Replace all individual .card click listeners with one delegated listener on .cards. Inside it: use e.target.closest('.card') to find the card, e.target.matches('.order-btn') to detect the order button, and e.target.matches('.remove-btn') to detect remove. Add a new card after wiring — confirm the listener covers it automatically.
Challenge
Replace individual card listeners with a single delegated listener on .cards. Use e.target.closest('.card') to find which card was clicked and e.target.matches('.order-btn') to check if the order button was the target.
Bubbling & Delegation
mediumThe menu now loads cards dynamically from a server. Attaching a click listener to every card would break whenever new cards arrive. Event delegation lets one listener on the container handle all cards — past and future.
Bubbling & Delegation
When you click a button inside a card inside a section, the click event fires on the button — then bubbles up to the card, then the section, then main, then body, then document. This is event bubbling.
Event Propagation Visualizer
See exactly how events travel through the DOM — click any element and watch the path:
Click an element above to see the bubble path
// Default: events bubble UP
el.addEventListener('click', handler)
// fires: target → parent → ... → documentstopPropagation & preventDefault
Two methods that control event behaviour — they are independent and do different things:
Cancels the browser's default action for the event. Does NOT stop bubbling — the event still propagates.
form.addEventListener('submit', (e) => {
e.preventDefault() // stops page reload
// now handle with JS:
const data = new FormData(e.target)
sendToServer(data)
})
link.addEventListener('click', (e) => {
e.preventDefault() // stops navigation
openModal(link.href)
})// Without preventDefault — page reloads on submit:
form.addEventListener('submit', (e) => {
sendToServer(new FormData(e.target))
// page reloads before fetch completes!
})Event Delegation — One Listener for All
Instead of attaching listeners to every child, attach one to the parent and use e.target to figure out what was clicked:
// ✓ Delegation: one listener on parent
container.addEventListener('click', (e) => {
const card = e.target.closest('[data-card-id]')
if (!card) return
if (e.target.matches('.order-action'))
handleOrder(card.dataset.cardId)
if (e.target.matches('.remove-action'))
card.remove()
// Works for ALL cards — even future ones!
})// Click buttons inside any card
Your Challenge
Replace all individual .card click listeners with one delegated listener on .cards. Inside it: use e.target.closest('.card') to find the card, e.target.matches('.order-btn') to detect the order button, and e.target.matches('.remove-btn') to detect remove. Add a new card after wiring — confirm the listener covers it automatically.
Challenge
Replace individual card listeners with a single delegated listener on .cards. Use e.target.closest('.card') to find which card was clicked and e.target.matches('.order-btn') to check if the order button was the target.