A Few jQuery Plugin Patterns
Doug Avery, Former Senior Developer
Article Category:
Posted on
One of jQuery’s great strengths is its simple and powerful plugin architecture. The extensible .fn method has been one key to jQuery’s success, it lets developers of any experience level quickly produce and distribute their work, and a lot of devs who work with jQuery use plugins and their patterns every day.
Plugins don’t have many hard rules, so it shouldn’t surprise anyone to find out that there’s a huge diversity in implementation styles. Still, if you look at enough plugins, a few patterns useful patterns emerge.
Here are five patterns, each with their own pros and cons, applied to a simple gallery behavior. If you’re already using some of these (or prefer even better ones), let me know in the comments. A few points about all five patterns: I’m wrapping the plugin code in an immediate (or ‘self-executing’) function to create a closure and assign the $ to jQuery. I’m also setting options the standard way, combining a passed-in object with a set of defaults. You play around with the gallery I’m trying to make here.
In-Loop Definition
return this.each( function(){ ... var $this = $( this ); $this.find( '.gallery-next' ).bind( 'click', next ); function next( event ){ ... }; } );
One of the simplest patterns, this one defines all the methods inside the $.each loop. This one’s really easy to work with when your plugin is still simple; all of your methods access variables inside the loop’s scope, so there’s no annoying variable-passing to deal with.
There are a few downsides, though. You don’t have any public methods, so other scripts and tests will have a much harder time advancing the gallery. The other issue is that if you’re using a method like this on a lot of elements, you might take a slight memory hit — since the internal methods reference vars from the scope above theirs, they create closures when they’re executed, saving up potentially hundreds of variable states.
Deep Namespace
$.fn.deep_namespace = function( options ){ ... }; $.fn.deep_namespace.next = function( $this, settings ){ ... };
An interesting way to move methods out of the loop and make them more accessible to developers. With ‘deep namespace’, we add methods to the original method object, allowing access through fn.gallery.methodName(). Unfortunately, it’s just a storage solution, and doesn’t solve our other problems. For example, even though a method like gallery.prev() is public, it’s useless until you pass it just the right arguments — arguments which are a pain to reconstruct after the initial call.
Public Access with Strings
var methods = { next : function( $this ){ ... }, ... }; $.fn.string_driven = function( options_or_method ){ return this.each(function(){ if ( methods[ options_or_method ] ) { return methods[ method ].apply( this, $( this ) ); } else { $.extend( settings, options_or_method ); } ... }); };
jQuery’s docs suggest this pattern for providing public access to otherwise private methods. Here, devs passing in a string like ‘prev’ can trigger the prev() method easily, and even pass it arguments. This is a really clever solution to the problem; it keeps as much as possible private and exposes exactly what we need.
I dislike the amount of argument passing involved; it might be a personal preference, but the idea of threading basic arguments like $this through my code feels icky. I feel like methods should KNOW what the root element is at any given time.
Event-driven
$.fn.event_driven = function( options ){ return this.each(function(){ ... $this.bind({ 'next' : next, 'prev' : prev }); $this.find( '.gallery-next' ).click(function( event ){ event.preventDefault(); $this.trigger( 'next' ); }); function next(){ ... }; });
Since jQuery plugins primarily run on DOM elements and work closely with events, the trigger() and bind() methods seem like a natural way to handle plugin behavior. With trigger, you’re creating a standard interface for your feature that any jQuery dev will understand and have no problems using. Trigger can elegantly handle extra parameters, which makes it really powerful as part of a pattern. The downside to this pattern is that events add a little extra complication — little issues like bubbling, unsetting, stacking that might add up in a bigger plugin.
Object-driven
$.fn.object_driven = function( options, callback ){ var objects = [], return_data; this.each( function(){ var object; if ( $( this ).data( 'object' ) === undefined ) { object = {}; object.$this = $( this ); object.next = function( event ){ ... }; object.$this.data( 'object', object ); } else { object = $( this ).data( 'object' ); } objects.push( object ); } ); if ( objects.length === 1 ) { return_data = objects[ 0 ]; } else { return_data = objects; } return_data.all = function( callback ){ $.each( objects, function( i ){ callback.call( this, i ); } ); }; return return_data; };
The object-driven pattern applies a little OO thinking while rooted to a DOM element like a traditional plugin. It builds an object — complete with its own set of methods, options, and elements — and returns it in a way that users can easily access its properties. As an added bonus, the object saves itself in the "data" property of a DOM element, so developers can access it later without needing to call the original plugin method. It even adds a .all() convenience method to the return that loops through multiple objects and applies a callback to them, similar to jQuery’s $.each.
The downside of object-driven is the added complexity. The extra code is kind of annoying, and since you’re using 'this' inside the object scope, you’ll need to use jQuery.proxy on all your event handlers to keep them from setting 'this' to the DOM element instead. Possibly more hassle than it’s worth. When I use this method, I usually move the framework code out into a method called $.as(), which cuts down on the repetition needed. Then I can call plugin code like so:
$( '.gallery' ).as( APPNAME.gallery ); $( '.gallery' ).as().next(); $( '.gallery' ).as().all(function(i){ alert( 'Gallery ' + i + ' has ' + this.galleryCount + ' pictures.' ); });
You can view working examples of all five patterns here:
Download: VigetInspire_jQueryPatterns.zip
Some interesting examples of plugin architecture choices from the wild:
- Autocomplete - $.fn.extend to add methods, "new" to create objects
- Cycle - Defines functions inside the immediate function but outside the plugin method; requires lots of extra passing
- Colorbox - Built around a single DOM element and a strong division of private/public methods
- Uniform - Public functions in a $.uniform object, private functions kept inside the fn.uniform method