Learn Event Delegation in JavaScript

Learn Event Delegation in JavaScript

Let’s say that you have a list of elements and you want to listen for an event on each one of them. One way of doing it would be to loop over all the elements in the list and add event listeners on them individually. But as you can see this could be an inefficient approach and this is where the concept of event delegation comes into play.

Event delegation is a concept where we listen for the event on the parent element and then on the basis of the event object that is passed in the event handler we are able to access the child items. This is useful when we have more child elements and if the content in the child elements is dynamically added.

General Syntax

parentElement.addEventListener( eventType, eventHandler, useCapture)

Before jumping into event delegation let’s first understand the concept of event propagation

Event Propagation

event propagation.png

Capturing phase

When an event takes place on a target element, the event handlers on the parent elements are executed first and after that, the event is propagated down towards the target element and the target event handler is executed.

Execution steps: Document→ Element 1→Element 2→Element 3 → …→Target element

Target phase

It is a phase where the event reaches the target element and the event handler is executed.

Bubbling phase

When an event takes place on a target element, the event handler on the target element is executed first and after that, the event is propagated up to its parent elements and their event handlers are executed.

Execution steps: Target element → …→Element 3→Element 2→Element 1→Document

Note: One can also stop the propagation if they want by using event.stopPropagation() inside of the handler*

Let’s get a better understanding with an example

Html

<div class="parent-box">
<div class="child-box" data-number=1>
<div class="child-box-1" data-number=2>
<div class="child-box-2" data-number=3>
</div>
</div>
</div>
</div>

CSS

{
    margin:0;
    padding:0;
    box-sizing:border-box;
}
.parent-box{
    height:100vh;
    background-color:red;
}

.parent-box,.child-box,.child-box-1,.child-box-2{
    display:flex;
    justify-content:center;
    align-items:center;
}

.child-box{
    height:200px;
    width:200px;
    background-color:blue;
}

.child-box-1{
    height:130px;
    width:130px;
    background-color:green;
}
.child-box-2{
    height:50px;
    width:50px;
    background-color:yellow;
}

With useCapture set to false

JS

const parentEl=document.querySelector(".parent-box");
const childBox=document.querySelector(".child-box");
const childBoxOne=document.querySelector(".child-box-1");
const childBoxTwo=document.querySelector(".child-box-2");

childBox.addEventListener("click",(e)=>{
    console.log("child box")
});

childBoxOne.addEventListener("click",(e)=>{
    console.log("child box 1")
});

childBoxTwo.addEventListener("click",(e)=>{
    console.log("child box 2")
});

The JS code has the useCapture equal to false so when we click on the .child-box-2 the event propagates from the .child-box-2 → .child-box-1 → .child-box

Console output:

Untitled.png

With useCapture set to true

JS

const parentEl=document.querySelector(".parent-box");
const childBox=document.querySelector(".child-box");
const childBoxOne=document.querySelector(".child-box-1");
const childBoxTwo=document.querySelector(".child-box-2");

childBox.addEventListener("click",(e)=>{
    console.log("child box")
},true);

childBoxOne.addEventListener("click",(e)=>{
    console.log("child box 1")
},true);

childBoxTwo.addEventListener("click",(e)=>{
    console.log("child box 2")
},true);

Console output:

Untitled (1).png

Event delegation

Html

<div class="parent-box">
    <div class="child-box" data-number=1>
    </div>
    <div class="child-box" data-number=2>
    </div>
    <div class="child-box" data-number=3>
    </div>
    <div class="child-box" data-number=4>
    </div>
    <div class="child-box" data-number=5>
    </div>
    <div class="child-box" data-number=6>
    </div>
</div>

CSS

{
    margin:0;
    padding:0;
    box-sizing:border-box;
}

.parent-box{
    height:100%;
    width:100%;
    background-color:red;
    display:flex;
    justify-content:center;
    align-items:center;
    gap:2rem;
    flex-wrap:wrap;
    padding:2rem;
}

.child-box{
    height:200px;
    width:200px;
    background-color:blue;
}

JS

const parentEl=document.querySelector(".parent-box");
parentEl.addEventListener("click",(e)=>{
    if(e.target.classList.contains('child-box')){
        console.log(`Box No: ${e.target.dataset.number}`);
    }
});

Code pen

Here we are basically applying the “click” event on the “.parent-box” and then console logging the box number. As you can see in the above example that by accessing the event.target we can get the child elements.

Few points to remember

  • A lot of the time the element we want to target also has child elements due to which the event.target gives us the child element of the target element. In that case, we should use closest( selectors ) property on the target element

  • event.target(child) gives the element on which the event takes place and event.currentTarget (parent) gives the element on which the event listener is attached

🙌 So this was about event delegation. Hope you were able to understand the concept of event delegation. You can connect with me on Twitter and LinkedIn.