3 min read

Modularity in jQuery

There have recently been several comments along the lines that jQuery is not very modular, once you get past modifying DOM elements. I get why people are saying that, but there's been a lot of features added to jQuery over the past year or so that provide a compelling architecture for building modular apps.

It's not exactly OO, which is what a lot of people are used to; it's more like a publish/subscribe model, with points of modularity added via message passing. That's because jQuery provides a pretty kick-ass system for custom events (events that are not built-into the browser), including a mechanism to subscribe to such events and a mechanism to trigger (i.e. publish) the events, with custom data.

It also provides the potential for setup and teardown for both browser and custom events, as well as a central data store for persistent data that would traditionally be attached directly to an element. This solves both the memory-leak issue (jQuery manages garbage collection of objects in the store that are no longer valid because the DOM element doesn't exist anymore) as well as a crucial compatibility issue (we don't store the data in the element itself, so we work fine with other libraries that do, or other libraries that have their own store in their own namespace).

Events

If you use jQuery, you already know about browser events:

$("div").click(function() { /* code here */ });

That's a shortcut for:

$("div").bind("click", function() { /* code here */ });

What you probably don't know is that you can also bind your own custom events:

$("div").bind("clickeroo", function() { /* code here */ });

That probably doesn't seem all that useful until you realize you can also trigger these custom events:

$("div").trigger("clickeroo")

or even:

$("div").bind("clickeroo", function(e, data1, data2) {
  // some stuff that uses data
});
$("div").trigger("clickeroo", [dataObject1, dataObject2]);

And finally, you can bind custom events with namespaces:

$("div").bind("activate.dialog", function(event, options) {
  // some stuff that uses options
});

Triggering activate or activate.dialog will trigger the event, and because all JS arguments are optional, the options argument will work whether or not the core event was triggered with the additional data.

Where does this bring us?

So I claimed earlier that this provides us with a useful tool for modularity. If you've been paying attention, and like me, have run into some modularity ceilings, you should be realizing some of the possibilities here. Effectively, what you have is a publish/subscribe model for events. If I create a dialog plugin, I don't have to have a massive options hash to provide a callback for the dialog opening, the dialog closing, the dialog resizing, etc. All I need to do is blindly trigger open.dialog, close.dialog, resize.dialog, etc. and allow the user to subscribe to those events if necessary. This even allows for meta-plugins that simply subscribe to events published by the main plugin and extend them. This is the approach I take in my autocomplete plugin, which publishes "autocomplete.activate", "autocomplete.cancel", and "autocomplete.selected" events.

My plugin also subscribes to the "updateList.autocomplete" event, which allows outside code to provide a list for the autocompleter at any time, without having to know how the internal autocomplete code works. In fact, the Ajax extension to the autocomplete plugin simply registers a callback for the call to getList that the plugin calls, which publishes an updateList.autocomplete event with the appropriate data when done.

In the end, this allows you to create self-contained modules in your application that communication with each other by providing public publish and subscribe points for interaction. In the case of the autocomplete plugin, all the events are published onto the input element, and external interfaces subscribe to events published to that element. This allows multiple autocomplete plugins on a single page that don't need to use a single message bus. However, for more global events, you can publish/subscribe to/from the window or document object, which can provide a more global event bus.

Next time, I'll talk about the setup and teardown methods for custom events, as well as using the element data store to store data about an element that needs to be shared between modules.