Many ways to deal with events in javascript

Many ways to deal with events in javascript

Do you remember the time where you discovered document.ready ? I do, it seemed like magic: run the code once your html is loaded. Then, you initialise your jQuery plugins, and you are good to go...

But recently, I've built a SPA (to be more precise, a PWA-SPA, which is a funky acronym if you ask me, or should we say a PSPWA ?). And I'm also dealing with ajax loaded interfaces for my admini panel.

And you know what? Suddenly, document.ready is not so magical anymore: your ajax loaded content does not trigger DOMContentLoaded . Your nodes get initialized twice. Event handlers are getting crazy. Stuff gets removed from the DOM. Not nice.

A more robust initialization phase

What's becoming very clear, is that as soon as you do anything a bit more complex, you cannot rely on DOMContentLoaded anymore. Take Unpoly for example. They have this special up.compile function which parse each fragment loaded into the page. Nice, but then you need to tweak everything to work with this. And what if you want to switch to Turbo for example?

The first step is to make sure you don't initialize things twice. This can be easy and simple. Simply call a initialize function which takes care of doing everything you need and add the element to a WeakSet. It won't prevent garbage collection once it leaves the dom, and you can just fire your big "init" function which contains multiple initialize calls.

But you could make your life easier

Most of the time, you want to listen to event, and add some initialization code. Therefore, calling addEventListener yourself might be a bit verbose, so you can go this way.

Again, the goal here is to prevent the same handler to fire twice, so in our case, we create a registry of events for each element and only add it once. And then, all you need is listen('.my-elem', 'click', someHandler)

But what about ajax content?

Yes, I hear you. This is all nice and well, but you still need to call your big old global init function each time some part of the dom changes. What if somehow this could be done automatically?

There are three ways to deal with this:

  • Event delegation: since we listen at document level, new element works transparently

  • Mutation observer: bind/unbind behaviour dynamically as stuff gets added to the dom

  • Self initializing elements: with custom elements, it's now trivial to have elements that take care of themselves

Let's look at these solutions.

Event delegation

I didn't find a library I liked, so, I've tried a little something on my own. That also let me test and try things :-)

It works like listen above, but instead of adding a listener to the element, we add everything to the document. We define a global listener. Then, we leverage closest to match the actual target during the capture phase.

Mutation observer

This is the big one. Inspired by jquery.entwine the entwine function lets you define a set of definitions for a given selector. For example:

    entwine('.my-elem', {
      click: function (e, el) {
        console.log("click", e, this, el, el === this);
      },
      mouseenter: (e) => {
        console.log("enter", e);
      },
      mouseleave: (e) => {
        console.log("leave", e);
      },
    });

There is also support for connected and disconnected events thanks to the mutation observer. I added namespaced definitions support like in the original lib, otherwise multiple definitions matching the same selector would get merged together.

Self initializing elements

Last, but not least, a topic I've already discussed on this blog: self-initializing elements. If you can afford to go the custom elements way, they will initialize themselves.

The only real downside with this is that is that you are forced to use dedicated html, so that may not work too well if you cannot change how your html is done.

Check the repo for modular behaviour

Also worth a visit