Filed under Javascript
I occasionally write about implementing design patterns (http://addyosmani.com/resources/essentialjsdesignpatterns/book/) in JavaScript. They’re an excellent way of building upon proven approaches to solving common development problems, and I think there’s a lot of benefit to using them. But while well-known JavaScript patterns are useful, another side of development could benefit from its own set of design patterns: jQuery plugins. The official jQuery plugin authoring guide (http://docs.jquery.com/Plugins/Authoring) offers a great starting point for getting into writing plugins and widgets, but let’s take it further.
Plugin development has evolved over the past few years. We no longer have just one way to write plugins, but many. In reality, certain patterns might work better for a particular problem or component than others.
Some developers may wish to use the jQuery UI widget factory (http://ajpiano.com/widgetfactory/); it’s great for complex, flexible UI components. Some may not. Some might like to structure their plugins more like modules (similar to the module pattern) or use a more formal module format such as AMD (asynchronous module definition) (https://github.com/amdjs/amdjs-api/wiki/AMD). Some might want their plugins to harness the power of prototypal inheritance. Some might want to use custom events or pub/sub to communicate from plugins to the rest of their app. And so on.
I began to think about plugin patterns after noticing a number of efforts to create a one-size-fits-all jQuery plugin boilerplate. While such a boilerplate is a great idea in theory, the reality is that we rarely write plugins in one fixed way, using a single pattern all the time.
Let’s assume that you’ve tried your hand at writing your own jQuery plugins at some point and you’re comfortable putting together something that works. It’s functional. It does what it needs to do, but perhaps you feel it could be structured better. Maybe it could be more flexible or could solve more issues. If this sounds familiar and you aren’t sure of the differences between many of the different jQuery plugin patterns, then you might find what I have to say helpful.
My advice won’t provide solutions to every possible pattern, but it will cover popular patterns that developers use in the wild.
Note: This post is targeted at intermediate to advanced developers. If you don’t feel you’re ready for this just yet, I’m happy to recommend the official jQuery Plugins/Authoring (http://docs.jquery.com/Plugins/Authoring) guide, Ben Alman’s plugin style guide (http://msdn.microsoft.com/en-us/scriptjunkie/ff696759) and Remy Sharp’s “Signs of a Poorly Written jQuery Plugin (http://remysharp.com/2010/06/03/signs-of-a-poorly-written-jquery-plugin/).”
Patterns
jQuery plugins have very few defined rules, which one of the reasons for the incredible diversity in how they’re implemented. At the most basic level, you can write a plugin simply by adding a new function property to jQuery’s $.fn object, as follows:
1 |
$.fn.myPluginName = function() { |
This is great for compactness, but the following would be a better foundation to build on:
2 |
$.fn.myPluginName = function() { |
Here, we’ve wrapped our plugin logic in an anonymous function. To ensure that our use of the $ sign as a shorthand creates no conflicts between jQuery and other JavaScript libraries, we simply pass it to this closure, which maps it to the dollar sign, thus ensuring that it can’t be affected by anything outside of its scope of execution.
An alternative way to write this pattern would be to use $.extend, which enables you to define multiple functions at once and which sometimes make more sense semantically:
We could do a lot more to improve on all of this; and the first complete pattern we’ll be looking at today, the lightweight pattern, covers some best practices that we can use for basic everyday plugin development and that takes into account common gotchas to look out for.
Some Quick Notes
You can find all of the patterns from this post in this GitHub repository (https://github.com/addyosmani/jquery-plugin-patterns/).
While most of the patterns below will be explained, I recommend reading through the comments in the code, because they will offer more insight into why certain practices are best.
I should also mention that none of this would be possible without the previous work, input and advice of other members of the jQuery community. I’ve listed them inline with each pattern so that you can read up on their individual work if interested.
A Lightweight Start
Let’s begin our look at patterns with something basic that follows best practices (including those in the jQuery plugin-authoring guide). This pattern is ideal for developers who are either new to plugin development or who just want to achieve something simple (such as a utility plugin). This lightweight start uses the following:
- Common best practices, such as a semi-colon before the function’s invocation;
window, document, undefined passed in as arguments; and adherence to the jQuery core style guidelines.
- A basic defaults object.
- A simple plugin constructor for logic related to the initial creation and the assignment of the element to work with.
- Extending the options with defaults.
- A lightweight wrapper around the constructor, which helps to avoid issues such as multiple instantiations.
11 |
;(function ( $, window, document, undefined ) { |
27 |
var pluginName = 'defaultPluginName', |
33 |
function Plugin( element, options ) { |
34 |
this.element = element; |
41 |
this.options = $.extend( {}, defaults, options) ; |
43 |
this._defaults = defaults; |
44 |
this._name = pluginName; |
49 |
Plugin.prototype.init = function () { |
58 |
$.fn[pluginName] = function ( options ) { |
59 |
return this.each(function () { |
60 |
if (!$.data(this, 'plugin_' + pluginName)) { |
61 |
$.data(this, 'plugin_' + pluginName, |
62 |
new Plugin( this, options )); |
67 |
})( jQuery, window, document ); |
Further Reading
“Complete” Widget Factory
While the authoring guide is a great introduction to plugin development, it doesn’t offer a great number of conveniences for obscuring away from common plumbing tasks that we have to deal with on a regular basis.
The jQuery UI Widget Factory is a solution to this problem that helps you build complex, stateful plugins based on object-oriented principles. It also eases communication with your plugin’s instance, obfuscating a number of the repetitive tasks that you would have to code when working with basic plugins.
In case you haven’t come across these before, stateful plugins keep track of their current state, also allowing you to change properties of the plugin after it has been initialized.
One of the great things about the Widget Factory is that the majority of the jQuery UI library actually uses it as a base for its components. This means that if you’re looking for further guidance on structure beyond this template, you won’t have to look beyond the jQuery UI repository.
Back to patterns. This jQuery UI boilerplate does the following:
- Covers almost all supported default methods, including triggering events.
- Includes comments for all of the methods used, so that you’re never unsure of where logic should fit in your plugin.
08 |
;(function ( $, window, document, undefined ) { |
16 |
$.widget( "namespace.widgetname" , { |
25 |
_create: function () { |
37 |
destroy: function () { |
42 |
$.Widget.prototype.destroy.call(this); |
48 |
methodB: function ( event ) { |
55 |
this._trigger('methodA', event, { |
60 |
methodA: function ( event ) { |
61 |
this._trigger('dataChanged', event, { |
68 |
_setOption: function ( key, value ) { |
80 |
$.Widget.prototype._setOption.apply( this, arguments ); |
86 |
})( jQuery, window, document ); |
Further Reading
Namespacing And Nested Namespacing
Namespacing your code is a way to avoid collisions with other objects and variables in the global namespace. They’re important because you want to safeguard your plugin from breaking in the event that another script on the page uses the same variable or plugin names as yours. As a good citizen of the global namespace, you must also do your best not to prevent other developers’ scripts from executing because of the same issues.
JavaScript doesn’t really have built-in support for namespaces as other languages do, but it does have objects that can be used to achieve a similar effect. Employing a top-level object as the name of your namespace, you can easily check for the existence of another object on the page with the same name. If such an object does not exist, then we define it; if it does exist, then we simply extend it with our plugin.
Objects (or, rather, object literals) can be used to create nested namespaces, such as namespace.subnamespace.pluginName and so on. But to keep things simple, the namespacing boilerplate below should give you everything you need to get started with these concepts.
13 |
$.myNamespace.myPluginName = function ( el, myFunctionParam, options ) { |
23 |
base.$el.data( "myNamespace.myPluginName" , base ); |
25 |
base.init = function () { |
26 |
base.myFunctionParam = myFunctionParam; |
28 |
base.options = $.extend({}, |
29 |
$.myNamespace.myPluginName.defaultOptions, options); |
42 |
$.myNamespace.myPluginName.defaultOptions = { |
46 |
$.fn.mynamespace_myPluginName = function |
47 |
( myFunctionParam, options ) { |
48 |
return this.each(function () { |
49 |
(new $.myNamespace.myPluginName(this, |
50 |
myFunctionParam, options)); |
Further Reading
Custom Events For Pub/Sub (With The Widget factory)
You may have used the Observer (Pub/Sub) pattern in the past to develop asynchronous JavaScript applications. The basic idea here is that elements will publish event notifications when something interesting occurs in your application. Other elements then subscribe to or listen for these events and respond accordingly. This results in the logic for your application being significantly more decoupled (which is always good).
In jQuery, we have this idea that custom events provide a built-in means to implement a publish and subscribe system that’s quite similar to the Observer pattern. So, bind('eventType') is functionally equivalent to performing subscribe('eventType'), and trigger('eventType') is roughly equivalent to publish('eventType').
Some developers might consider the jQuery event system as having too much overhead to be used as a publish and subscribe system, but it’s been architected to be both reliable and robust for most use cases. In the following jQuery UI widget factory template, we’ll implement a basic custom event-based pub/sub pattern that allows our plugin to subscribe to event notifications from the rest of our application, which publishes them.
14 |
;(function ( $, window, document, undefined ) { |
15 |
$.widget("ao.eventStatus", { |
20 |
_create : function() { |
26 |
self.element.bind( "myEventStart", function( e ) { |
27 |
console.log("event start"); |
31 |
self.element.bind( "myEventEnd", function( e ) { |
32 |
console.log("event end"); |
42 |
$.Widget.prototype.destroy.apply( this, arguments ); |
45 |
})( jQuery, window , document ); |
Further Reading
Prototypal Inheritance With The DOM-To-Object Bridge Pattern
In JavaScript, we don’t have the traditional notion of classes that you would find in other classical programming languages, but we do have prototypal inheritance. With prototypal inheritance, an object inherits from another object. And we can apply this concept to jQuery plugin development.
Alex Sexton (http://alexsexton.com/) and Scott Gonzalez (http://scottgonzalez.com/) have looked at this topic in detail. In sum, they found that for organized modular development, clearly separating the object that defines the logic for a plugin from the plugin-generation process itself can be beneficial. The benefit is that testing your plugin’s code becomes easier, and you can also adjust the way things work behind the scenes without altering the way that any object APIs you’ve implemented are used.
In Sexton’s previous post on this topic, he implements a bridge that enables you to attach your general logic to a particular plugin, which we’ve implemented in the template below. Another advantage of this pattern is that you don’t have to constantly repeat the same plugin initialization code, thus ensuring that the concepts behind DRY development are maintained. Some developers might also find this pattern easier to read than others.
11 |
init: function( options, elem ) { |
13 |
this.options = $.extend( {}, this.options, options ); |
32 |
myMethod: function( msg ){ |
40 |
if ( typeof Object.create !== 'function' ) { |
41 |
Object.create = function (o) { |
49 |
$.plugin = function( name, object ) { |
50 |
$.fn[name] = function( options ) { |
51 |
return this.each(function() { |
52 |
if ( ! $.data( this, name ) ) { |
53 |
$.data( this, name, Object.create(object).init( |
Further Reading
jQuery UI Widget Factory Bridge
If you liked the idea of generating plugins based on objects in the last design pattern, then you might be interested in a method found in the jQuery UI Widget Factory called $.widget.bridge. This bridge basically serves as a middle layer between a JavaScript object that is created using $.widget and jQuery’s API, providing a more built-in solution to achieving object-based plugin definition. Effectively, we’re able to create stateful plugins using a custom constructor.
Moreover, $.widget.bridge provides access to a number of other capabilities, including the following:
- Both public and private methods are handled as one would expect in classical OOP (i.e. public methods are exposed, while calls to private methods are not possible);
- Automatic protection against multiple initializations;
- Automatic generation of instances of a passed object, and storage of them within the selection’s internal
$.data cache;
- Options can be altered post-initialization.
For further information on how to use this pattern, look at the comments in the boilerplate below:
12 |
var widgetName = function( options, element ){ |
13 |
this.name = "myWidgetName"; |
14 |
this.options = options; |
15 |
this.element = element; |
20 |
widgetName.prototype = { |
39 |
option: function( key, value ){ |
45 |
if( $.isPlainObject( key ) ){ |
46 |
this.options = $.extend( true, this.options, key ); |
49 |
} else if ( key && typeof value === "undefined" ){ |
50 |
return this.options[ key ]; |
54 |
this.options[ key ] = value; |
64 |
publicFunction: function(){ |
65 |
console.log('public function'); |
69 |
_privateFunction: function(){ |
70 |
console.log('private function'); |
Further Reading
jQuery Mobile Widgets With The Widget factory
jQuery mobile is a framework that encourages the design of ubiquitous Web applications that work both on popular mobile devices and platforms and on the desktop. Rather than writing unique applications for each device or OS, you simply write the code once and it should ideally run on many of the A-, B- and C-grade browsers out there at the moment.
The fundamentals behind jQuery mobile can also be applied to plugin and widget development, as seen in some of the core jQuery mobile widgets used in the official library suite. What’s interesting here is that even though there are very small, subtle differences in writing a “mobile”-optimized widget, if you’re familiar with using the jQuery UI Widget Factory, you should be able to start writing these right away.
The mobile-optimized widget below has a number of interesting differences than the standard UI widget pattern we saw earlier:
$.mobile.widget is referenced as an existing widget prototype from which to inherit. For standard widgets, passing through any such prototype is unnecessary for basic development, but using this jQuery-mobile specific widget prototype provides internal access to further “options” formatting.
- You’ll notice in
_create() a guide on how the official jQuery mobile widgets handle element selection, opting for a role-based approach that better fits the jQM mark-up. This isn’t at all to say that standard selection isn’t recommended, only that this approach might make more sense given the structure of jQM pages.
- Guidelines are also provided in comment form for applying your plugin methods on
pagecreate as well as for selecting the plugin application via data roles and data attributes.
008 |
;(function ( $, window, document, undefined ) { |
012 |
$.widget( "mobile.widgetName", $.mobile.widget, { |
020 |
_create: function() { |
034 |
_dosomething: function(){ ... }, |
040 |
enable: function() { ... }, |
044 |
destroy: function () { |
048 |
$.Widget.prototype.destroy.call(this); |
053 |
methodB: function ( event ) { |
060 |
this._trigger('methodA', event, { |
065 |
methodA: function ( event ) { |
066 |
this._trigger('dataChanged', event, { |
072 |
_setOption: function ( key, value ) { |
084 |
$.Widget.prototype._setOption.apply(this, arguments); |
090 |
})( jQuery, window, document ); |
RequireJS And The jQuery UI Widget Factory
RequireJS is a script loader that provides a clean solution for encapsulating application logic inside manageable modules. It’s able to load modules in the correct order (through its order plugin); it simplifies the process of combining scripts via its excellent optimizer; and it provides the means for defining module dependencies on a per-module basis.
James Burke has written a comprehensive set of tutorials on getting started with RequireJS. But what if you’re already familiar with it and would like to wrap your jQuery UI widgets or plugins in a RequireJS-compatible module wrapper?.
In the boilerplate pattern below, we demonstrate how a compatible widget can be defined that does the following:
- Allows the definition of widget module dependencies, building on top of the previous jQuery UI boilerplate presented earlier;
- Demonstrates one approach to passing in HTML template assets for creating templated widgets with jQuery (in conjunction with the jQuery tmpl plugin) (View the comments in
_create().)
- Includes a quick tip on adjustments that you can make to your widget module if you wish to later pass it through the RequireJS optimizer
21 |
define("ao.myWidget", ["jquery", "text!templates/asset.html", "jquery-ui.custom.min","jquery.tmpl"], function ($, assetHtml) { |
25 |
$.widget( "ao.myWidget", { |
32 |
_create: function () { |
48 |
destroy: function () { |
52 |
$.Widget.prototype.destroy.call( this ); |
57 |
methodB: function ( event ) { |
62 |
this._trigger('methodA', event, { |
67 |
methodA: function ( event ) { |
68 |
this._trigger('dataChanged', event, { |
74 |
_setOption: function ( key, value ) { |
86 |
$.Widget.prototype._setOption.apply( this, arguments ); |
Further Reading
Globally And Per-Call Overridable Options (Best Options Pattern)
For our next pattern, we’ll look at an optimal approach to configuring options and defaults for your plugin. The way you’re probably familiar with defining plugin options is to pass through an object literal of defaults to $.extend, as demonstrated in our basic plugin boilerplate.
If, however, you’re working with a plugin with many customizable options that you would like users to be able to override either globally or on a per-call level, then you can structure things a little differently.
Instead, by referring to an options object defined within the plugin namespace explicitly (for example, $fn.pluginName.options) and merging this with any options passed through to the plugin when it is initially invoked, users have the option of either passing options through during plugin initialization or overriding options outside of the plugin (as demonstrated here).
08 |
;(function ( $, window, document, undefined ) { |
10 |
$.fn.pluginName = function ( options ) { |
20 |
options = $.extend( {}, $.fn.pluginName.options, options ); |
22 |
return this.each(function () { |
36 |
$.fn.pluginName.options = { |
39 |
myMethod: function ( elem, param ) { |
44 |
})( jQuery, window, document ); |
Further Reading
A Highly Configurable And Mutable Plugin
Like Alex Sexton’s pattern, the following logic for our plugin isn’t nested in a jQuery plugin itself. We instead define our plugin’s logic using a constructor and an object literal defined on its prototype, using jQuery for the actual instantiation of the plugin object.
Customization is taken to the next level by employing two little tricks, one of which you’ve seen in previous patterns:
- Options can be overridden both globally and per collection of elements;
- Options can be customized on a per-element level through HTML5 data attributes (as shown below). This facilitates plugin behavior that can be applied to a collection of elements but then customized inline without the need to instantiate each element with a different default value.
You don’t see the latter option in the wild too often, but it can be a significantly cleaner solution (as long as you don’t mind the inline approach). If you’re wondering where this could be useful, imagine writing a draggable plugin for a large set of elements. You could go about customizing their options like this:
2 |
$('.item-a').draggable({'defaultPosition':'top-left'}); |
3 |
$('.item-b').draggable({'defaultPosition':'bottom-right'}); |
4 |
$('.item-c').draggable({'defaultPosition':'bottom-left'}); |
But using our patterns inline approach, the following would be possible:
2 |
$('.items').draggable(); |
2 |
<li class="item" data-plugin-options='{"defaultPosition":"top-left"}'></div> |
3 |
<li class="item" data-plugin-options='{"defaultPosition":"bottom-left"}'></div> |
And so on. You may well have a preference for one of these approaches, but it is another potentially useful pattern to be aware of.
12 |
;(function( $, window, document, undefined ){ |
15 |
var Plugin = function( elem, options ){ |
18 |
this.options = options; |
24 |
this.metadata = this.$elem.data( 'plugin-options' ); |
30 |
message: 'Hello world!' |
36 |
this.config = $.extend({}, this.defaults, this.options, |
52 |
sampleMethod: function() { |
58 |
Plugin.defaults = Plugin.prototype.defaults; |
60 |
$.fn.plugin = function(options) { |
61 |
return this.each(function() { |
62 |
new Plugin(this, options).init(); |
68 |
})( jQuery, window , document ); |
Further Reading
AMD- And CommonJS-Compatible Modules
While many of the plugin and widget patterns presented above are acceptable for general use, they aren’t without their caveats. Some require jQuery or the jQuery UI Widget Factory to be present in order to function, while only a few could be easily adapted to work well as globally compatible modules both client-side and in other environments.
For this reason, a number of developers, including me, CDNjs (http://cdnjs.com) maintainer Thomas Davis (https://github.com/thomasdavis) and RP Florence (https://github.com/rpflorence), have been looking at both the AMD (https://github.com/amdjs/amdjs-api/wiki/AMD) (Asynchronous Module Definition) and CommonJS (http://wiki.commonjs.org/wiki/Modules) module specifications in the hopes of extending boilerplate plugin patterns to cleanly work with packages and dependencies. John Hann (http://twitter.com/unscriptable) and Kit Cambridge (https://gist.github.com/1251221) have also explored work in this area.
AMD
The AMD module format (a specification for defining modules where both the module and dependencies can be asynchronously loaded) has a number of distinct advantages, including being both asynchronous and highly flexible by nature, thus removing the tight coupling one commonly finds between code and module identity. It’s considered a reliable stepping stone to the module system (http://wiki.ecmascript.org/doku.php?id=harmony:modules) proposed for ES Harmony.
When working with anonymous modules, the idea of a module’s identity is DRY, making it trivial to avoid duplication of file names and code. Because the code is more portable, it can be easily moved to other locations without needing to alter the code itself. Developers can also run the same code in multiple environments just by using an AMD optimizer that works with a CommonJS environment, such as r.js.
With AMD, the two key concepts you need to be aware of are the require method and the define method, which facilitate module definition and dependency loading. The define method is used to define named or unnamed modules based on the specification, using the following signature:
1 |
define(module_id , [dependencies], definition function ); |
As you can tell from the inline comments, the module’s ID is an optional argument that is typically required only when non-AMD concatenation tools are being used (it could be useful in other edge cases, too). One of the benefits of opting not to use module IDs is having the flexibility to move your module around the file system without needing to change its ID. The module’s ID is equivalent to folder paths in simple packages and when not used in packages.
The dependencies argument represents an array of dependencies that are required by the module you are defining, and the third argument (factory) is a function that’s executed to instantiate your module. A barebones module could be defined as follows:
04 |
define('myModule', ['foo', 'bar'], function ( foo, bar ) { |
07 |
return function () {}; |
11 |
define('myModule', ['math', 'graph'], function ( math, graph ) { |
14 |
return graph.drawPie(math.randomGrid(x,y)); |
The require method, on the other hand, is typically used to load code in a top-level JavaScript file or in a module should you wish to dynamically fetch dependencies. Here is an example of its usage:
04 |
require(['foo', 'bar'], function ( foo, bar ) { |
11 |
define(function ( require ) { |
12 |
var isReady = false, foobar; |
14 |
require(['foo', 'bar'], function (foo, bar) { |
16 |
foobar = foo() + bar(); |
The above are trivial examples of just how useful AMD modules can be, but they should provide a foundation that helps you understand how they work. Many big visible applications and companies currently use AMD modules as a part of their architecture, including IBM (http://www.ibm.com) and the BBC iPlayer (http://www.bbc.co.uk/iplayer/). The specification has been discussed for well over a year in both the Dojo and CommonJS communities, so it’s had time to evolve and improve. For more reasons on why many developers are opting to use AMD modules in their applications, you may be interested in James Burke’s article “On Inventing JS Module Formats and Script Loaders (http://tagneto.blogspot.com/2011/04/on-inventing-js-module-formats-and.html).”
Shortly, we’ll look at writing globally compatible modules that work with AMD and other module formats and environments, something that offers even more power. Before that, we need to briefly discuss a related module format, one with a specification by CommonJS.
CommonJS
In case you’re not familiar with it, CommonJS (http://www.commonjs.org/) is a volunteer working group that designs, prototypes and standardizes JavaScript APIs. To date, it’s attempted to ratify standards for modules (http://www.commonjs.org/specs/modules/1.0/) and packages (http://wiki.commonjs.org/wiki/Packages/1.0). The CommonJS module proposal specifies a simple API for declaring modules server-side; but, as John Hann correctly states, there are really only two ways to use CommonJS modules in the browser: either wrap them or wrap them.
What this means is that we can either have the browser wrap modules (which can be a slow process) or at build time (which can be fast to execute in the browser but requires a build step).
Some developers, however, feel that CommonJS is better suited to server-side development, which is one reason for the current disagreement over which format should be used as the de facto standard in the pre-Harmony age moving forward. One argument against CommonJS is that many CommonJS APIs address server-oriented features that one would simply not be able to implement at the browser level in JavaScript; for example, io>, system and js could be considered unimplementable by the nature of their functionality.
That said, knowing how to structure CommonJS modules is useful so that we can better appreciate how they fit in when defining modules that might be used everywhere. Modules that have applications on both the client and server side include validation, conversion and templating engines. The way some developers choose which format to use is to opt for CommonJS when a module can be used in a server-side environment and to opt for AMD otherwise.
Because AMD modules are capable of using plugins and can define more granular things such as constructors and functions, this makes sense. CommonJS modules are able to define objects that are tedious to work with only if you’re trying to obtain constructors from them.
From a structural perspective, a CommonJS module is a reusable piece of JavaScript that exports specific objects made available to any dependent code; there are typically no function wrappers around such modules. Plenty of great tutorials on implementing CommonJS modules are out there, but at a high level, the modules basically contain two main parts: a variable named exports, which contains the objects that a module makes available to other modules, and a require function, which modules can use to import the exports of other modules.
03 |
this.foo = function(){ |
04 |
console.log('Hello foo'); |
07 |
this.bar = function(){ |
08 |
console.log('Hello bar'); |
12 |
exports.foobar = foobar; |
20 |
var foobar = require('./foobar').foobar, |
21 |
test = new foobar.foo(); |
There are a number of great JavaScript libraries for handling module loading in AMD and CommonJS formats, but my preference is RequireJS (http://requirejs.org) (curl.js (https://github.com/unscriptable/curl) is also quite reliable). Complete tutorials on these tools are beyond the scope of this article, but I recommend John Hann’s post “curl.js: Yet Another AMD Loader (http://unscriptable.com/index.php/2011/03/30/curl-js-yet-another-amd-loader/),” and James Burke’s post “
LABjs and RequireJS: Loading JavaScript Resources the Fun Way (http://msdn.microsoft.com/en-us/scriptjunkie/ff943568).”
With what we’ve covered so far, wouldn’t it be great if we could define and load plugin modules compatible with AMD, CommonJS and other standards that are also compatible with different environments (client-side, server-side and beyond)? Our work on AMD and UMD (Universal Module Definition) plugins and widgets is still at a very early stage, but we’re hoping to develop solutions that can do just that.
One such pattern we’re working on (https://github.com/addyosmani/jquery-plugin-patterns/issues/1) at the moment appears below, which has the following features:
- A core/base plugin is loaded into a
$.core namespace, which can then be easily extended using plugin extensions via the namespacing pattern. Plugins loaded via script tags automatically populate a plugin namespace under core (i.e. $.core.plugin.methodName()).
- The pattern can be quite nice to work with because plugin extensions can access properties and methods defined in the base or, with a little tweaking, override default behavior so that it can be extended to do more.
- A loader isn’t necessarily required at all to make this pattern fully function.
usage.html
02 |
<script type="text/javascript" src="pluginCore.js"></script> |
03 |
<script type="text/javascript" src="pluginExtension.js"></script> |
05 |
<script type="text/javascript"> |
21 |
core.plugin.setGreen("div:first"); |
29 |
core.plugin.setRed('div:last'); |
pluginCore.js
11 |
(function ( name, definition ){ |
12 |
var theModule = definition(), |
14 |
hasDefine = typeof define === 'function' && define.amd, |
16 |
hasExports = typeof module !== 'undefined' && module.exports; |
20 |
} else if ( hasExports ) { |
21 |
module.exports = theModule; |
23 |
(this.jQuery || this.ender || this.$ || this)[name] = theModule; |
25 |
})( 'core', function () { |
28 |
module.highlightColor = "yellow"; |
29 |
module.errorColor = "red"; |
36 |
module.highlight = function(el,strColor){ |
38 |
jQuery(el).css('background', strColor); |
42 |
highlightAll:function(){ |
43 |
module.highlight('div', module.highlightColor); |
pluginExtension.js
03 |
(function ( name, definition ) { |
04 |
var theModule = definition(), |
05 |
hasDefine = typeof define === 'function', |
06 |
hasExports = typeof module !== 'undefined' && module.exports; |
10 |
} else if ( hasExports ) { |
11 |
module.exports = theModule; |
16 |
var namespaces = name.split("."); |
17 |
var scope = (this.jQuery || this.ender || this.$ || this); |
18 |
for (var i = 0; i < namespaces.length; i++) { |
19 |
var packageName = namespaces[i]; |
20 |
if (obj && i == namespaces.length - 1) { |
21 |
obj[packageName] = theModule; |
22 |
} else if (typeof scope[packageName] === "undefined") { |
23 |
scope[packageName] = {}; |
25 |
obj = scope[packageName]; |
29 |
})('core.plugin', function () { |
36 |
setGreen: function ( el ) { |
37 |
highlight(el, 'green'); |
39 |
setRed: function ( el ) { |
40 |
highlight(el, errorColor); |
While this is beyond the scope of this article, you may have noticed that different types of require methods were mentioned when we discussed AMD and CommonJS.
The concern with a similar naming convention is, of course, confusion, and the community is currently split on the merits of a global require function. John Hann’s suggestion here is that rather than call it require, which would probably fail to inform users of the difference between a global and inner require, renaming the global loader method something else might make more sense (such as the name of the library). For this reason, curl.js uses curl, and RequireJS uses requirejs.
This is probably a bigger discussion for another day, but I hope this brief walkthrough of both module types has increased your awareness of these formats and has encouraged you to further explore and experiment with them in your apps.
Further Reading
What Makes A Good jQuery Plugin?
At the end of the day, patterns are just one aspect of plugin development. And before we wrap up, here are my criteria for selecting third-party plugins, which will hopefully help developers write them.
Quality
Do your best to adhere to best practices with both the JavaScript and jQuery that you write. Are your solutions optimal? Do they follow the jQuery core style guidelines (http://docs.jquery.com/JQuery_Core_Style_Guidelines)? If not, is your code at least relatively clean and readable?
Compatibility
Which versions of jQuery is your plugin compatible with? Have you tested it with the latest builds? If the plugin was written before jQuery 1.6, then it might have issues with attributes, because the way we approach them changed with that release. New versions of jQuery offer improvements and opportunities for the jQuery project to improve on what the core library offers. With this comes occasional breakages (mainly in major releases) as we move towards a better way of doing things. I’d like to see plugin authors update their code when necessary or, at a minimum, test their plugins with new versions to make sure everything works as expected.
Reliability
Your plugin should come with its own set of unit tests. Not only do these prove your plugin actually works, but they can also improve the design without breaking it for end users. I consider unit tests essential for any serious jQuery plugin that is meant for a production environment, and they’re not that hard to write. For an excellent guide to automated JavaScript testing with QUnit, you may be interested in “Automating JavaScript Testing With QUnit (http://msdn.microsoft.com/en-us/scriptjunkie/gg749824),” by Jorn Zaefferer (http://bassistance.de/).
Performance
If the plugin needs to perform tasks that require a lot of computing power or that heavily manipulates the DOM, then you should follow best practices that minimize this. Use jsPerf.com (http://jsperf.com) to test segments of your code so that you’re aware of how well it performs in different browsers before releasing the plugin.
Documentation
If you intend for other developers to use your plugin, ensure that it’s well documented. Document your API. What methods and options does the plugin support? Does it have any gotchas that users need to be aware of? If users cannot figure out how to use your plugin, they’ll likely look for an alternative. Also, do your best to comment the code. This is by far the best gift you could give to other developers. If someone feels they can navigate your code base well enough to fork it or improve it, then you’ve done a good job.
Likelihood of maintenance
When releasing a plugin, estimate how much time you’ll have to devote to maintenance and support. We all love to share our plugins with the community, but you need to set expectations for your ability to answer questions, address issues and make improvements. This can be done simply by stating your intentions for maintenance in the README file, and let users decide whether to make fixes themselves.
Conclusion
Today, we’ve explored several time-saving design patterns and best practices that can be employed to improve your plugin development process. Some are better suited to certain use cases than others, but I hope that the code comments that discuss the ins and outs of these variations on popular plugins and widgets were useful.
Remember, when selecting a pattern, be practical. Don’t use a plugin pattern just for the sake of it; rather, spend some time understanding the underlying structure, and establish how well it solves your problem or fits the component you’re trying to build. Choose the pattern that best suits your needs.
And that’s it. If there’s a particular pattern or approach you prefer taking to writing plugins which you feel would benefit others (which hasn’t been covered), please feel free to stick it in a gist (http://gist.github.com) and share it in the comments below. I’m sure it would be appreciated.
Until next time, happy coding!
Thanks to John Hann, Julian Aubourg, Andree Hanson and everyone else who reviewed this post for their comments and feedback.
- Mireille Mautte
- Latricia Cuch
- Kyu
- Linda
- Rayonna