The `matches` operation Or how to do Event delegation without jQuery

An article, posted about 6 years ago filed in jquery, underscore, prototype, live, Events & javascript.

I’m slowly migrating projects away from jQuery. It has served me really well in the past, and it still works, but it is no longer necessary. One of the few things that have been bothering me though was the lack of ‘live’-attaching of EventListeners. jQuery allows you to bind an onsubmit event listener to any <form>, even <form>s that haven’t been added to the DOM yet. It used to be called live() (from version 1.3 to 1.9), but the functionality was later reintroduced with on(). You would bind the event listener not to a <form> directly, but to a container (up to document).

$(document).on('click', 'p.alert', function(e){ 
  confirm("Did you read this carefully?"); }
);

Moving away from jQuery, we use the ‘standard’ addEventListener(): but it has no easy way of delegating the events.

Introducing matches

Element.querySelector(".CSS.selector.here") is probably quite well known by know, but I was unaware of the matches() method, also available on any element.

Given this simple html:

 <body><p>Test</p><p class="alert">Test 1</p></body>

We could get all p elements simply by:

 var ps = document.getElementsByTagName("p") // Returns a HTMLCollection (basically a NodeList for HTMLElements only)

or with the modern (but probably somewhat slower CSS-style selector):

 var ps = document.querySelectorAll("p") // Returns a NodeList

The matches operation allows us to check wether the returned node would match another CSS-selector.

 ps[0].matches("p") && ps[1].matches("p") // return: true

 ps[1].matches("p.alert") // returns: true
 ps[0].matches("p.alert") // returns: false

This is useful for catching the delegated event jQuery-style (using CSS-selectors)

Note that IE hasn’t implemented the matches method using its standard name, even in the latest versions. Hence a polyfill is required (abridged, from MDN)

if (!Element.prototype.matches) {
    Element.prototype.matches = Element.prototype.msMatchesSelector
}

Event delegation

Event delegation is delegating the catching of an event to an element higher up the hierarchy. Tell document to catch errors for a form. I’m not fan of this abstract wording that practically results in automatic binding of events (and considering this is the end-goal, you might actually think actually listening for DOM changes and binding it directly on DOM-change might also be a viable alternative to the approach presented here, but hey, this is simplest for now)

The rewrite

document.addEventListener('click', (e) => {
    if (e.currentTarget.matches("p.alert")) {
        console.log("hit")
    }
});

I don’t like this as much as the jQuery listener.

If it is you know what you’re doing, I think it is perfectly fine to implement the following (I don’t want to replace my $’s with _1 (or whatever) either, so there we go):

HTMLDocument.prototype.addDelegatedEventListener = function(event, matcher, cb) {
  var base = this;
  var newCB = function(event) {
      if (event.target.matches(matcher)) {
          return cb(event);
      }
  };
  base.addEventListener(event, newCB);
}

This allows you to simply write, which suits me well:

document.addDelegatedEventListener('click', "p.alert", (e) => {
    console.log("hit new")
});

Enjoy. Codepen here.

1I’m referring here to the popular Underscore JavaScript library that “provides a whole mess of useful functional programming helpers without extending any built-in objects.” Well, I do like extending built-in objects. That’s what prototype is made for. Really: when this method name has gotten another implementation some day it won’t matter if the project doesn’t get any attention. If it is still being maintained, find and replace it if it bothers you2 and get back to work. It is nothing compared to switching jQuery or underscore-like frameworks.

2As you might have seen, I try to stay close to the most the API of the addEventListener-method. This to improve chances that if browsermakers would decide to add this functionality, simply removing this code might even work.

Op de hoogte blijven?

Maandelijks maak ik een selectie artikelen en zorg ik voor wat extra context bij de meer technische stukken. Schrijf je hieronder in:

Mailfrequentie = 1x per maand. Je privacy wordt serieus genomen: de mailinglijst bestaat alleen op onze servers.