Event delegation without jQuery
Event delegation Archive is a pattern where a container element has a single event listener that handles events for all descendants that match a CSS selector.
This pattern was popularized by jQuery that lets you do this:
Copy$('.container').on('click', '.message', function(event) { console.log("A message element was clicked!") })
This technique has some advantages:
- When you have many descendants, you save time by only registering a single listener.
- When the descendants are changed dynamically via JavaScript, no additional event listeners must be registered to enhance new elements.
This technique has some disadvantages:
- In the example above the container receives all click events and must run JavaScript to check if a
.message
element was hit. This can degrade performance when you deal with fast-firing events likescroll
ormousemove
. Performance will be fine with events that fire less frequently, likeclick
,keydown
orsubmit
. - A delegated event listener cannot use
Event#stopPropagation()
Archive to control the event's bubbling within its descendants. The event has already bubbled up to the container andstopPropagation()
will only prevent it from bubbling up to the container's own ancestors. - As a conclusion of (2), a delegated listener will always be called after listeners registered directly on a descendant, regardless of the order in which the listeners where added.
Info
jQuery has some complicated code in place to mitigate (2) and (3) for events listeners registered through
jQuery#on()
Archive .
Native event delegation without jQuery
There is no built-in browser API to do event delegation in the style of
jQuery#on()
Archive
. However, it is not hard to build this on your own, or use one of many libraries that do it for you.
Building your own delegating event listener
Register a standard event listener to your target. Before you run your callback, check that the actual element (event.target
) matches the desired selector, or has the desired selector in its ancestors:
Copylet container = document.querySelector('.container') container.addEventListener('click', function(event) { if (event.target.closest('.message')) { console.log('A message was clicked!') } })
If you do this a lot, you may want to define a function to do it for you:
Copyfunction addMatchingEventListener(element, eventType, selector, listener, ...optionsArgs) { element.addEventListener(eventType, ...optionsArgs, (event) => { let target = event.target if (target && target.closest(selector)) { listener.call(this, event) } }) }
Use it like this:
CopyaddMatchingEventListener(container, 'click', '.message', function(event) { console.log("A message was clicked!") }
With Unpoly
If you're using
Unpoly
Archive
you can use
up.on()
Archive
to register a delegating event listener:
Copyup.on(container, 'click', '.message', function(event) { console.log("A message was clicked!") }
Your development team has a full backlog of feature requests, chores and refactoring coupled with deadlines? We are familiar with that. With our "DevOps as a Service" offering, we support developer teams with infrastructure and operations expertise.