HAP studying and taking notes

Event Bubbling and Delegation

Quick Reference

Everything I learned about bubbling and delegation at Station 4, organized for quick reference. How events travel up the DOM, why one listener on a parent beats many on the children, and the .closest() pattern that made delegation actually work. 🟠

← Back to Station 4: Bubbling Up

How Bubbling Works

1

Events Fire on the Target First

The element that was actually clicked is called event.target. The event fires there first, then travels upward through every ancestor.

2

Every Ancestor Gets the Event

After the target, the event fires on the parent, grandparent, and so on up to document and window. Any element with a matching listener along the way will respond.

3

Bubbling Is Default Behavior

Most DOM events bubble. Some do not (focus, blur). You do not need to do anything to make bubbling happen — it is always on unless you stop it.

The Bubbling Path

What happens when a nested element is clicked:
// Click on <span class="robot-name"> inside a .robot-card
// The event travels up:
//
//   span.robot-name        ← event fires here first (event.target)
//   div.robot-card
//   div.card-container     ← delegated listener lives here
//   main
//   body
//   html
//   document
//   window
//
// Any element with a matching listener along the path fires it.

The Delegation Pattern

1

Listen on a Parent, Not Every Child

Add one listener to the container. Any child that is clicked bubbles the event up to the container. The container's handler runs for all of them.

2

Dynamic Children Work for Free

Elements added to the container after the listener was wired still bubble events up to it. No additional listeners needed when adding cards dynamically.

3

Use .closest() to Identify the Card

event.target.closest('.robot-card') finds the nearest ancestor matching the selector, starting from whatever element was actually clicked. Returns null if none found.

4

Always Guard Against Null

After .closest(), check the result before using it: if (!card) return;. Clicks on container padding or gaps between cards return null.

Delegation Pattern

Complete delegation pattern:
// The delegation pattern
const container = document.querySelector('.card-container');

container.addEventListener('click', function(event) {
  // Find the nearest .robot-card ancestor of what was clicked
  const card = event.target.closest('.robot-card');

  // Guard: if click missed all cards, do nothing
  if (!card) return;

  // Work with the card
  console.log(card.dataset.robotId);
});

The .closest() Pattern

How .closest() walks up the DOM:
// .closest(selector) walks UP the DOM tree
// Returns the nearest ancestor matching selector
// Returns null if no match is found

// Starting from event.target (the clicked element):
const card = event.target.closest('.robot-card');

// Works whether the user clicked:
//   - the .robot-card div itself
//   - a .robot-name span inside it
//   - an icon or image inside that span
// All three bubble up through .robot-card.

event.target vs. event.currentTarget

Which element is which in a delegated listener:
// event.target vs. event.currentTarget in delegation

container.addEventListener('click', function(event) {
  console.log(event.target);
  // The element actually clicked — could be a span, icon, etc.
  // This changes with every click.

  console.log(event.currentTarget);
  // Always the element the listener is attached to — the container.
  // This never changes.
});

Dynamic Cards with Delegation

Adding a card that works with an existing delegated listener:
// Dynamic cards work automatically with delegation
// — no extra listener needed

const newCard = document.createElement('div');
newCard.classList.add('robot-card');         // delegation targets this class
newCard.dataset.robotId = 'unit-99';         // data your handler reads
newCard.innerHTML = '<span class="robot-name">Unit 99</span>';

container.appendChild(newCard);
// Clicking newCard bubbles up to the container listener. ✓

Stopping Propagation

stopPropagation — use carefully:
// Stopping bubbling (use carefully)
innerButton.addEventListener('click', function(event) {
  event.stopPropagation();
  // This event will NOT bubble up to parent listeners.
  // Useful when a child has a different action than the parent.
  // Overusing this breaks delegation — parent listeners never fire.
});

← Back to Station 4: Bubbling Up