Writing the Perfect jQuery Plugin

jQuery is an excellent DOM abstraction, but compared to other libraries, it leaves much to desire towards building complex, reusable functionality. There’s lots of good examples and resources, but most of them fall short in providing a pattern that is:

  • Extensible – the plugin is able to add new features or derive new functionality from old functionality.
  • Organised – the plugin is structured without a lot of anonymous functions
  • Destroyable – the plugin is able to remove the plugin without lingering side effects
  • Deterministic – find out what’s going on where.

1. Problem Plugins

There are serious problem with how many people build jQuery widgets. The following could be a first cut of a context menu that shows content in an element and removes it when someone clicks away.

This code might be fine for now, but what if you wanted to:

  • Remove the plugin? How are you going to remove the event listener on the document? It’s going to keep el in memory until the page refreshes.
  • Change show and hide to fade in and out.
  • If you saw this happening, how could you find this code?

2. The Perfect Pattern

To illustrate the perfect-plugin-pattern, I’ll use a basic tabs widget that we’ll extend to a history tabs. Lets start with what a first attempt at a Tabs might be:

I’m using as an example a simple tabs that we will extend to a history tabs.

You can see this “Simple Tab” at the top of the example page.

Although we’ll add an additional 150 lines it takes to make this the ‘perfect’ plugin. At the end of this article, I’ll show you how to get this back down to 20 lines and still be perfect.

3. Extensible

We want to make a history enabled tabs with our plugin. So, we should start by making our base tabs widget extendable.

There’s a lot of techniques out there for extending JavaScript objects. But, JavaScript provides us a fast and preferred technique – prototypal inheritance.

The first thing we’ll do is make a tabs constructor function that we can use. I’ll namespace it with my company name so there are no conflicts:

Now I’ll create a skeleton of the history tabs widget. I’ll make the HistoryTabs extend the base Tabs widget.

And, I’ll use this handy little plugin creator to turn this class into a jQuery plugin:

I can use pluginMaker to turn Jptr.Tabs and Jptr.HistoryTabs into jQuery widgets like:

This allows us to add tabs to an element like:

And call methods on it like:

So, we now have two extendable classes, that we’ve turned into jQuery plugins. Our classes don’t do anything yet, but that’s ok, we’ll take care of that later.

4. Deterministic

It’s great if we know, just by looking at the DOM, which objects are controlling which elements. To help with this, we’ll:

  • Save a reference to the element on the widget
  • Save the plugin instance in the element’s data
  • Add the name of the widget to the element

Jptr.Tabs init method now looks like:

This makes it much easier to debug our widget. Just by looking at the html, we can see which widgets are where. If we want more information on the widget, we can just do:

To get our widget back.

Finally, if we get a widget, we know where to look to see which element the widget is on (ie, widget.element).

5. Destroyable

For big apps, it’s important to let multiple plugins to operate on the same element or elements. This is especially needed for behavioral or event-oriented plugins.

For this to work, you need to be able to add and remove plugins on the same element without affecting the element or the other plugins.

Unfotunately, most jQuery plugins expect you to remove the element entirely to teardown the plugin. But what if you want to teardown (ie, destroy) the plugin without removing the element?

With most plugins, to teardown the plugin, you simply have to remove its event handlers. So, the tricky part is knowing when to remove the functionality.

You need to be able to remove a plugin both programatically and when the element it operates on is removed.

We’ll listen for a ‘destroyed’ event that happens when an element is removed from the page via the jQuery modifiers: .remove, .html, etc. This will call our teardown method.

We’ll also add a destroy function that removes the event handlers and calls teardown.

Our Tabs widget becomes:

Phew, this is a lot of code, but it’s worth it. We made sure that our widgets clean themselves up when their element is removed from the page. Further, we made it so we can remove the widget programatically like:

6. Organised

Now we just have to add our functionality back in. Tabs now looks like:

Notice how functions are clearly labeled and are not in anonymous functions! Although longer, this code is much more readable.

7. Extensible Cont

Finally, we want to make our history tab. The code looks like:

Notice how easy it is to convert a normal tabs to a history enabled tabs. Of course, inheritence isn’t necessarily the best pattern, but sometimes it is. The “perfect-plugin-pattern” gives you inheritence by default. Use it or don’t. Don’t cost nothin.

Also notice how this tabs widget will unbind the window hashchange event handler if the element is removed, or the plugin is destroyed.

8. Widget Factories

This pattern is VERY similar to jQueryUI’s widget and JavaScriptMVC’s controller. They both provide extendable, deterministic, destroyable widgets. But controller has one (in our opinion) critical advantage – it will automatically unbind event handlers.

This allows a tab’s widget with controller to look like:

Controller recognizes function names like “li click” and will automatically unbind them when the controller is destroyed.

9. Conclusions

I believe in widget factories, and it’s disappointing that they aren’t used more in third party jQuery plugins. Our hope is that articles like this can highlight their importance both by showing the necessity of the features they provide and how cumbersome it is to do it yourself.

Regardless of your choice of ‘widget factory’, it’s important think about the characteristics that practically every jQuery widget should have.

If you need the destroyed event, you can find it on jQuery++’s site.

If you’re looking for our implementation of this pattern, you can find it in CanJS’s can.Control.

Resource

Leave a Reply

Your email address will not be published. Required fields are marked *

CAPTCHA