JavaScript Event System Tips
by Michael on (Updated on )
Back
JavaScript is simultaneously an amazing and terrible language. I've had the pleasure of working with it for about five years now, so here are some useful tips related to its wonderful event system.


Automatically Log All Events for an Element (DevTools)

Have you ever been confused by the order in which HTML events fire? Or perhaps you would just like to see what events are available. Chrome's DevTools has a handy function called monitorEvents which can help:


    <input type="text" id="myInput">
    <script>
        var myInput = document.getElementById('myInput');
        //Signature: monitorEvents(elementRef, [, eventNames]); 
        monitorEvents(myInput);
    </script>

After focusing the input by using the tab key and interacting with it, the console would look something like this: (It should be noted that $0 refers to the currently selected element in DevTools on most modern browsers)


...and just like that, we know that the event order for keyboard interaction is focus, keydown, keypress, textInput, input, keyup, etc. We can also see that Chrome fires a textInput event, which is a non-standard event. Had we not used monitorEvents, we'd have no idea it existed. Optionally, the eventNames argument can be populated with the names of events to monitor. Read more about monitor events.

Registering Event Listeners on Dynamically Added Elements

When I first started working with JSF (a server-side rendering framework), my biggest frustration was my inability to easily add event listeners to dynamically generated elements. For example, I had a common component on the server which would render a collapsible panel. The output would have looked something like this:


<div class="panel collapsible">
   <div class="panel-header">
        <div class="panel-title">My Panel</div>
        <button type="button" class="panel-toggle">+</button>
    </div>
    <div class="panel-body">
        Hello, world
    </div>
</div>

The problem was the panel-toggle button needed to call some JavaScript to handle collapsing the panel-body div.  My solution at the time was to create a function on the global scope so I could add an onclick attribute on the server:


    <!--The updated button with click listener-->
    <button type="button" onclick="CollapsiblePanel.togglePanel(event)" class="panel-toggle">+</button>
    <script>
         window.CollapsiblePanel = {
            togglePanel: function(event){
               //magic to collapse the panel
            }
         };
    </script>

Now I could render all the panels I wanted dynamically and they would collapse. But I really didn't like that I was forced to add to the global scope for this. Could there be another way? Yes, of course there is, with event bubbling. When an event occurs on an element, that event gets propagated up the DOM tree so that all parents of that element can also listen to the event. If we put a listener on the highest level node (the document), we can listen for any click event and then add some logic to filter only the events we want to listen to:

document.addEventListener('click', function(event){
    if(event.target.classList.contains('panel-toggle')){//logic to select the correct target
       //magic to collapse the panel
    }
});

Now, this is exactly what jQuery did with its now deprecated .live method, and this behavior is still possible with the .on method. The only case where the document-level listener would not be called is when some child element decided to stop event propagation, which is pretty unlikely. This also works for content dynamically added with JS in addition to elements rendered on the server.

Use the Input Event Instead of Keypress to Detect Advanced Content Change

A very common scenario I've encountered during my webdev career is the need to detect when the user has added content to an input or textarea. My old method of detecting this was listening for a keypress event or a similar keyboard event. The issue is that when the user pastes (by using the context menu, for example) no keyboard event is fired, so the listener is never called. My solution to this was to listen to the paste event as well.

We need worry about this no longer as HTML5 introduces the input event which normalizes several events into a single consistent input event. 

    <input type="text" id="myInput">
    <script>
        var myInput = document.getElementById('myInput');
        myInput.addEventListener('input', (e) => console.log(e));
    </script>

Now, not only will our listener get called whenever the input changes, but we also get the inputType property on the event which let's us know how the content was changed.

Some examples include

  • insertText (the user just typed in regularly)
  • insertFromPaste (the user pasted the given text)
  • deleteWordBackward (the user hit the backspace key)
  • deleteWordForward (the user hit the delete key)

Conclusion

JavaScript has a very powerful event system which we should take full advantage of. Leveraging DevTools can help us learn how it works and make us aware of events which we never knew existed. Happy hacking.