Posted 10 months ago. Visible to the public. Repeats.

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:

$('.container').on('click', '.message', function(event) { console.log("A message element was clicked!") })

This technique has some advantages:

  1. When you have many descendants, you save time by only registering a single listener.
  2. When the descendants are changed dynamically via JavaScript, no additional event listeners must be registered to enhance new elements.

This technique has some disadvantages:

  1. 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 like scroll or mousemove. Performance will be fine with events that fire less frequently, like click, keydown or submit.
  2. 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 and stopPropagation() will only prevent it from bubbling up to the container's own ancestors.
  3. 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.


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 ( matches the desired selector, or has the desired selector in its ancestors:

let container = document.querySelector('.container') container.addEventListener('click', function(event) { if ('.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:

function addMatchingEventListener(element, eventType, selector, listener, ...optionsArgs) { element.addEventListener(eventType, ...optionsArgs, (event) => { let target = if (target && target.closest(selector)) {, event) } }) }

Use it like this:

addMatchingEventListener(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:

up.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.

Owner of this card:

Henning Koch
Last edit:
9 months ago
by Dennis
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra dev
This website uses short-lived cookies to improve usability.
Accept or learn more