Mastering JavaScript Event Handling Techniques: Bubbling, Capturing, Delegation, and Propagation

Mastering JavaScript Event Handling Techniques: Bubbling, Capturing, Delegation, and Propagation

Introduction

Events in JavaScript are essential and fundamental for creating interactive web pages. They can be considered the driving force behind a browser, enabling it to perform actions on behalf of the user. When you find a button and want to click it, you hover over it and press the mouse button. This action triggers a response within the browser, which identifies the type of event (in this case, a click) and executes a corresponding function, often in the form of a callback.

Let's see the code below -

// HTML - 
<button onclick="clickMe()">Click Me</button>

//JS - 
function clickMe() {
  alert("I'm button, and I was clicked.");
}

We can see that the browser alerted our text when clicked.

Let's dive deep into different concepts revolving around event handling in DOM.

Event Bubbling

Event bubbling is what an event does, when an event is triggered on any DOM element, it travels up to its parent element and continues this process until it reaches the topmost HTML element. This propagation to all parent elements allows events attached to a child element to automatically become available to all parent elements present in the DOM.

This is the HTML code we have --

<body>
  <div id="div">
    <p id="p">
      <span id="span">
        <button id="btn">
          Click Me
        </button>
      </span>
    </p>
  </div>
</body>

When the DOM tree is created, we get something like this --

As soon as we click on button, it fires the event associated with it and then triggers the event attached to its parent element span which then triggers p that again triggers an event on div and this goes top to the HTML.

Here we need to make sure, that event bubbling goes from bottom to top only. It'll never trigger events of children element when fired from any parent.

function clickMe(target) {
  console.log(`${target} is clicked`);
}

document.getElementById("div").addEventListener("click", () => {
  clickMe("div");
});

document.getElementById("p").addEventListener("click", () => {
  clickMe("paragraph");
});

document.getElementById("span").addEventListener("click", () => {
  clickMe("span");
});

document.getElementById("btn").addEventListener("click", () => {
  clickMe("button");
});

Here we get the following scenarios -

  1. If we click on the div, we get -

     "div is clicked"
    
  2. If we click on the p, we get -

     "paragraph is clicked"
     "div is clicked"
    
  3. If we click on the span, we get -

     "span is clicked"
     "paragraph is clicked"
     "div is clicked"
    
  4. If we click on the button, we get -

     "button is clicked"
     "span is clicked"
     "paragraph is clicked"
     "div is clicked"
    

    We can visualize, how the event was propagated to the top and all the subsequent events present in parent elements were fired too.

Event Capturing

Event Capturing is a method of applying event delegation to events that don't bubble.

There are several events, such as blur, focus, load, and unload, that does not bubble and cannot be delegated when necessary. For these types of events, we use event capturing to simulate the bubbling of an event.

This is similar to event bubbling, with one key difference: in this case, we navigate from top to bottom, while in event bubbling, we go from bottom to top. The commonality is that events are never fired on any child elements.

If we have a structure like this - div > p > button - and we click on the button, the sequence of events fired will be the same as the tree, i.e., event on div, then on p, and finally on the button.

To make this happen, we need to pass the third argument as true while adding an event like this: document.getElementById("btn").addEventListener("click", () => { clickMe("button"); }, true);

If we modify our previous code, we get this response when the button is clicked:

"div is clicked"
"paragraph is clicked"
"span is clicked"
"button is clicked"

Event Delegation

Event Delegation is a pattern that relies on Event Bubbling. It is an event-handling approach where we manage the event on the parent element rather than the location where the event was initially triggered.

Let's consider a use case where we have multiple buttons inside any element. Ideally, we would need to add a separate event listener to each button, which is quite common. However, what if we have dozens of buttons, and all of them need to trigger the same callback? In this scenario, we can employ the event delegation pattern to achieve our desired result by binding a single event to just the parent element.

<body>
  <div id="div">
    <p id="p">
      <button id="btn1">
        Button 1
      </button>
      <button id="btn2">
        Button 2
      </button>
    </p>
  </div>
</body>

We can add a single event to the div element, and delegate the events to the buttons without adding multiple events separately -

document.getElementById("div").addEventListener("click", (e) => {
  if (e.target.tagName === "BUTTON") {
    console.log(e.target.innerText);
  }
});

event-delegation

Benefits of Event Delegation

There are several benefits to using this pattern when writing elegant code, including:

  1. It helps in writing less code by attaching fewer events inside the DOM.

  2. If multiple DOM elements share similar logic inside event callbacks, they can be handled and maintained in the same place.

  3. Using fewer event listeners helps in minimizing the risk of memory leaks and helps in overall application optimization.

  4. If we attach a large number of events inside the DOM on a single page, it results in the unresponsiveness of the page and slows down the whole page. Event delegation helps a lot in these kinds of scenarios.

Event Propagation

Event Propagation is a concept of DOM, on which event bubbling occurs within the browser. It is a bi-directional method on which the lifecycle of any event relies. Every time any event is fired, event propagation occurs internally to complete the execution.

This is divided into a total of three phases -

  1. Capturing Phase - The first phase begins when an event is triggered. In this phase, the event travels from the window to the specific element where the event was initiated, notifying every element in its path.

  2. Targeting Phase - This phase only occurs on the target element where the event was triggered. The event is registered here.

  3. Bubbling Phase - This is the last phase which starts from the element where the event was actually fired and propagates to the top till Window. Now event starts triggering from the target element, and all other events present to other parent elements are also triggered.

Event Propagation

stopPropagation()

Now, what if multiple events are attached to both the parent and child elements?

In this case, If we try to trigger the element on any of the child elements then other events attached to the parent element will also be triggered which can lead to unexpected behavior if not handled properly.

To fix this issue, we have an in-built method stopPropagation() which prevents to bubble of the event to the top and only the event that is associated with the element firing triggering the event is executed.

// HTML - 
<body>
  <div id="div">
    <button id="btn">
      Button
    </button>
  </div>
</body>

// JS - 
function clickMe(target) {
  console.log(`${target} is clicked`);
}

document.getElementById("div").addEventListener("click", () => {
  clickMe("div");
});

document.getElementById("btn").addEventListener("click", () => {
  clickMe("button");
});

If we click on a button, we get the following output -

"button is clicked"
"div is clicked"

Let's modify our JS code by adding stopPropagation() -

document.getElementById("btn").addEventListener("click", (e) => {
  e.stopPropagation();
  clickMe("button");
});

This will be output now -

"button is clicked"

This is how we terminated the default bubbling of the event by using stopPropagation().

Summary

In this article, we discuss the fundamentals of event handling in JavaScript, focusing on concepts like event bubbling, event capturing, event delegation, and event propagation. We also explore the benefits of event delegation and the use of stopPropagation() to prevent unexpected behavior in our applications.


🌟 Enjoyed the read? I’d love to hear your thoughts! Share your takeaways and let’s start a conversation. Your insights make this journey richer for all of us.

👏 If you found this helpful, a clap or two would mean the world — it helps more folks find these guides!

🔜 What’s next? I’m already brewing the next topic, packed with more insights and a-ha moments. Stay tuned!

🙏 A big thank you for spending time with my words. Your support fuels my passion for sharing knowledge.

👋 Until next time, keep coding and keep exploring!

💬 Let’s stay connected! Follow me on Hashnode for more adventures in coding: Rishav Pandey: Hashnode