Emerging Patterns in JavaScript Event Handling

Share this article

During the last few months the debate on the web about the best way to handle events has thrived. First, a few months ago, Google released the JsAction library; then, more recently, the Object.observe() method was introduced as part of the ECMAScript 7 specification (but already supported in Chrome 36 and Node.js Harmony).

Developers had already been taking sides on whether it is still “mandatory” to have all the logic confined to script files, or if it is acceptable or even preferable to inline selected parts of this logic into HTML. In this post, we will try to sort out this debate, going through the different patterns for error handling, and then weighting pros and cons of these alternatives.

The Facts

JsAction is a Google library for event delegation in JavaScript. It is based on Closure Library, and was introduced on Google maps a few years ago to overcome some browsers errors related to management of event listeners. JsAction aims to decouple events from the methods handling them, and to do so it moves part of the event handling logic to the HTML.

A general, recent trend has started that moves part of the logic not just to HTML files, but within DOM elements affected by that logic. This isn’t true just for event handling: a number of template-based frameworks (like Angular, Ractive, React) are emerging; they enforce the Model-View-Controller pattern in web application, and allow data-binding and reactive programming.

The introduction of the Object.observe() method in the next ECMAScript specification is another step in that direction, since it allows developers to natively apply the Publisher/Subscriber pattern to a whole new set of situations, and not just event handling. Declarative frameworks are already based on this logic, but the introduction of Object.observe() will help them gain an amazing improvement in performance.

The Story So Far

Since the introduction of JavaScript, the orthodox way to handle events has changed several times. Initially, if you wanted to add dynamic behavior to elements on your page, you only had one way: adding an attribute to the tag itself, and associate a snippet of JavaScript code with it. You could either write code inside the attribute value, or call one or more functions previously defined in the global scope.

For example, to change the background of your page to blue with a button click:

<button onclick="document.bgColor='lightblue'">Feel Blue</button>

It wasn’t long before the limitations and hazards of HTML on[event] attributes were discovered. As of November 2000, the addEventListener method was added to ECMAScript 3 specification as an alternative way to bind handlers to browser events. Previously, Microsoft had already added the attachEvent() method, but it took a while to catch on. While word spread on the Net in early 2000s, it was not until around 4 years after that the term unobtrusive JavaScript was coined.

The Netscape approach that in-lines event handlers had, indeed, some downsides that the event listener approach solved:

  • Mixing code and markup can make your code less readable and far less maintainable.

  • Global Scope Pollution: in-line code is defined in the global scope, and every function called in it must also be defined in the global scope.

  • It’s a weak spot for XSS injection: the attribute can contain any code that will be fed to the “evil” eval function without any control.

The introduction, in 2006, of the first widespread Ajax libraries, YUI and jQuery, pushed this new approach beyond any expectation, and they enforced good practices simply making them the most convenient choice for developers.

They also added to the event listeners approach:

  • Scalability: encapsulating an event handler into a function is DRY-compliant, since it allows to “prototype” and reassign the same logic to multiple handlers; jQuery CSS selectors added an easy and effective way to attach event handlers programmatically to set of nodes:
$(document).ready(function () {
  $('.clickable').click(function () {
    document.body.style.background='lightblue';
    return false;
  });
});
  • Debugging: with in-browser tools like FireBug and Chrome Developer Tools, debugging JavaScript became less of a nightmare, but in-lining code would frustrate it all.

Problems With the addEventListener Pattern

The event listener approach, though, raised some serious concerns:

  • Attaching listeners to objects in JavaScript can lead to closure leaks, if it’s not done properly. Closures are one of the most powerful language features of JavaScript, but they must be used with caution when intertwined with DOM elements. Closures keep a pointer to their enclosing scope. As a result, attaching a closure to a DOM element can create a circular reference and thus, a memory leak. This example from Google’s JavaScript Style Guide shows the right and wrong way to deal with it.

  • Internet Explorer had a rather problematic handling of garbage collection, especially when it came to events. Besides the well known mutual circular reference problem, in older versions of Microsoft’s browser, when a node was removed from the DOM its handlers weren’t garbage collected, and this caused memory leaks.

