Event-driven application design with JavaScript

Look beyond jQuery to an MV* approach

When you start building dashboards for interacting with data, such as calculators, editors, or result browsers, understanding JavaScript and client-side MVC becomes important. Why do you need an event-driven application design and a separation of interface state and behavior? Let me walk you through some examples.

You’ve probably seen seen simple example editors, where the browser acts as an editor for to-do lists. In these applications, you edit to-do items, consisting of a text and a state (pending or done). These small editors are very helpful for studying monitoring events from the keyboard and partially updating page content. These are the main principles for building applications in web browsers.

Once you going beyond to-do list editors, the requirements quickly increase. For example, you might work with multiple counters that observe the cursor position, or the number of words in a text box. You might need to support multiple editing modes or text formatters, or edit actions and state synchronization with a backend.

Even if you are not building an advanced editor in a web browser, but more a reader or browser for documents, why would you want to know about the MVC pattern? Client-side MVC is often important for these cases as well. For example, in web applications that render search results, there are often opportunities for microinteractions. If you render a collection of search results, users might want to save items to a “wishlist” or mark results as “favorites”. These actions require saving tiny bits of information on the server and locally updating DOM nodes depending on the outcome of the action. Also, when a user requests details of an item, you might want to save the search state in the browser, and maybe items in a shopping cart.

The jQuery Option

So, assuming you want to hook into browser events and state of a web browser, how do you start building the application? One option, and a good one, would be to start out with jQuery. There are a number of reasons that speak for jQuery:

  • jQuery allows us to easily observe events and changes such as clicks and inputs
  • jQuery supports a number of strategies to replace HTML on a page accordingly
  • jQuery comes with support for Ajax and supports “old”, but widely used browsers such as IE8 out of the box
  • jQuery has a rich ecosystem with many plugins and example codebases.

But there are a number of problems with jQuery too. Combining and rearranging jQuery building blocks can quickly become messy. Plugins and components in jQuery are far from being Lego blocks. In particular, managing the number of callbacks or custom contexts to use a plugin quickly explode. Also, mocking an environment suitable for application test would be difficult with pure jQuery.

Looking Beyond jQuery

Once an application grows beyond a certain size, or when you are starting out with a single page application right from the beginning, you quickly will benefit from two strategies:

  1. The Observer pattern. With jQuery’s on method, you can subscribe to events from DOM nodes. Yet, instead of coupling all kinds of logic to an event, you can build notifiers to decouple a user interface from state changes. Many MV* frameworks directly descend from the observer pattern.
  2. You can represent application state with custom objects. This is important because the number of events can quickly explode. Instead of having many similar events triggering state changes, you can map states to variables that orchestrate and update different places in the application. There are different opinions on how to represent application state across the MV* frameworks however. In a pure form, the JavaScript object notation offers an elegant solution to store values under arbitrary keys and to track browser state.

Frameworks to the Rescue

Both the observer pattern and the representation of application state are implemented by front-end MV* libraries and frameworks. Libraries such as Backbone.js are special in that sense however, that they are unopinionated and provide freedom in tuning an application to your custom needs.

With Backbone.js, you can use Backbone components as you need them. For example, you can use Backbone.Views to observe events from users. Or, you can update the state of Backbone.Models and Backbone.Collections directly from jQuery callbacks. Or, you can use models and collection to fetch remote data and update nodes in the DOM accordingly.

Minimizing coupling between components is not only very scalable, but has a number of additional benefits. You have single places for extending state or behavior, you can re-use business logic across multiple interfaces, and the interface becomes better testable, since you can easily mock dependencies such as data or user events.

Once you render your interfaces from within JavaScript, you can also start thinking about reusing JavaScript environments across servers and browsers. Pioneering work in this area is currently done with Airbnb’s Rendr.js project, and Facebook/Instagram’s React.js. Whereas Rendr embeds JavaScript in advanced HandlebarsJS templates, React.js generates HTML by transformation rules. Last but not least, both approaches for dealing with views can be nicely combined with plain old Backbone.Models and Collections to track and update application state.

All these benefits don’t come for free unfortunately. Before you can put front-end MV* to use, you most likely will encounter some questions around JavaScript build tools that support modes for development, test, and production. You also will meet new ideas about deployment and hosting of front-end assets, which might range from extremely simplistic to very advanced integration within server-side frameworks. These ideas are currently influenced by different opinions on JavaScript module loaders, and the asynchronous nature of JavaScript.

As the web landscape is currently disrupted by JavaScript, the ideas from front-end MV* can also be an investment for understanding the role of JavaScript to develop backends and APIs. Abstractions around key/value data structures and collections can give you a head start into functional programming (as Michael Fogus explains in his book Functional JavaScript). Some ideas of Backbone.Collections are diffusing into libraries for NoSQL data stores such as ArangodDB.Foxx and Everplay’s Serverbone.js. The concept of Promises can open doors into parallel computations with a minimum overhead.

tags: , , ,