Event delegation (without jQuery)

Updated . Posted . Visible to the public. Repeats.

Event delegation Show archive.org snapshot 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() Show archive.org snapshot to control the event's bubbling Show archive.org snapshot 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.

Info

jQuery has some complicated code in place to mitigate (2) and (3) for events listeners registered through jQuery#on() Show archive.org snapshot .

Native event delegation without jQuery

There is no built-in browser API to do event delegation in the style of jQuery#on() Show archive.org snapshot . 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:

let 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:

function 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:

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

With Unpoly

If you're using Unpoly Show archive.org snapshot you can use up.on() Show archive.org snapshot to register a delegating event listener:

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

Also see

Henning Koch
Last edit
Felix Eschey
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra dev (2021-10-06 07:03)