What’s JsAction Again?

This leads us right to JsAction. As mentioned at the beginning of this post, it is an event delegation library that allows mapping between events and handlers via their names, using a custom HTML attribute called jsaction, which will be directly handled by the library.

Each event handler is separately registered in one or more JavaScript files or inline scripts; they are associated with method names, and since the mapping between names and functions is taken care of by the library itself, there is no need to add them to global scope.

In summary, JsAction should provide a few advantages: 1. Work around memory leaks problems in some (older) browsers; 2. Reduce or avoid global scoping pollution; 3. Reduce coupling between events and handlers implementations; 4. Better performance and scalability, since it allows to set one event listener per page, and then routes itself the events to the proper handler;

To see an example of how it works, check JsAction’s GitHub page.

Truth to be told, the example code isn’t exactly easy to read, nor as simple as you would expect. Also, most of the properties above can be obtained with a few lines of JavaScript. Global scope pollution, for example, can be limited using the module and namespace patterns. Late loading can be as easily achieved by initially assigning stubs to the event handlers, then asynchronously load an external script with the real handlers and remapping the events on completion.

Implementing points 3 and 4 is a bit more complicated: we need to set a single handler for the whole page, set an attribute in the DOM elements stating which method will be used as handler, and create a “super-handler” method that routes the workflow to the appropriate method.

Once again, it may or may not be the right solution for your needs, depending on the characteristics of your project. Despite its many pros, it still has some weakness:

  • The library isn’t exactly lightweight.

  • It doesn’t look particularly intuitive to use, and the learning curve will probably be steep for beginners. The documentation is skinny, and that doesn’t help.

  • It can be hard to get started with it. Without a compiled version available, you are forced to download Closure compiler and Closure library.

Declarative Frameworks

So, JsAction might not be the definitive solution to event handling in JavaScript, and, as we saw, it has been been around for a while, although not as an open source project. And yet, after it was open-sourced, a lively debate started on the net between enthusiasts and critics. Besides the innate love for flames of the Internet generation, I believe that one of the main reasons is probably the fact that declarative frameworks, whose popularity is quickly rising, largely share the same design choice, with a higher degree of integration between presentation and logic and a return to in-line code not just for event handlers, but even for populating page elements with content. Wait a minute, wasn’t mixing logic and presentation bad? Well, it is! We mentioned a few advantages of having your logic separated from presentation, easiness of debugging and clarity above all. But, sometimes, maintainability can be improved specifying the logic connected to an object next to the object itself.

Frameworks like RactiveJs, Angular, Ember, and React are not just meant to let you inject code in your views. They heavily use template based models for presentation to allow you binding event handlers, data and even presentation logic directly inside the DOM elements, and then specify the details of these logic in separate scripts. Basically, it’s the same schema used by JsAction to decouple event handlers names and handlers implementations. All in all, they rather increase separation between presentation and logic by enforcing the application of the MVC pattern to an higher degree, and at the same time they allow a very convenient use of templates.

These frameworks controls much more than event handling. They also allow data-binding, which starts to be important when you care about Model-View-Controller separation. They let you bind parts of the view to JavaScript objects, updating it every time the object behind it is modified. Moreover, they update views in particularly efficient ways, modifying only the smallest DOM nodes affected by the change, limiting page repainting, since that would be a bottleneck in most webapps.

To this end, Ractive and React use a virtual DOM – an abstract representation of the DOM that allows for very fast operations by minimizing the amount of DOM manipulation that needs to take place. They are very similar to each other, both focusing on reactive programming and visualization. While Angular is not just focused on the view part of MVC, it is a more complex framework that, at the same time, handles routing, connection to the server, etc.

All these frameworks support two-way binding, a convenient way to enforce consistency between values in the DOM and state in the application logic. Say, for example, that you need to display a list of items in your page. Suppose you want to use a traditional imperative paradigm. Then, you’d need to do something like this:

<!doctype html>
<html>
  <body>
    <div id="container" class="container" >
    </div>
    <script type="text/javascript" src="..."></script>
  </body>
