Popping some bubbles (event bubbling in JS)

Photo by Giu Vicente on Unsplash

Popping some bubbles (event bubbling in JS)

Part 2: Exploring capturing, bubbling, default behavior, and delegation

Introduction

The first time I ran into these topics, I was perplexed, but when I finished my research, things made more sense than ever before, and I'll aim to help you gain the same level of understanding through this blog.

Pre-requisites

This is the second part of my blog exploring Events in JavaScript and covering the basics, If you're unsure about them, I would ask of you to read that before diving into this one. Link to Part 1: A series of fortunate events (in JavaScript)

Preventing default behavior

Preventing the default behavior of an event is not exactly related to bubbling or capturing but nonetheless it is something that comes in handy, especially when you're playing with forms.

What preventing default behavior means is basically, preventing it from doing what it does by default.

Suppose, you create a submission form of some sort to take details from the user, now the user is not our most ideal user and they don't like to play by the rules so they add data in the form that doesn't exactly fit the description of what we expected. This doesn't seem like much of an issue, and that's cause there's this tiny bit of detail I forgot to mention.

When you click the submit button on a form, the natural behavior is for the data to be submitted to a specified page on the server for processing, and you get redirected to a page of the developer's choice. But wait, what about the incorrect data? Well...too late, it is already sent, before you could tell the user that this is a world of your making and they must follow your rules.

What can we do?

Enters preventDefault() event method. Calling this method will stop the form from being submitted by default, you can verify data and do all sorts of things and if everything is perfect, then submit the data.

image.png

Note: You will have to call whatever API you've built for submission, once prevented the event will/can not resort back to default behavior.

Bubbling sounds fun!

When we read 'Bubbling' we immediately think of soap bubbles one after the other rising in the air, right? The term means something similar in JavaScript, it's a term that describes how the event propagates or traverses when the element on which the event happens, is nested.

When an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors.

Okay, let's visualize this, suppose you're in your room, and you're given the instruction, to exit any door you see, you look around( if you don't already know where the door is) and you exit your room, then again you look around and see the door to your house, so you follow the instruction and leave the house.

Here, your room was nested inside your house. We might've seen something similar in HTML, have we? Lets try to create a similar situation.

<div id="container-1">
    <div id="container-2">
        <div id="container-3">
             <button id="alert-btn">Alert Button</button>
        </div>
    </div >
</div >

//In the script file, we pass a handler function to each of these elements like
//After querying the DOM elements,
const handlerFunc = function (e) {
  alert(e.currentTarget.id); //this will get the id we've passed
};
container1.addEventListener("click", handlerFunc);
container2.addEventListener("click", handlerFunc);
container3.addEventListener("click", handlerFunc);
alertBtn.addEventListener("click", handlerFunc);

See it in action: CodeSandBox Link

Here the 'Alert Button' is nested inside 3 elements, that's three levels of nesting. Now we've attached a 'click' event listener to all these elements. Which will simply, console the value of the tag, with e.currentTarget.id.

What happens in event bubbling is, that it starts from the element which you click, also called the 'target' element, and bubbles upwards, so here if you click on the button, it will check to see if an event listener for 'click' event is there and run it.

Then it will traverse upwards to 'container3', it will again check for an event listener for 'click' and run that handler. This will continue until the topmost element. This mechanism of event propagation is called event bubbling. As the event bubbles up.

So, we'll first be updated by 'alert-btn', followed by 'container-3', and so on... till 'container-1'. If we had 'click' event listeners attached to body and html elements as well, it would stop traversing at the html element

Note: This is the default behavior of how an event propagates in the modern browser.

Capturing

Capturing is the same but opposite to what bubbling is. In this type of event propagation, the event traverses from the topmost tag where the nesting for the target event starts, checks whether it has a listener for the event that just occurred, and runs it. This is repeated until it reaches the target element.

How to set the propagation method to capturing

Remember, the third property I mentioned in part I of this blog, called 'useCapture'? No, let me remind you.

The addEventListener method takes three parameters, the third one is optional. It could either be an options object or the useCapture boolean value, when we pass useCapture value as true, the event propagates in the capture method.

