At the Converge SE conference this April in Columbia, SC, I had the pleasure of listening to Jed Schneider of Mode Set discuss untangling jQuery from the DOM using object-oriented best practices and patterns. The biggest takeaway for me was Mode Set's Utensils component library, specifically how their Bindable utility helps to decouple the components' HTML markup from the behavior driven by JavaScript. I would now like to show you why this separation is important to having high-quality, maintainable code and how to apply it to your own projects.

Mode Set wrote Utensils specifically for use with the Ruby on Rails Asset Pipeline. We will be a bit more agnostic in terms of the platform, but I will implement the examples in CoffeeScript. If you are unfamiliar with CoffeeScript (you should definitely read through the docs!) or would like to see the compiled JavaScript, check out the Gist for this article.

Why even bother?

The vast majority of websites have simple enough requirements that abstraction would be overkill. Marketing sites typically require only a few decorations applied to the DOM such as slideshows, accordions or simple AJAX forms. These can be handled using typical procedural jQuery calls:

At this scale, the tight coupling between the jQuery and the DOM is manageable; your file is no more than one or two hundred lines, maybe even smaller. You may even benefit from ditching the app.js file altogether and – dare I say it? – adding the script inline before the close of body. One less HTTP request; improved load times!

Before I continue, what do I mean by tight coupling? Tight coupling is an anti-pattern in object-oriented programming (OOP) where an object has too much knowledge about how one of its dependencies is implemented. In the above example, it is assumed that, somewhere in the markup, there is an element with a class of slides. Again, not a big deal in this case. But suppose we're working on a large web application …

jQuery is great at what it does, but it should not be treated like a framework for larger projects. Many JavaScript developers (myself included) have fallen into the trap of using jQuery as the core foundation of their applications. Over time, your codebase will grow from a few hundred lines of decently manageable jQuery calls to tens of thousands of lines across dozens of files that might look something like this:

This script is entirely dependent on the markup of the application. Even making small changes to the HTML might have catastrophic side effects on the behavior of the application that would require wading through all these event bindings and callback inception to figure out where things are acting up. This format also does not separate concerns; why are dialog box bindings with list event callbacks? Making changes, adding features or managing dependencies quickly become painful tasks. And how would you go about testing your application and its components? Test/Behavior-driven development is nearly impossible with an application in this state.

Let's clean this up by breaking out a single component of the application, decoupling it from the DOM and using the Bindable utility to set up the behavior.

The Accordion Component

A common component on many websites and web applications is the accordion. Content is hidden beneath a header that, when clicked, reveals the content below it, usually with a collapsing animation. Today, we will be looking at the markup for two such accordions: a list of browsers and a list of the CNP web team.

In terms of functionality, we would like both lists to behave as an accordion. For the browser list, we want to allow multiple items to be open at the same time in order to compare the contents. For the web team, because the content will be long bios, we just want one item to be open at a time. Also, for some very good reason(s?), the browsers will have a class of active toggled on their element while the web team will toggle on. Very, very good reasons … Our first pass might look this:

It works! Looks like we're done here. It's not the most beautiful thing, but it does the job, and that's what counts, right? For now … but, suppose down the road we need to add in support for another accordion list. This particular one should behave like the browser list but have a class of on toggled like the web team list. Also, our front-end designer has moved away from using IDs to style and wants all of them to be removed from the markup. By Friday! Oh, and we forgot to write tests for this thing! You do write tests, don't you …?!

While the example might be ridiculous, the likelihood of these types of changes cropping up are high. The current implementation is very tightly coupled with the markup (and business logic) of the site. Just these three requests will have us rewriting the entire functionality. How can we fix this?

Using The Bindable Utility

Mode Set's Utensils library comes with the Bindable utility (source: CoffeeScript | JavaScript) with the following description from their documentation:

Light weight dependency injection for client-side components. Looks for all DOM elements with a data-bindable attribute, stores references within a registry and instantiates their respective classes.

Classes registering with Bindable are passed the existing element converted to a jQuery object.

