Handling Events in JavaScript
Table of contents
But first, back to basics !
What are events and event handlers ?
An event is a signal that something has happened. Often, when events happen, you may want to do something. There are many events that JavaScript provides to interact with the DOM. And to react to those events, event handlers are required. It takes a "event", and a callback function that runs in case of an event.
What is Event Propagation ?
Now you know. Think of a HTML code structure where there are multiple nested elements. Event Propagation determines in which order the elements receive the event. Event propagation is a way to describe the βstackβ of events that are fired in a web browser. It is method that describes how the browser handles events targeted at nested elements.
What is Event Capturing and Event Bubbling ?
Event Capturing and Bubbling are the two ways of event propagation in the DOM tree.
Bubbling
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.
Suppose we have the following nested HTML structure π
<div id="outer" onClick="outer()">Outer
<div id="parent" onClick="parent()">Parent
<div id="child" onClick="child()">Child</div>
</div>
</div>
On clicking on child, in what sequence the methods will be called ?
On clicking the child element, event bubbling takes place. It is the default behavior. Think of it like when is bubble is born, it grows and expands itself. So, child -> parent -> outer
, this is the sequence of the entire click events that will take place. Bubbling starts from the clicked element.
Similarly, on clicking the parent element, parent -> outer
will be the sequence of the click events that will take place.
Event Capturing/ Trickling
When an event happens on an element, it first runs on the outer most parent, then all the way down to other descendants. So it is capturing down the DOM tree.
Again, for the following code π
<div id="outer" onClick="outer()">Outer
<div id="parent" onClick="parent()">Parent
<div id="child" onClick="child()">Child</div>
</div>
</div>
On clicking the child element, event capturing takes place. The sequence of the entire click events will be : outer -> parent -> child
. Capturing starts from the outer most parent and ends up on the clicked element.
Syntax π
child.addEventListener("click", callback, useCapture);
useCapture
is an optional parameter that is to be provided to the event listener. It's value is boolean
. true -> capturing
and false -> bubbling
. By default, the value is false or in the case this parameter is not provided.
Now, let's see bubbling in action.
HTML code π
<div id="parent">
Parent
<div id="child">
Child
<div id="grandchild">Grand Child</div>
</div>
</div>
JavaScript code π
const parent = document.querySelector("#parent");
const child = document.querySelector("#child");
const grandChild = document.querySelector("#grandchild");
const output = document.querySelector("#output");
parent.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Parent clicked`;
output.appendChild(para);
}
);
child.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Child clicked`;
output.appendChild(para);
}
);
grandChild.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Grand Child clicked`;
output.appendChild(para);
}
);
As I said earlier, by default, events will bubble up. Hence, clicking on grand child will result into grandChild -> child -> parent
as the sequence of events.
Same output is obtained, if we provide optional third parameter as false
, in all the event listeners.
Capturing/ Trickling in action
Provide the third parameter, true
. Your code should look like : π
parent.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Parent clicked`;
output.appendChild(para);
},
true
);
child.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Child clicked`;
output.appendChild(para);
},
true
);
grandChild.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Grand Child clicked`;
output.appendChild(para);
},
true
);
Events will capture down. Hence, clicking on grand child will result into parent -> child -> grand child
.
Mix-match case - What if you want certain events to fire up afterwards and certain early ? See the following code : π
parent.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Parent clicked`;
output.appendChild(para);
},
true // capturing
);
child.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Child clicked`;
output.appendChild(para);
},
false // bubbling
);
grandChild.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Grand Child clicked`;
output.appendChild(para);
},
true // capturing
);
Here, the child element won't be covered in the capturing phase. When the target element is reached, the phase is changed. The following output is obtained when clicking on the grand child
element.
Stop Bubbling or Capturing
But, consider a scenario in which there are multiple nested elements. We need to stop propagation, at some point of the chain as these operations could be costly in such cases. How we can stop propagation up/down the hierarchy ? Using e.stopPropagation()
.
Code π
parent.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Parent clicked`;
output.appendChild(para);
},
true // capturing
);
child.addEventListener(
"click",
(e) => {
let para = document.createElement("p");
para.innerHTML = `Child clicked`;
output.appendChild(para);
e.stopPropagation();
},
true // capturing
);
grandChild.addEventListener(
"click",
() => {
let para = document.createElement("p");
para.innerHTML = `Grand Child clicked`;
output.appendChild(para);
},
true // capturing
);
When clicked on the grand child
, the following will be the output.
What to use ? Bubbling ? Capturing ? π
It depends on what you want to do. There is no better. The difference is the order of the execution of the event handlers.
Bubbling and capturing lay the foundation for βevent delegationβ β an extremely powerful event handling pattern.
Event Delegation
Event delegation is based on event bubbling. When working with events in a larger applications, there comes a point when there are many event handlers. Consider a situation, where you have a render a list of elements or something like infinite scroll
. There, on clicking each of the element under the list, you want certain action to be taken. Hence, `number of event listeners β to number of elements. Hence, resulting in bad performance and can be costly too.
Event delegation states that, "Instead of attaching the event handlers to all the child elements, attach to the only parent of all those child elements."
Traditional Aprroach. π
<ul id="products-list">
<li class="product" id="shoes">Shoes</li>
<li class="product" id="jacket">Jacket</li>
<li class="product" id="clothes">Clothes</li>
</ul>
<div id="output"></div>
<script>
const productsList = document.querySelectorAll(".product");
const output = document.querySelector("#output");
productsList.forEach((product) =>
product.addEventListener("click", () => {
output.innerHTML = `${product.innerHTML} is clicked`;
})
);
</script>
Delegation in action. Replace the script, with this : π
const productsList = document.querySelector("#products-list");
productsList.addEventListener("click", (e) => {
output.innerHTML = `${e.target.id} is clicked`;
});
Now, there is single event listener(on the parent element) instead of many. When clicked on the clothes, the following output is obtained.
This is done using the concept of event bubbling. Child(shoes) is clicked, it is bubbled up to the parent(products-list). Because of the single event handler attached, it listens to each and every click happens inside of it.
Pros of Event Delegation:
- It consumes less memory
- As there is single event handler instead of many, it leads to better performance.
- Less code
Cons of Event Delegation:
- Not all events are bubbled up.
- For eg: blur, focus
CSB's
Play around with the code ! π―
-> Event Bubbling and Capturing: https://codesandbox.io/s/event-bubbling-and-capturing-trickling-jqd1lf?file=/index.html
-> Event Delegation (Eg 1): https://codesandbox.io/s/event-delegation-eg-1-uh6qbp?file=/index.html
-> Event Delegation (Eg 2 - Tree Menu): https://codesandbox.io/s/event-delegation-eg-2-v6gj1i?file=/index.html
Bubble Up, Trickle Down
That's it for this article. If you liked and learned something new, do consider sharing β€οΈπ
Subscribe to my newsletter
Read articles from Hinanshi Suthar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Hinanshi Suthar
Hinanshi Suthar
Frontend Developer constantly learning and building