//We only need tp pass the third parameter to the above code example, like
container1.addEventListener("click", handlerFunc, true);
container2.addEventListener("click", handlerFunc,true);
container3.addEventListener("click", handlerFunc,true);
alertBtn.addEventListener("click", handlerFunc,true);

See it in action: CodeSandBox Link

This will work in exactly the opposite way as in the bubbling example, Here, first, we'll get 'container-1' alert, followed by 'container-2' and so on...till we reach the target element, here, 'alertBtn'

Now, you must be wondering, what if you don't want this to happen inside your nested elements, and why on earth are there two methods of propagation!? Well...

The dark ages

When browsers were a little less compatible, the internet was majorly accessed through two browsers, namely, Netscape and Internet Explorer. Netscape only used event capturing and Internet explorer only used event bubbling.

54.gif

When the W3C (World Wide Web Consortium) decided to standardize it, they ended up with a system that allowed both of these for countless reasons that are beyond the scope of this blog post.

Event phases

When the event starts traversing, its current phase can be accessed by the eventPhase property of the event object. The event is propagating back up through the target's ancestors in reverse order, starting with the parent, and eventually reaching the containing window.

my-goals-are-beyond-your-understanding-reverse-flash.gif

It returns a numeric value that can be interpreted as follows:

  • value:0 => None, i.e, the event is not being processed right now
  • value:1 => Capturing phase,i.e, The event is being propagated through the target's ancestor objects. This process starts with the Window object, then Document, then the html element, and so on through the elements until the target's parent is reached.
  • value:2 => At target, i.e, the event has arrived at the event's target.
  • value:3 => Bubbling phase, i.e, The event is propagating back up through the target's ancestors in reverse order, starting with the parent, and eventually reaching the containing Window object.

Can I stop it 🥺?

The answer is yes! Yes, you can stop it and without much hassle, you only need to add one small, teensie bit of code.

The stopPropagation() method on the event object can be added to stop the event from further propagating.

//the handler function will be modified to this
const handlerFunc = function (e) {
  e.stopPropagation();
  alert(e.currentTarget.id);
};

Now, we'd only get one alert, depending on the method of propagation you've chosen, either 'alert-btn' if the mode is set to bubbling (default) or 'container-1' if the mode is set to capturing.

spider-man-i-got-this.gif

Playing around with useCapture

Since we like to play around and break or build stuff so much, what if we were to pass the useCapture value as true for two of these elements in our code example, and not provide any value for the other two elements. Something like,

container1.addEventListener("click", handlerFunc, true);
container2.addEventListener("click", handlerFunc);
container3.addEventListener("click", handlerFunc, true);
alertBtn.addEventListener("click", handlerFunc);

Take a moment, and try to guess what will happen!

See it in action: CodeSandBox Link

Answer this first, can you get inside your room without first getting inside your house? No, right? Well, here something similar is happening, when the alert button will be clicked, it will run the outermost element's (container1) click handler, as its 'useCapture' property is set to true, then it will see that container2 is registered for bubbling, so it will move on for now. Again, as in the case of container-1, container3's handler will run because of it's useCapture value of 'true'. It will finally reach the target element, run its handler, and then bubble up to those elements which are set for the bubbling method.

The order of alerts we'll get

  1. container1
  2. container3
  3. alertBtn
  4. container2

Event Delegation

You look at event bubbling and might see a problem, however, some very talented developers look at it and see an opportunity for less work. How? Through event delegation.

Event delegation is a practice that is best put to use when we want to run some code when the user interacts with any element amongst a large number of similar children elements.

We set the event listener on the parent element or the enclosing elements for this large number of children and have events that happen on the children bubble up to their parent. The 'event.target' property will always return the element on which the event occurred if we need to access it, which is usually the case.

This way, we only need to add one listener for all the numerous children, and helps us save a good amount of time.

See it in action: MDN Example

Resources

  1. MDN documentation on events
  2. MDN example showing event phases

Conclusion

We've covered topics like preventing default behavior, event capturing, event bubbling, and event delegation, these are sometimes even asked in interviews. You should be able to use these concepts to your advantage.

Stay tuned.