What exactly does that mean for us? Here are the steps we will take to implement the accordion using Bindable:

  1. We will write the accordion functionality as a class (in the CoffeeScript sense) that takes an element in its constructor and applies the functionality without knowing about the rest of the DOM.
  2. This class is then registered with the Bindable utility to listen for any elements with the data-bindable="accordion" attribute.
  3. Finally, on page load, we call Bindable.bindAll(), and every element on the page with that attribute will have the functionality applied to it.

How exactly does this work? The short answer: dependency injection, which is a fancy way of saying that the logic for the accordion will no longer depend on the actual DOM. The long answer: to check out the nitty-gritty of Bindable, I would suggest looking at the source. To appreciate it, however, all we need is an example. Let's rewrite the accordion now:

Stepping through the Accordion class, we can see how it all works. For the uninitiated, CoffeeScript classes are just a pretty wrapper around JavaScript's prototypal inheritance pattern. The constructor() method receives an el (which will be a jQuery Object) that will have the functionality applied to it.

applyOptions() takes the passed element and parses any data attributes on it; in this case, it's looking for data-toggle="..." and data-behavior="...". The toggle attribute defines the class to be, well, toggled on the selected item (defaults to “active”). Likewise, the behavior attribute defines how the items will actually toggle, either as “radio” elements (one item selected at a time) or “checkbox” elements (multiple items selected at a time).

The eventBindings() method binds the click event to el, firing off toggle() with the targeted item. This method uses jQuery's .on() syntax, which delegates clicks on the li elements to the parent el.

On click, toggle() checks if the behavior option is set to “radio” and, if so, removes the toggle class from all li elements. It then toggles that class on the passed item.

Notice how Accordion makes no reference to the DOM, and makes no assumptions about how each list should function. The only thing it expects is that you have a collection of li elements that will toggle classes based on data attributes applied to the container el. Taking this even further, the li requirement can be abstracted out into a data attribute, data-target="...", with a default of “li.” Now Accordion can be used with any container and child elements!

Lastly, the Accordion class is registered with Bindable. This tells the utility when it encounters data-bindable="accordion" on an element to pass that element to an instance of the Accordion class as a jQuery object. And that's it!

How will this change the markup?

While the new syntax might be more verbose, this method of applying the accordion logic to the lists is much more flexible. You can change the behavior, replace the toggled class and add/remove new lists without ever touching the JavaScript. Also, now that Accordion is independent of the markup, it can be reused anywhere and across multiple projects. Never repeat yourself again! Even better, this method frees up the id and class attributes to be used only for styling, leaving our dogmatic front-end designer to use whatever convention suits 'em!

Of course, right now nothing would happen if we load the page. We haven't bound the DOM elements to their associated classes yet. Assuming we use a utility like CodeKit, Grunt, Yeoman, etc., we would be concatenating all the required javascript into one file and minifying it before serving it on the page. With just one more line, all accordians will be wired up as well as any other components we defined using the Bindable utility:

That's it! Bindable takes care of linking up the lists with the Accordion class. If a bindable element is added to the DOM after the initial bindAll(), we would need to call bindable.bind(el) to apply the functionality. Painless! As they say, demo or it didn't happen:

Check out this Pen!

Moral of the story?

For small projects with minimal JavaScript, abstraction of this nature is unnecessary, but as soon as the project grows to more than a few hundred lines with many moving parts, managing the code and its relationship to the markup becomes exponentially more difficult. Implementing changes, new features and bug fixes might have unintended side effects that slow down development and produce serious code smell. By decoupling the DOM from the JavaScript and using dependency injection to associate functionality with the elements on the page, we can easily improve the quality of our code and make it more flexible, testable and reusable across projects.

The Bindable utility provided with Mode Set's Utensils library simplifies using this technique with your own code. As you saw in the example above, it allows you to quickly and easily bind functionality to elements in a clean, customizable way. Give this pattern a shot on your next project, and see how it helps improve the quality of your code!

Photograph by David (DaveOnFlickr). Used with permission under the Creative Commons license.