JavaScript Event Madness! Capturing *all* events without interference

Avatar of Ghostlab
Ghostlab on (Updated on )

The following is a guest post by Matthias Christen and Florian Müller from Ghostlab. Ghostlab is cross-browser cross-device testing software for Mac and PC. One of the things I’m very impressed Ghostlab can do is sync the events from one browser to all the others. Scroll one page, the others you are testing scroll. Click somewhere on one page, that same click happens on the others. I asked them about how the heck it does that when there is so much that could interfere. Matthias and Florian explain:

We have been developing Ghostlab, a tool for cross-device and cross-browser testing your websites. In essence, it syncs any number of clients that view a website and makes it easy to go through that site and make sure it works great and looks good on all viewports and platforms. A central component is the replication of events across clients – when the user clicks on a button, scrolls, or enters text into a form field on one client, we have to make sure that the very same thing happens on all others.

Capturing missed events

The client side script component of Ghostlab is listening for all sorts of events that happen, tries to catch them, and replicate them to all the other clients. At some point, we noted that we didn’t quite catch all events. We had to figure out what the problem was, and came up with a solution that allows you to catch any events that happen on your site, no matter how they are handled by any custom JavaScript.

How can it be that you are listening for an event, but don’t catch it? That’s because any event handler has the option of doing several things with an event. You’ll know the ability to prevent the default action usually taken by the browser (preventDefault()). It allows you, for example, to have a link (<a>) for which the user does not go to its href on a click event.

In addition to telling the browser to not do the default action whenever the event occurs, an event handler can also stop the propagation of an event. When an event is triggered on an element, say a link, any event handler that is attached to this specific element will be allowed to handle the event first. After it is done, the event will bubble up until it reaches the document level. Every listener for this event at any parent of the original element will be able to react to the event – that is, unless a lower event handler decides to stop propagation, in which case the event will no longer go further up in the DOM.

Our example 1 demonstrates this. When you click on the inner div (Level 3), the click handler for this element will handle the event first. Unless it prevents propagation, the parent elements (Level 2, Level 1) will afterwards be able to react to the event in order. In case you tick the “Stop Propagation” checkbox, the event handler will prevent further propagation – so, click events on Level 3 will no longer reach Levels 1 and 2, and click events on Level 2 will no longer reach Level 1.

See the Pen Event Propagation Example I by Florian Mueller (@mueflo00) on CodePen.

In example 2, we demonstrate the effect of stopping immediate propagation. This method implicitly stops the bubbling up of the event, so if there were any parent elements, we would observe the same behavior as in example 1. In addition, it also prevents any additional handlers of the same event on the same element from being executed. In our example, we have to click event handlers registered on our element. If we choose to stop immediate propagation, only the first responder will be able to handle the event, and after calling stopImmediatePropagation, no other handler will be called.

See the Pen Event Propagation Example II by Florian Mueller (@mueflo00) on CodePen.

So if you would want to listen to all the events that happen in the DOM, that’s quite difficult. To prevent missing events due to cancelled bubbling up, you’d have to register all the event handlers on every single element of the DOM. And even then, in case a developer chooses to stop immediate propagation, this would only work if you were the first one to register for the event.

If we want to be absolutely sure that we are informed of any event, no matter what its handlers do with it, we have to get in the loop at the very beginning of event registration. For that purpose, we override the addEventListener function of the EventTarget object. The basic idea is simple: every event handler registration will, in the end, call this method. If we override it, we have full control of what happens when any event handler registers.

The original addEventListener function takes an event handler function (the “original event handler”) as its second argument. If we don’t override the addEventListener function, the original event handler function will be called whenever the specified event occurs. Now, in our custom addEventListener function, we simply wrap the original event handler in our own event handler (the “wrapper function”). The wrapper function contains any logic we require, and can ultimately call the original event handler – if we desire to do so.

Example 3 demonstrates this. The three click events attached to the “Level” elements are registered through our custom addEventListener function, so any time a click event occurs on these elements, our wrapper function is called. There, we observe the status of the checkbox – if it is ticked, we simply do not call the original event handler, thereby preventing any click events to trigger any original event handler. A little side note: if you want to make sure that you take control of all events, you have to make sure that you override the addEventListener function before any event is registered.

See the Pen Event Override Example by Florian Mueller (@mueflo00) on CodePen.

While this solution has helped us improve Ghostlab, you may ask yourself what this could be good for? Well, there are several possibilities. We have sketched two possible use cases below – if you can come up with any other, please share!

Possible Application 1: Event Visualizer

There are tools to help you visualize events on your website (we love, for example, VisualEvent Allan Jardine). Using our technique we can quickly implement such a tool ourselves. In our example, for every registered event, we simply draw a little square on top of the element for which the event was registered. On hover, we display the source code of the (original) registered event handler function.

See the Pen Event Visualizer by Florian Mueller (@mueflo00) on CodePen.

Possible Application 2: Event Statistics

Instead of drawing event indicators onto the screen, you can also display that information in another manner. This example shows you a tabular overview of the registered events on any page (given that you inject the code into it), and updates the triggered events in real-time. This can, for example, be helpful when you are experiencing performance issues and are suspecting it might be because there are too many event handlers.

See the Pen Event Override: Stats by Florian Mueller (@mueflo00) on CodePen.

Events are just a small part of the huge complex world of Front End Development. At Vanamco we are excited to bring you tools that help simplify and streamline your processes whilst keeping you up to date with best practice.