Close and Go BackBack to Viget

Single-Use jQuery Plugins

David Eisinger
David Eisinger, Web Developer, July 16, 2009 6

One of the best features of jQuery is its simple, powerful plugin system. The most obvious reason to write a plugin is the same reason you’d create a Rails plugin: to package up functionality for reuse. While code reuse between projects is certainly a worthy goal, it sets a prohibitively high bar when deciding whether or not to pull a piece of functionality into a plugin.

There are a number of good reasons to create jQuery plugins for behavior specific to the app under development. Consider the following example, a simple plugin to create form fields for an arbitrary number of nested resources, adapted from a recent project:

(function($) {
  $.fn.cloneableFields = function() {
    return this.each(function() {
      var container = $(this);
      var fields    = container.find("fieldset:last");
      var label     = container.metadata().label || "Add";

      container.count = function() {
        return this.find("fieldset").size();
      };

      // If there are existing entries, hide the form fields by default

      if (container.count() > 1) {
        fields.hide();
      }

      // When link is clicked, add a new set of fields and set their keys to
      // the total number of fieldsets, e.g. instruction_attributes[5][name]

      var addLink = $("<a/>").text(label).click(function() {
        html = fields.html().replace(/\[\d+\]/g, "[" + container.count() + "]");
        $(this).before("<fieldset>" + html + "</fieldset>");
        return false;
      });

      container.append(addLink);
    });
  };
})(jQuery);

Cleaner Code

When I was first starting out with jQuery and unobtrusive JavaScript, I couldn’t believe how easy it was to hook into the DOM and add behavior. I ended up with monstrous application.js files consisting solely of giant $(document).ready() functions — exactly the kind of spaghetti code I switched to Ruby and Rails to avoid. A pre-refactoring version of SpeakerRate had one over 700 lines long.

By pulling this feature into a plugin, rather than some version of the above code in our $(document).ready() function, we can stash it in a separate file and replace it with a single line:

$("div.cloneable").cloneableFields();

Putting feature details into separate files turns our application.js into a high-level view of the behavior of the site.

State Maintenance

In JavaScript, functions created inside of other functions maintain a link to variables declared in the outer function. In the above example, we create variables called container and fields when the page is loaded, and then access those variables in the click() handler of the inserted link. This way, we can avoid performing potentially expensive jQuery selectors every time an event is fired.

Right now, you might be thinking, “But David, isn’t $(document).ready() also a function? Shouldn’t this same principle apply?” Yes and no, dear reader. Variables declared in $(document).ready() can be accessed by functions declared there, but since it’s only called once, there will only be one copy of those variables for the page. By using the standard return this.each() plugin pattern, we ensure that there will be a copy of our variables for each selector match, so that we can have multiple sets of CloneableFields on a single page.

Faster Scripts

Aside from being able to store the results of selectors in variables, there are other performance gains to be had by containing your features in plugins. If a behavior involves attaching event listeners to five different DOM elements, rather than running selectors to search for each of these elements when the page loads, we’ll get better performance by searching for a containing element and then invoking our plugin on it, since we’ll only have to make one call on pages that don’t have the feature. Furthermore, inside your plugin, you’ll be more inclined to scope your selectors properly, further increasing performance.

If you opt to put your features into separate files, make sure compress all your JavaScript into one file in production to reduce the number of HTTP requests.

Conclusion

As Rubyists, the reasons to package up jQuery features follow many of the ideas to which we already subscribe: DRY, separation of concerns, and idiomatic code. Using jQuery plugins is by no means the only way to achieve clean JavaScript; the April edition of JSMag has a great article about containing features within object literals, a more framework-agnostic approach. Whatever method you choose, do something to avoid the novel-length $(document).ready() function. Your future self will thank you for it.

Jacek Becela said on 07/16 at 01:35 PM

We do exactly the same and it feels right.

Eli Van Zoeren said on 07/16 at 01:55 PM

I’m glad I’m not crazy for creating tons of plugins to do very limited & non-reusable tasks! Usually I keep them all in the same file for speed, and let $(document).ready() serve as the “table of contents” to the rest of the file.

I actually touched on this subject (in more philosophical and less technical terms) in a recent blog post: http://www.newmediacampaigns.com/page/write-mini-plugins

David Eisinger said on 07/16 at 04:57 PM

@Jacek: that’s really the #1 reason — writing jQuery this way is going with the grain of the framework.

@Eli: excellent article. Glad we’re on the same page :)

Jacek Becela said on 07/16 at 05:18 PM

“grain of the framework” is the key in my opinion. Problems arising in the area of a single DOM tree can be easily solved using small, fine-grained plugins.

And it’s surprising that even complex UI stuff can be built upon a bunch of tiny single-purposed plugins - all because of the ultra simple plugin interface in jQuery and few other things.

Michael Kimsal said on 07/16 at 07:37 PM

Hey David, thanks for the mention of JSMag :)

Doug Avery said on 07/17 at 08:15 AM

Since the early SpeakerRate stuff, I’ve taken to doing the same thing, as well as using object literals to store sets of related queries. Aside from the benefits listed, there’s just something calming about seeing those chainable little functions broken down to their most useful, reusable core...like Jacek said, it just feels right.

Commenting is not available in this weblog entry.

Next entry: Google Maps the jQuery Plugin Way

Previous entry: Hackday Recap

We're the Developers

at Viget Labs. We write about web development trends, tips, best practices, industry events, and our projects — all with an emphasis on Ruby on Rails.

Contact Us

Have any questions, comments, ideas, or secrets to share? Let us know.


What is the third letter in apple?

Sorry, you need to have Javascript enabled to use this form. (Don't blame us, blame the spammers!) If you'd like to contact us, please visit our Contact page.