DEV Community

Whoissosick
Whoissosick

Posted on

How Do Event Bubbling, and Event Delegation Work?

Event bubbling, or propagation, refers to how an event "bubbles up" to parent objects when triggered. For example, consider this code:

<p>
  <span>Click me~!</span>
</p>
Enter fullscreen mode Exit fullscreen mode

The p element here would be considered the parent of the span element.

When you click on the span element, like you are instructed to, the span element becomes the target of a click event. That event, however, also bubbles up to the parent – the p element can receive and consume that event as needed.

But what does this actually mean? Well, you could attach an event listener to the p element:

const p = document.querySelector("p");
p.addEventListener("click", (event) => {
  console.log(event.target);
});
Enter fullscreen mode Exit fullscreen mode

Then, when you click on the span element you will see the text Click me~! logged to the console.

Just to be sure how things are working

const p = document.querySelector("p");
const span = document.querySelector("span");
p.addEventListener("click", (event) => {
  console.log("P listener: ");
  console.log(event.target);
});
span.addEventListener("click", (event) => {
  console.log("Span listener: ");
  console.log(event.target);
});
Enter fullscreen mode Exit fullscreen mode

To give you an idea of how the event bubbles up, here's what you'll see in the console after clicking the span element:

"Span listener: "
<span>Click me~!</span>
"P listener: "
<span>Click me~!</span>
Enter fullscreen mode Exit fullscreen mode

Now let's see what happens when you prevent the propagation of an event with stopPropagation(). We'll call it in our span's event listener:

const p = document.querySelector("p");
const span = document.querySelector("span");
p.addEventListener("click", (event) => {
  console.log("P listener: ");
  console.log(event.target);
});
span.addEventListener("click", (event) => {
  console.log("Span listener: ");
  console.log(event.target);
  event.stopPropagation();
});

"Span listener: "
<span>Click me~!</span>
Enter fullscreen mode Exit fullscreen mode

This time, we don't see our p listener trigger. The event never fires for the p element, because we told it to stop propagation while it was being processed for the child span element.

Event delegation can be thought of as the opposite. It's the process of taking a captured event, and delegating it to another element.

Going back to our code, let's update it so clicking on a span element changes it to red:

const p = document.querySelector("p");
const span = document.querySelector("span");
p.addEventListener("click", (event) => {});
span.addEventListener("click", (event) => {
  event.target.style.color = "red";
});
Enter fullscreen mode Exit fullscreen mode

But what if you have twenty span elements? Or maybe you use JavaScript to create more span elements on the fly?

Rather than having to attach an event listener to every single span element, you can actually use the listener on the p element for all of them. In other words, you can delegate the handling of the span clicks to the parent p element.

Our code might now look something like this:

const p = document.querySelector("p");
p.addEventListener("click", (event) => {
  event.target.style.color = "red";
});
Enter fullscreen mode Exit fullscreen mode

Notice how we no longer have any listener attached to the span element at all. You have properly delegated the event handling to the p element. But does it work?

Let's generate a few extra span elements and see:

<p>
  <span>Click me~!</span>
  <span>Click me~!</span>
  <span>Click me~!</span>
  <span>Click me~!</span>
</p>
Enter fullscreen mode Exit fullscreen mode

Now, each time we click on a span, that element's text will become red.

And just like that, with a single event listener we've properly allowed a click event to bubble up from span elements to the parent p, and delegated the logic for that click event to the p element.

Event propagation and delegation can be a complex topic, especially as you get into heavily nested elements like tables. You are encouraged to explore this further and experiment with some of the code we've written here.

function instrumentCards(instrumentCategory) {
  const instruments =
    instrumentCategory === "all"
      ? instrumentsArr
      : instrumentsArr.filter(
          ({ category }) => category === instrumentCategory
        );

  return instruments
    .map(({ instrument, price }) => {
      return `
          <div class="card">
            <h2>${instrument}</h2>
            <p>$${price}</p>
          </div>
        `;
    }).join("");
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)