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
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 …
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
Light weight dependency injection for client-side components. Looks for all DOM elements with a
data-bindableattribute, stores references within a registry and instantiates their respective classes.
Classes registering with
Bindableare passed the existing element converted to a
What exactly does that mean for us? Here are the steps we will take to implement the accordion using
- 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.
- This class is then registered with the
Bindableutility to listen for any elements with the
- 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
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
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).
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
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
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!
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?
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
class attributes to be used only for styling, leaving our dogmatic front-end designer to use whatever convention suits 'em!
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:
Moral of the story?
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.