Skip to content

shawnbot/event-delegation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 

Repository files navigation

Event delegation

Event delegation is a DOM JavaScript pattern that replaces event listeners on individual elements with listeners further up the tree that can conditionally respond to bubbling events.

Why?

  • Adding lots of direct event listeners can have serious performance implications (citation needed).
  • Knowing when to add listeners can be tricky if your DOM is built or updated asynchronously (via AJAX or with React, Angular, or web components).
  • Adding, removing, and otherwise managing direct listeners can be tricky when the DOM is constantly changing.

Some or all of these concerns may apply if you're building web components with technologies like HTML custom elements. In that case, it's safe to assume that you have no control over what's going on inside of your component — and really, you shouldn't need to — unless you're using mutation observers... in which case, well, ugh.

jQuery example

Consider this example of direct listeners to implement an accordion component in jQuery:

$(function() {
  $('.accordion').each(function() {
    var accordion = this;
    $('button', this).on('click', function() {
      // this function is left as an exercise to the reader
      toggleAccordionButton(accordion, button);
    });
  });
});

There is one glaring issue here: it assumes that the DOM as it exists when it's "ready" will never change. If your page or application adds or removes either a .accordion or .accordion button at runtime, the necessary listeners won't be added or removed with them.

Lucky for us, jQuery implements its own form of event delegation, which makes it possible to rewrite the above as:

$('body').on('click', '.accordion button' function(event) {
  var accordion = $(event.target).closest('.accordion').get();
  toggleAccordionButton(accordion, event.target);
});

This is much better for a couple of reasons:

  1. It doesn't care when the DOM is initialized (as long as there's a <body>).
  2. The .accordion button selector ensures that only buttons within an .accordion will trigger the behavior, and that our .closest('.accordion') query will always give us the right ancestor.
  3. Also thanks to the use of .closest(), we can now nest accordion elements — as long as we're sure to prevent the event from bubbling up to another ancestor with event.stopPropagation().

Implementations

Here are a couple of different implementations of the event delegation pattern to check out:

  • dom-delegate is the Financial Times' excellent implementation, with a simple API:

    var delegate = new Delegate(document.body);
    delegate.on('click', '.accordion button', toggleAccordion);
  • receptor offers a declarative API and introduces the concept of reusable behaviors, which encapsulate collections of listeners that can be atomically added and removed:

    var behavior = receptor.behavior({
      'click': {
        '.accordion button': toggleAccordion,
        '.menu button': toggleMenuButton,
        'a[href^="#"]': scrollToLink,
      },
    });
    behavior.add(document.body);

    ProTip: If you're having trouble with child elements (e.g., images in <button> elements) gobbling up your delegates, receptor might be the ticket: it uses Element.closest() to match the delegate selector to the event target.

  • Gator.js does the job, too.

  • You can find lots of other delegation modules on npm.

Further reading

Releases

No releases published

Packages