Login with OpenID

Building an Object-Oriented jQuery Plugin

Written by Hector Virgen
Published on October 4, 2009
Last updated on August 23, 2010

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