DEV Community

Mohamed Idris
Mohamed Idris

Posted on • Updated on

What is Event Delegation? And why would we need it?!

Event Delegation is the process of delegating to a parent element the ability to manage events for child elements.

We able to do this by making use of:

  • the event object and its .target property
  • the different phases of an event (Capturing / At target / Bubbling)

That might sound like "Huh!", so let's look for an example and come back here later!

A case study to demonstrate why it's SUPER useful!

Imagine there is a <div> that has 200 paragraphs child elements, and when clicking on any of <p> tags of those, we want to console.log some message!
So at first, we might write the code like this:

const myCustomDiv = document.createElement('div');

for (let i = 1; i <= 200; i++) {
    const newElement = document.createElement('p');
    newElement.textContent = 'This is paragraph number ' + i;

    newElement.addEventListener('click', function respondToTheClick() {
        console.log('A paragraph was clicked.');
    });

    myCustomDiv.appendChild(newElement);
}

document.body.appendChild(myCustomDiv);
Enter fullscreen mode Exit fullscreen mode

200 respondToTheClick()

There are a number of ways we could refactor this code. For example, as of right now, we're creating 200 different respondToTheClick() (that all actually do the exact same thing!). We could extract this function and just reference it, instead of creating two hundred different functions:

const myCustomDiv = document.createElement('div');

function respondToTheClick() {
    console.log('A paragraph was clicked.');
}

for (let i = 1; i <= 200; i++) {
    const newElement = document.createElement('p');
    newElement.textContent = 'This is paragraph number ' + i;

    newElement.addEventListener('click', respondToTheClick);

    myCustomDiv.appendChild(newElement);
}

document.body.appendChild(myCustomDiv);
Enter fullscreen mode Exit fullscreen mode

1 respondToTheClick()

So, we've made refactoring on the number of event listeners by writing 1 function instead of writing it 200 times!

However, we still have two hundred event listeners. They're all pointing to the same listener function, but there are still two hundred different event listeners.
What if we moved all of the listeners to the <div> instead?

const myCustomDiv = document.createElement('div');

function respondToTheClick() {
    console.log('A paragraph was clicked.');
}

for (let i = 1; i <= 200; i++) {
    const newElement = document.createElement('p');
    newElement.textContent = 'This is paragraph number ' + i;

    myCustomDiv.appendChild(newElement);
}

myCustomDiv.addEventListener('click', respondToTheClick);

document.body.appendChild(myCustomDiv);
Enter fullscreen mode Exit fullscreen mode

1 respondToTheClick function, and only 1 event listener

Now there is only:

  • a single event listener
  • a single listener function

So, now the browser doesn't have to store in memory two hundred different event listeners, and two hundred different listener functions. That's great for performance!

However, you would notice that we've lost access to the individual paragraphs. We can not target a specific paragraph element. So how do we combine this efficient code with the access to the individual paragraph items that we did before with the first code?

We use the process event delegation.

const myCustomDiv = document.createElement('div');

function respondToTheClick(evt) {
    console.log('A paragraph was clicked: ' + evt.target.textContent);
}

for (let i = 1; i <= 200; i++) {
    const newElement = document.createElement('p');
    newElement.textContent = 'This is paragraph number ' + i;

    myCustomDiv.appendChild(newElement);
}

document.body.appendChild(myCustomDiv);

myCustomDiv.addEventListener('click', respondToTheClick);
Enter fullscreen mode Exit fullscreen mode

Nearly PERFECT!
It's just there's nothing to ensure that it was actually a <p> tag that was clicked on before console that message. Imagine having different tag siblings to the 200 <p> tags, or the parent <div> element has some padding, and we didn't click an actual <p> tag!

The solution? We should check the Node Type.

if (evt.target.nodeName === 'P') {} or (evt.target.nodeName.toLowerCase() === 'p') {}

Note that:

The .nodeName property will return a capital string, not a lowercase one. So when you perform your check make sure to either:

  • check for capital letters
  • convert the .nodeName to lowercase

All the credit goes to: Udacity and its nanodegree program.

happy coding!

Top comments (0)