3 min read

Modularity in jQuery: Part II

Last time, we talked about using event bindings to provide a publish/subscribe mechanism for your plugins, and introduce modularity via the event system.

This time, we'll talk about two new features added since jQuery 1.2: Setup and teardown for events and the element data store. First up, the element data store:

Prior to jQuery 1.2, data was added to elements via expandos. If, for instance, d was an element, and we wanted to add some data named "foo" with the value "bar", we would do d.foo = "bar". Simple enough. But there are two major issues:

  • Other libraries, plugins, or widgets might try and use the foo name. We could try and obfuscate the name (jQuery 1.0 used $events as the expando name for the events array on an element), but there was still a chance that another library could accidentally use the same name. In fact, that's exactly what happened. MooTools inadvertently used $events, and people who tried to use both libraries got hosed.
  • IE's garbage collection gets all flummoxed under certain circumstances when an element is removed but its expandos still have references to live objects.

The solution, which jQuery adopted for 1.2, is to create a new data store that's independent of the element and in the jQuery namespace, and which jQuery garbage collects itself when you remove the associated elements. This provides a mechanism for tagging elements with information that can then be used by different modules in your program. While jQuery uses it to track which events belong to which elements, it can also be used for other purposes.

For instance, the jQuery metadata plugin allows you to specify data to be stored in the data store in your markup. Here's an example:

The markup

``` <div data="{foo: 'bar'}">Some div element is here</div> ```

The code

``` $.metadata.setType("attr", "data"); $("div").metadata().foo == "bar" ```

The metadata plugin produces a thin sheen over the data-store, allowing you to pass information into the store via markup. That might be quite useful for plugins, to allow your users to specify options for your plugin without needing to write any special code. For instance, you could write this code in your plugin:

Markup

``` <div class="myPlugin" data="{myPlugin: {option1: 12, option2: "stringyString"}}">Some Text</div> ```

The Code

``` $.metadata.setType("attr", "data"); jQuery(function($) { $("div.myPlugin").each(function() { $(this).myPlugin($(this).metadata().myPlugin); }); }); ```

This is assuming, of course, that there is some $.fn.myPlugin that takes option1 and option2 as options. This technique allows you to put the initialization code in your plugin itself and let the user interact with it in fairly high-level ways via markup. The TableSorter plugin makes use of this technique.

But you can use the data store for other purposes as well. In particular, you can interact directly with the data store by doing $.data(el, "key", "value") to set a key in the store, or $.data(el, "key") to retrieve a key. jQuery 1.2.3 will also add a new shortcut (which we'll discuss in more length in the next post in this series), which will allow $("div").data("key").

I use this technique in the autocomplete plugin to tag a keystroke as "final" (hitting escape, for instance, should not trigger a new dropdown if it's used to cancel the autocomplete). Even though the autocompleter is in a separate module from the event code that triggers the autocompleter, this information can be tagged onto the element so it's accessible from any module.

Setup and Teardown

Setup and teardown lets you further customize event binding. In effect, setup and teardown functions are called immediately before an event is bound (and immediately before an event is unbound), allowing you to preform processing. Returning true from the setup function causes it to get bound using the browser's native handlers, instead of the jQuery event system (don't worry about this; it's extremely rare). Let's take a hypothetical activate.myPlugin event.

The Code

``` $.event.special["activate.myPlugin] = function() { /* "this" is the element being bound */ /* do some processing */; } ```

Later, when you do $("div").bind("activate.myPlugin") the special method will get run. This allows your plugin's users to add some processing to when your plugin gets bound to an element, without having to override the plugin binding method.

Next up: New features that will be introduced in 1.2.3, and bringing this all together :)