</html>
//...

function createItemHTML (val) {
  return '<span class="">' + val + '</span>';
}

function displayList (container, items) {
  container.empty();
  $.each(items, function (index, val) {
    var element = $('<div>');
    element.attr('id', 'div_' + index);
    element.html(createItemHTML(val));
    container.append(element);
  });
}

function editItem (container, itemId, itemValue) {
  var element = container.find('#' + itemId);
  if (element) {
    element.html(createItemHTML(itemValue));
  }
}
//...
displayList($('#container'), items);
//...
editItem(container, id, newVal);

The code above makes use of some good patterns to avoid repetition, but still you can see that we are mixing logic and presentation, just the other way around.

Now, let’s see how you would do the same thing in Ractive:

<!doctype html>
<html>
  <body>
    <div id="container" class="container" >
    </div>
    <script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
    <script src="logic.js"></script>
    <script id='listTemplate' type='text/ractive'>
      {#items:num}
        <div id="div_{{num}}" on-click="itemClick">
          <span>{{this}}</span>
        </div>
      {/items}
    </script>
  </body>
</html>
var ractive = new Ractive({
  el: 'container',
  template: '#listTemplate',
  data: {
    'items': items
  }
});

ractive.on({
    'itemClick': function (e) {
      //access e.node and e.context for both the DOM element 
      //  and the Ractive state associated with it
    }
});

//...

//Now update items with a new list
ractive.set('items', newItemsList);

That’s it! No need to write code to update your page. Ractive will take care of it for you. It is clearer, more maintainable, better designed, and more performant. We were even able to add event handlers to our items in a scalable way.

Object.observe()

Object.observe() is a peek into the future, since it hasn’t even made it into ES6 specification – it has just been added to ES7. However, Google has already implemented it in Chrome 36, and the Observe-JS Polymer library will mimic support for it in every browser, exploiting native support when available.

This method allows you to asynchronously observe changes to objects and arrays. Observers will receive time-ordered sequences of change records describing the set of changes which took place in a set of observed objects. With Object.observe(), event-centered programming, otherwise known as reactive programming, is not restricted to the user interface anymore. For example, you can implement two-way data binding with language primitives – no need to install a framework like Ractive just for that.

Data-binding in Declarative Frameworks

One solution to provide data-binding is dirty-checking, (used by Angular). Any time data could have changed, the library has to go and check if it actually did, using either a digest cycle or a change cycle. Angular’s digest cycle identifies all expressions registered to be watched and checks if there are any change.

Another solution, used by Ember, Backbone, and Ractive, is employing container objects. The framework creates objects which hold the data. These objects have accessors to the data and so every time you set or get any property the framework can capture your action and internally broadcast it to all the subscribers. This solution works well and it is relatively performant in comparison to dirty-checking, with a good algorithmic behaviour, proportional to the number of things changed.

Performance Improvement

The new method added to the language allows us to observe an object, mutate properties, and see a report of what has changed. If you want to watch a plain JavaScript object, it is as easy as this:

// A model can be an object literal
var plainObject = {
  name: 'Counter',
  total: 0
};

// Define an observer method
function observer(changes){
  changes.forEach(function(change, i){
    console.log('what property changed? ' + change.name);
    console.log('how did it change? ' + change.type);
    console.log('whats the current value? ' + change.object[change.name]);
    console.log(change); // all changes
  });
}

// Start watching the object
Object.observe(plainObject, observer);

At some point, you might decide that you don’t need to watch that object anymore:

Object.unobserve(plainObject, observer);

Roadmap

As mentioned above, native support for Object.observe() has only been added to Chrome 36 and to nodejs Harmony (enable it with the --harmony flag). Opera is also reported to be at work for shipping support to native data-binding in one of the next releases. In the meantime, waiting for other browsers to catch up, you can use Observe-JS Polymer library, in order to guarantee that your application will work even with older browsers versions as well.

As you can imagine even declarative frameworks have, on average, embraced this as an opportunity: Ember and Ractive are planning to release full support for Object.observe() asap, in the next releases; at Angular they have a more “long-term” approach, so they are working to add it in version 2 of the framework.

Conclusions

We have taken a long tour to review pros and cons of a few design choices, and taken a peek at the future of web development. Hopefully after reading through the whole post you are now at least aware of a variety of solutions and patterns that can help you dealing with event handling and data binding. When you face your next design challenge, keep in mind that there is not one single right solution for all problems.

References and Further Reading

  1. Crockford on JavaScript – Episode IV: The Metamorphosis of Ajax
  2. Google JavaScript Style Guide
  3. Javascript Closures
  4. JsAction Repo on Github
  5. The difference between Ractive and Angular
  6. The difference between Ractive and React
  7. Containers & Dependency in Ember.js
  8. Data-binding Revolutions with Object.observe(), by Addy Osmani

Frequently Asked Questions (FAQs) on JavaScript Event Handling

What are the different types of JavaScript event handling patterns?

JavaScript event handling patterns are diverse and can be categorized into traditional, inline, and advanced patterns. Traditional patterns involve the use of HTML attributes to call JavaScript functions. Inline patterns, on the other hand, involve the use of JavaScript within HTML tags. Advanced patterns are more complex and involve the use of JavaScript libraries or frameworks like jQuery or React. These patterns allow for more sophisticated event handling, including event delegation and event propagation.

How does event propagation work in JavaScript?

Event propagation in JavaScript involves the process of an event moving through the Document Object Model (DOM). It can move either from the top (document) down to the target element (event capturing), or from the target element back up to the top (event bubbling). Developers can control this process using methods like stopPropagation() or preventDefault().

What is the difference between event capturing and event bubbling?

Event capturing and event bubbling are two phases of event propagation in JavaScript. Event capturing is the phase where the event goes down the DOM tree to the target element, while event bubbling is the phase where the event bubbles up from the target element to the document. By default, event handlers are executed in the bubbling phase, but this can be changed by setting the useCapture parameter to true.

How can I prevent the default action of an event in JavaScript?

The default action of an event can be prevented in JavaScript using the preventDefault() method. This method is called on the event object passed into the event handler function. For example, to prevent a form from submitting when the submit button is clicked, you would use event.preventDefault() in the submit event handler.

What is event delegation in JavaScript?

Event delegation in JavaScript is a technique where you delegate the handling of events to a parent element instead of handling events on individual child elements. This technique is useful when you have many elements that require the same event handling functionality, as it can improve performance and simplify code.

How can I use regular expressions in JavaScript?

Regular expressions in JavaScript are used for pattern matching within strings. They are created using the RegExp constructor or by using a regular expression literal, which consists of a pattern enclosed between slashes. Regular expressions can be used with various string methods like match(), replace(), search(), and split().

What are JavaScript design patterns?

JavaScript design patterns are reusable solutions to commonly occurring problems in software design. They can be structural, creational, or behavioral and can help improve the efficiency, maintainability, and scalability of your code. Examples of JavaScript design patterns include the Module pattern, Prototype pattern, and Observer pattern.

How can I handle errors in JavaScript?

Errors in JavaScript can be handled using the try…catch…finally statement. The try block contains the code that may throw an error, the catch block contains the code to be executed if an error occurs, and the finally block contains the code to be executed regardless of whether an error occurs or not.

What is the difference between == and === in JavaScript?

The == operator in JavaScript is used for comparison, but it performs type coercion if the types of the two variables being compared are different. On the other hand, the === operator, also known as the strict equality operator, does not perform type coercion and will return false if the types are different.

How can I debug JavaScript code?

JavaScript code can be debugged using various tools and techniques. The console.log() method can be used for simple debugging and to log output to the console. More complex debugging can be done using the debugging tools in modern web browsers, which allow you to set breakpoints, step through code, and inspect variables.

Marcello La RoccaMarcello La Rocca
View Author

I'm a full stack engineer with a passion for Algorithms and Machine Learning, and a soft spot for Python and JavaScript. I love coding as much as learning, and I enjoy trying new languages and patterns.

angularColinIevent handlingRactive
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week