Event delegation
Hey ,
I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.
Please upgrade (use this link) access this content.
I'm super eager to help you learn more!
Hey ,
I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.
Please upgrade (use this link) access this content.
I'm super eager to help you learn more!
Let’s say you have a list of items. You want to listen to a click event on each item. One way is to attach an event listener to every item.
<ul>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li>Item 4</li>
  <li>Item 5</li>
</ul>
const items = Array.from(document.querySelectorAll('li'))
items.forEach(item => item.addEventListener('click', e => {
  // Do something with item
}))
But what if you had one thousand items? If you do the above, you’ll create one thousand event listeners. That’s not the best way.
A better way is to use the event delegation pattern.
The event delegation pattern makes use of event propagation. It works like this:
Note: the event delegation pattern only works for events that bubble.
<ul>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li>Item 4</li>
  <li>Item 5</li>
</ul>
const list = document.querySelector('ul')
list.addEventListener('click', e => {
  // Do something when list is clicked on
})
The element that fires the event is called the event target. It can be found with the target property.
list.addEventListener('click', e => console.log(e.target))
 
  The event delegation pattern is sensitive to all events fired from the listening element onwards. In this case, you can also fire the callback when you click on the list itself.
 
  To prevent such misfires from happening, we need to check if the target element matches the element we’re looking for. We can do so with the matches method.
matches checks if the element matches the selector we provided. You should be familiar with it’s syntax.
element.matches(selector)
matches will either return true or false. It will be true if the element matches the selector.
In this case, we only want to do something if a user clicks on a list item. We can check whether the event target is a list item with matches.
list.addEventListener('click', e => {
  if (e.target.matches('li')) {
    // Do something
  }
})
Let’s say you want to listen to a click event on a <button>. This button has an SVG and some text embedded in it.
<button>
  <svg> <!-- Gear icon --> </svg>
  <span>Click me!</span>
</button>
const button = document.querySelector('button')
button.addEventListener('click', e => {
  console.log(e.target)
})
Watch what happens if you click on the gear icon or the text.
 
  When we click on the gear icon, the SVG shows up in the console. that’s because the event.target (which was the clicked element) is the SVG itself. Ditto for the text.
Most of the time, we’re not looking for the SVG or the text. We’re looking for the button element instead. There are two methods to always ensure we get the button element.
pointer-events to noneclosestpointer-events is a CSS property that determines how an element respond to mouse events. (click is a mouse event). If you set pointer-events of an element to none, it will not respond to mouse events.
In this case, we can set pointer-events of all descendant elements to none. We can do this with the following CSS:
/* Preventing events from bubbling in CSS */
button * {
  pointer-events: none;
}
Note: I placed this CSS in the reset.css file.
closest searches the DOM upwards for an element that matches the selector. This search includes the element itself.
element.closest(selector)
undefinedIf search for button, we can ensure we work with the button element even though the user clicked on the SVG (or text).
button.addEventListener('click', e => {
  const button = event.target.closest('button')
  if (button) {
    // Activates when user clicked any element within `button`
  }
})
In practice, you would normally add click events to <button>s. In this case, pointer-events: none is easier to use because it will work for all your buttons
Here’s a list of famous people. Do the following:
liclosest to filter the event target<ul>
  <li><a href="#">Benjamin Franklin</a></li>
  <li><a href="#">Thomas Edison</a></li>
  <li><a href="#">Franklin Roosevelt</a></li>
  <li><a href="#">Napoleon Bonaparte</a></li>
  <li><a href="#">Abraham Lincoln</a></li>
</ul>
// With `closest`
const list = document.querySelector('ul')
list.addEventListener('click', ev => {
  if (ev.target.closest('li')) {
    console.log(ev.target)
  }
})
With Pointer Events:
/* CSS */
li a {
  pointer-events: none;
}
// JS
const list = document.querySelector('ul')
list.addEventListener('click', ev => {
  if (ev.target.matches('li')) {
    console.log(ev.target)
  }
})