This is Part 4 of my I Need To Learn More JavaScript series.
- Part 1: What is a Closure in JavaScript?
- Part 2: What is forEach and Map in JavaScript?
- Part 3: What is a Reference in JavaScript?
- Part 4: What is Event Delegation in JavaScript?
- Part 5: What is Variable Scope in JavaScript?
Event Delegation
To understand Event Delegation you must first understand event listeners.
I hate defining things by using the words in its name? but having gotten that caveat out of the way, an event listen listener is something that listens for an event.
An event in JavaScript is defined as ?things that happen to HTML elements,? and there are a a lot of them.
Here are some of the common JavaScript events:
- change:: An HTML element has been changed
- click:: The user clicks an HTML element
- mouseover:: The user moves the mouse over an HTML element
- mouseout:: The user moves the mouse away from an HTML element
- keydown:: The user pushes a keyboard key
- load:: The browser has finished loading the page
addEventListener()
To add an event listener on an HTML element you use the addEventListener() method.
An Example of the addEventListener() Methodconst character = document.getElementById(“disney-character”);character.addEventListener(‘click’, showCharactersName);
The first part document.getElementById is the event target ? in our case it is the HTML element we are targeting. But in JavaScript, the event target can be a plethora of things. It can be an HTML element in the document (like our example above), it can be the document itself (i.e. a web page loaded in the browser), or the even window; a top-level object in Client Side JavaScript which encompasses everything. However in most use cases, it is an HTML element. The second part is the actual event listener.
The eventListener above works like this:
When a user clicks the HTML element with the id disney-character the event listener is executed and calls the showCharactersName function.
Event listeners are set on page load. So when you first open an website, the browser downloads, reads, and executes the JavaScript.
const character = document.getElementById(“disney-character”);character.addEventListener(‘click’, showCharactersName);
In our code above, on page load, the event listener finds an HTML element with the id disney-character and sets a click event listener on that HTML element.
This works fine if the element exists on the page when the page is loaded. However what happens to the event listener when the element is added to the DOM (webpage) after the initial page load?
Event Delegation
Event Delegation solves this problem. To understand Event Delegation, we need to look below at our list of Disney Characters.
Thank you Wes Bos for the CSS styles!
This list has some basic functionality. For our purposes, you can add characters to the list, and you can check the boxes next to the characters name.
This list is also dynamic. The inputs (Mickey, Minnie, Goofy) were added AFTER the initial page load, and subsequently didn?t have event listeners attached to them.
Let?s take a look at the code:
const checkBoxes = document.querySelectorAll(?input?)checkBoxes.forEach(input => input.addEventListener(?click?, ()=> alert(?hi!?)))//an alert should fire when I click on the inputs (Mickey, Minnie, or Goofy)
But lets? take a look at the HTML AT PAGE LOAD:
<ul class=?characters?></ul>
Now let?s take a look at the HTML AFTER PAGE LOAD (from local storage, API call, etc):
<ul class=?characters?> <li> <input type=?checkbox? data-index=?0″ id=?item0″> <label for=?item0″>Mickey</label> </li> <li> <input type=?checkbox? data-index=?1″ id=?item1″> <label for=?item1″>Minnie</label> </li> <li> <input type=?checkbox? data-index=?2″ id=?item2″> <label for=?item2″>Goofy</label> </li></ul>** The inputs were placed on the DOM after page load and DID NOT have event listeners bound to them.
If you try to click on the inputs (the characters ? Mickey, Minnie, or Goody), you would expect an alert to pop up that says ?hi!?, but because they weren?t present at page load, the event listeners WERE NOT bound to them, and subsequently nothing happens.
The alert ?hi? does not appear!
So ? how do we fix this problem?
Event Delegation.
The whole idea behind event delegation is that instead of listening for a change on the inputs directly, we should look for an HTML element that is going to be on the page when the page initially loads.
In our example ? the unordered list with the class name characters is on the page at page load. We can attached the event listener there!
<ul class=?characters?> // PARENT – ALWAYS ON THE PAGE <li> <input type=?checkbox? data-index=?0″ id=?char0″> //CHILD 1 <label for=?char0″>Mickey</label> </li> <li> <input type=?checkbox? data-index=?1″ id=?char1″> //CHILD 2 <label for=?char1″>Minnie</label> </li> <li> <input type=?checkbox? data-index=?2″ id=?char2″> //CHILD 3 <label for=?char2″>Goofy</label> </li></ul>
It is best to think of event delegation as responsible parents and negligent children. The parents are basically gods, and the children have to listen to whatever the parents say. The beauty is if we add more children (more inputs), the parents stay the same ? they were there from the beginning or, in other words, on page load.
Let?s attach the event listener.
<ul class=?characters?></ul><script> function toggleDone (event) { console.log(event.target) } const characterList = document.querySelector(‘.characters’) characterList.addEventListener(‘click’, toggleDone)</script>
So now that we have an event listener set on the unordered list, characters and not the individual children (the actual characters), what happens if we click an input (Mickey, Minnie, or Goofy) after page load and console.log that event.target?
Console.log(event.target)
The following event target is returned.
event.target
The event.target is a reference to the object that dispatched the event. Or in other words, it identifies the HTML element on which the event occurred.
The event in our case is the click! The object on which the event occurred is the <input/>.
** A label is considered part of the input object ? that is why we see both. **
Console.log(event.currentTarget)
If we console.log(event.currentTarget) ? we see something different.
event.currentTarget
The event.currentTarget identifies the current target for the event, as the event traverses the DOM. It always refers to the element to which the event listener has been attached. In our case the event listener was attached to the unordered list, characters, so that is what we see in our console.
Writing Event Delegation in JavaScript
Because we now know that the EVENT.TARGET identifies the HTML elements on which the event occurred, and we also know what element we want to listen for (the input element), solving this in JavaScript is relatively easy.
//Event Delegationfunction toggleDone (event) { if (!event.target.matches(?input?)) return console.log(event.target) //We now have the correct input – we can manipulate the node here}
Basically the code above states, if the event target that was clicked DOES NOT match the input element, exit the function.
If the event target that was clicked DOES match the input element, console.log the event.target and execute subsequent JavaScript code on that child node.
This is important, now we can be confident that a user clicked the correct child node, even though the inputs were added to the DOM after the initial page load.
Event Bubbling
If you want to stop reading here ? by all means please do! We just covered the basics of event delegation. But for a deeper understanding of why event delegation works, we need to understand Event Bubbling.
What REALLY happens when you make a click?
Whenever a user makes a click it ripples up all the way up to the top of the DOM and triggers clicks events on all the parent elements of the element you clicked. You don?t always see these clicks, because you aren?t always listening (with an event listener) for a click on these elements, but this bubbling up does happen.
This is called event bubbling or event propagation.
Because of its bubbling nature, event propagation basically means that anytime you click one of our inputs on the DOM, you are effectively clicking the entire document body.
Here is an example in action:
<div class=?one?> <div class=?two?> <div class=?three?> </div> </div></div><script> const divs = document.querySelectorAll(‘div’); function logClassName(event) { console.log(this.classList.value); } divs.forEach(div => div.addEventListener(‘click’, logClassName));</script>
Above we have three divs: DIV #1, DIV #2, DIV #3. Each DIV has it?s own event listener, and when we click on a DIV in browser, we ask it to console.log its class name by executing the function, logClassName().
Thanks Wes Bos again!
Above is what we see in the browser. Notice how my mouse is clicking DIV #3. As one would expect, when I click DIV #3, I see the class name logged in the console (to the right). But when I click DIV #3, I also DIV #2 and DIV #1 being logged to the console. This is event bubbling! We see the class name logged, because we added event listeners to each parent div.
Source: https://javascript.info/ ? clicking on DIV #3 bubbles up to 2, and 1.
Back to our Event Delegation Example
<ul class=?characters?> // PARENT — This is where the listener is! <li> <input type=?checkbox? data-index=?0″ id=?char0″> //CHILD 1 <label for=?char0″>Mickey</label> </li> <li> <input type=?checkbox? data-index=?1″ id=?char1″> //CHILD 2 <label for=?char1″>Minnie</label> </li> <li> <input type=?checkbox? data-index=?2″ id=?char2″> //CHILD 3 <label for=?char2″>Goofy</label> </li></ul><script> const characterList = document.querySelector(‘.characters’); characterList.addEventListener(‘click’, toggleDone);</script>
So back to our example in the event delegation section ? we had only one event listener, and it was set on our unordered list characters. YET when we clicked a child of that parent HTML element, the HTML element input, it fired the event listener we set that was bound to the unordered list.
Because of event bubbling you can place an event listener on a single parent HTML element that lives above a HTML child, and that event listener will get executed whenever an event occurs on any of its child nodes ? even if these node children are added to the page after the initial load!
In Conclusion ? Why Use Event Delegation?
Without event delegation you would have to rebind the click event listener to each new input loaded to the page. Coding this is complicated and burdensome. For one, it would drastically increase the amount of event listeners on your page, and more event listeners would increase the total memory footprint of your page. Having a larger memory footprint decreases performance? and poor performance is a bad thing. Second, there can be memory leak issues associated with binding and unbinding event listeners and removing elements from the dom. But that is beyond the scope of this article!
That?s all I have for this round ? keep chugging people!