Building an Object-Oriented jQuery Plugin
Update: Jamie Talbot has written an excellent article that improves this method and is definitely worth a read.
So you've been using jQuery as your Javascript framework and now you need to write a plugin. If you come from an Object-Oriented background like me, you may feel that jQuery's plugins leave a lot to be desired.
The basic formula to create a jQuery plugin is to extend the plugin namespace with a single method:
#myplugin.js
jQuery.fn.myplugin = function()
{
// Do some cool stuff here
}
While that seems all fine and dandy for simple plugins, you may need to create more robust plugins that do many things, often in a non-linear fashion.
Some plugins get around this by adding tons of methods to jQuery's plugin namespace.
$('#test').plugin();
$('#test').pluginAdd('stuff');
$('#test').pluginRemove('other stuff');
$('#test').pluginDoSomethingCool();
I personally don't like that approach because it pollutes the jQuery plugin namespace with lots of methods. I personally like to stick to just one plugin method per plugin.
Other plugins use the first parameter of the plugin to call methods:
$('#test').plugin();
$('#test').plugin('add', 'stuff');
$('#test').plugin('remove', 'other stuff');
$('#test').plugin('doSomethingCool');
I think this approach is a little awkward, especially if the plugin accepts an options object the first time it is created. This approachs means you would have to either write a switch of all the methods you want to expose, or blindly accept any string as a method name.
To get around these hurdles, I've created a basic template for jQuery plugins that provides access to an Object-Oriented interface if needed while still maintaining jQuery's simplicity of a single method in the plugin namespace.
The first thing you need to do is wrap all your plugin code in an anonymous function. This will help keep things nice and tidy without creating global variables.
#myplugin.js
(function($){
// Your plugin code goes here
})(jQuery);
Next, create your plugin as a class, where the first parameter is a single DOM element.
#myplugin.js
(function($){
var MyPlugin = function(element)
{
var elem = $(element);
var obj = this;
// Public method
this.publicMethod = function()
{
console.log('publicMethod() called!');
};
};
})(jQuery);
To make your new object-oriented class available as a jQuery plugin, write a simple wrapper function in the plugin namespace:
#myplugin.js
(function($){
var MyPlugin = function(element)
{
var elem = $(element);
var obj = this;
// Public method
this.publicMethod = function()
{
console.log('publicMethod() called!');
};
};
$.fn.myplugin = function()
{
return this.each(function()
{
var myplugin = new MyPlugin(this);
});
};
})(jQuery);
Now, when you call $(element).myplugin(), the jQuery plugin instantiates an instance of MyPlugin, passing the element as the first argument.
But now there's a problem of how to get the object "myplugin" once it's been created. For this, I usually store the object in the elements data. This provides easy access to the object while allowing you to prevent accidental double instantiation in the event that the plugin was called again on the same element.
#myplugin.js
(function($){
var MyPlugin = function(element)
{
var elem = $(element);
var obj = this;
// Public method
this.publicMethod = function()
{
console.log('publicMethod() called!');
};
};
$.fn.myplugin = function()
{
return this.each(function()
{
var element = $(this);
// Return early if this element already has a plugin instance
if (element.data('myplugin')) return;
var myplugin = new MyPlugin(this);
// Store plugin object in this element's data
element.data('myplugin', myplugin);
});
};
})(jQuery);
Now you have easy access to the object should you need to run methods on it.
$('#test').myplugin();
var myplugin = $('#test').data('myplugin');
myplugin.publicMethod(); // prints "publicMethod() called!" to console
If you need to get fancy and add options parameter or other required parameters, just pass them from the jQuery plugin to your plugin's constructor:
#myplugin.js
(function($){
var MyPlugin = function(element, options)
{
var elem = $(element);
var obj = this;
// Merge options with defaults
var settings = $.extend({
param: 'defaultValue'
}, options || {});
// Public method
this.publicMethod = function()
{
console.log('publicMethod() called!');
};
};
$.fn.myplugin = function(options)
{
return this.each(function()
{
var element = $(this);
// Return early if this element already has a plugin instance
if (element.data('myplugin')) return;
// pass options to plugin constructor
var myplugin = new MyPlugin(this, options);
// Store plugin object in this element's data
element.data('myplugin', myplugin);
});
};
})(jQuery);
You may also want to expose some of your object's methods while keeping others private. To make a private method, create a local function within your object using the var keyword:
#myplugin.js
(function($){
var MyPlugin = function(element, options)
{
var elem = $(element);
var obj = this;
var settings = $.extend({
param: 'defaultValue'
}, options || {});
// Public method - can be called from client code
this.publicMethod = function()
{
console.log('public method called!');
};
// Private method - can only be called from within this object
var privateMethod = function()
{
console.log('private method called!');
};
};
$.fn.myplugin = function(options)
{
return this.each(function()
{
var element = $(this);
// Return early if this element already has a plugin instance
if (element.data('myplugin')) return;
// pass options to plugin constructor
var myplugin = new MyPlugin(this, options);
// Store plugin object in this element's data
element.data('myplugin', myplugin);
});
};
})(jQuery);
To see an example of a plugin I wrote that uses this template, check out my Tagger plugin.

Comments
blog comments powered by Disqus