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
Event Listener from thednp
Subscribe to my newsletter
Read articles from Thomas Portelange directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Thomas Portelange
Thomas Portelange
A Web Developer based in Belgium, specialized in PHP & JS development