const list = document.querySelector('ul')
list.addEventListener('click', e => {
// Do something when list is clicked on
})
Determining the event target
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))
Avoid misfires
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
}
})
Dealing with nested elements
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.
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.
Set pointer-events to none
Use closest
Pointer events
pointer-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
closest searches the DOM upwards for an element that matches the selector. This search includes the element itself.
element.closest(selector)
If it finds an element that matches the selector, it returns the element.
If it doesn’t find any elements, it returns undefined
If 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`
}
})
Pointer events or closest?
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
Exercise
Here’s a list of famous people. Do the following:
Create an event listener that uses the event delegation pattern
Log the element if the target matches li
Try using both pointer events and closest to filter the event target
// 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)
}
})