10
X

Making a jQuery Plugin Truly Customizable

read 17 comments

Most if not all of the jQuery plugins out there have some level of customization. But very few of the plugin authors have mastered the very particular art involved.

Achieving the "optimum level" of customization is a bit of a balancing act… go too far either way and you've got an unusable plugin!

Bob and Sue

Let's say Bob has created a wicked new gallery plugin (called "superGallery") which takes a list of images and makes them navigable. Bob's thrown in some animation to make it more interesting. He's tried to make the plugin as customizable as possible, and has ended up with something like this:

Continue Reading Below
JavaScript:
  1. jQuery.fn.superGallery = function(options) {
  2.    
  3.     // Bob's default settings:
  4.     var defaults = {
  5.        
  6.         textColor : '#000',
  7.         backgroundColor : '#FFF',
  8.         fontSize : '1em',
  9.         delay : 'quite long',
  10.         getTextFromTitle : true,
  11.         getTextFromRel : false,
  12.         getTextFromAlt : false,
  13.         animateWidth : true,
  14.         animateOpacity : true,
  15.         animateHeight : true,
  16.         animationDuration : 500,
  17.         clickImgToGoToNext : true,
  18.         clickImgToGoToLast : false,
  19.         nextButtonText : 'next',
  20.         previousButtonText : 'previous',
  21.         nextButtonTextColor : 'red',
  22.         previousButtonTextColor : 'red'
  23.        
  24.     };
  25.    
  26.     var settings = $.extend({}, defaults, options);
  27.    
  28.     return this.each(function(){
  29.         // ----------------------------
  30.         // Plugin code would go here...
  31.         // ----------------------------
  32.     });
  33.    
  34. };

The first thing that probably comes to your mind (ok, maybe not the first) is the prospect of how huge this plugin must be to accommodate such a level of customization. The plugin, if it weren't fictional, would probably be a lot larger than necessary. There are only so many kilobytes people will be willing to spend!

Now, our friend Bob thinks this is all fine; in fact, he's quite impressed with the plugin and its level of customization. He believes that all the options make for a more versatile solution, one which can be used in many different situations.

Sue, another friend of ours, has decided to use this new plugin. She has set up all of the options required and now has a working solution sitting in front of her. It's only five minutes later, after playing with the plugin, that she realizes the gallery would look much nicer if each image's width were animated at a slower speed. She hastily searches through Bob's documentation but finds no animateWidthDuration option!

Do you see the problem?

It's not really about how many options your plugin has; but what options it has!

Bob has gone a little over the top. The level of customization he's offering, while it may seem high, is actually quite low, especially considering all the possible things one might want to control when using this plugin. Bob has made the mistake of offering a lot of ridiculously specific options, rendering his plugin much more difficult to customize!

A better model

So it's pretty obvious: Bob needs a new customization model, one which does not relinquish control or abstract away the necessary details.

The reason Bob is so drawn to this high-level simplicity is that the jQuery framework very much lends itself to this mindset. Offering a previousButtonTextColor option is nice and simple, but let's face it, the vast majority of plugin users are going to want way more control!

Here are a few tips which should help you create a better set of customizable options for your plugins:

Don't create plugin-specific syntax

Developers who use your plugin shouldn't have to learn a new language or terminology just to get the job done.

Bob thought he was offering maximum customization with his delay option (look above). He made it so that with his plugin you can specify four different delays, "quite short," "very short," "quite long," or "very long":

JavaScript:
  1. var delayDuration = 0;
  2. switch (settings.delay) {
  3.     case 'very short' : delayDuration = 100;
  4.     break;
  5.     case 'quite short' : delayDuration = 200;
  6.     break;
  7.     case 'quite long' : delayDuration = 300;
  8.     break;
  9.     case 'very long' : delayDuration = 400;
  10.     break;
  11.     default : delayDuration = 200
  12. }

Not only does this limit the level of control people have, but it takes up quite a bit of space. Twelve lines of code just to define the delaying time is a bit much, don't you think? A better way to construct this option would be to let plugin users specify the amount of time (in milliseconds) as a number, so that no processing of the option needs to take place.

The key here is not to diminish the level of control through your abstraction. Your abstraction, whatever it is, can be as simplistic as you want, but make sure that people who use your plugin will still have that much-sought-after low-level control! (By low-level I mean non-abstracted)

Give full control of elements

If your plugin creates elements to be used within the DOM, then it's a good idea to offer plugin users some way to access those elements. Sometimes this means giving certain elements IDs or classes. But note that your plugin shouldn't rely on these hooks internally:

A bad implementation:

JavaScript:
  1. // Plugin code
  2.  
  3. $('<div id="the_gallery_Wrapper" />').appendTo('body');
  4. $('#the_gallery_wrapper').append('...');

A good implementation:

JavaScript:
  1. // Retain an internal reference:
  2. var $wrapper = $('<div />')
  3.                  .attr(settings.wrapperAttrs)
  4.                  .appendTo(settings.container);
  5. $wrapper.append('...'); // Easy to reference later...

Notice that we've created a reference to the injected wrapper and we're also calling the 'attr' method to add any specified attributes to the element. So, in our settings it might be handled like this:

JavaScript:
  1. var defaults = {
  2.  
  3.     wrapperAttrs : {
  4.         id : 'gallery-wrapper'
  5.     },
  6.    
  7.     // ... rest of settings ...
  8.    
  9. };
  10.  
  11. // We can use the extend method to merge options/settings as usual:
  12. // But with the added first parameter of TRUE to signify a DEEP COPY:
  13. var settings = $.extend(true, {}, defaults, options);

The $.extend() method will now recurse through all nested objects to give us a merged version of both the defaults and the passed options, giving the passed options precedence.

The plugin user now has the power to specify any attribute of that wrapper element — so if they require that there be a hook for any CSS styles then they can quite easily add a class or change the name of the ID without having to go digging around in plugin source.

The same model can be used to let the user define CSS styles:

JavaScript:
  1. var defaults = {
  2.  
  3.     wrapperCSS : {},
  4.    
  5.     // ... rest of settings ...
  6.    
  7. };
  8.  
  9. // Later on in the plugin where we define the wrapper:
  10. var $wrapper = $('<div />')
  11.                  .attr(settings.wrapperAttrs)
  12.                  .css(settings.wrapperCSS) // ** Set CSS!
  13.                  .appendTo(settings.container);

Your plugin may have an associated StyleSheet where developers can add CSS styles. Even in this situation it's a good idea to offer some convenient way of setting styles in JavaScript, without having to use a selector to get at the elements.

Provide callback capabilities

What is a callback? - A callback is essentially a function to be called later, normally triggered by an event. It's passed as an argument, usually to the initiating call of a component. (in this case, a jQuery plugin).

If your plugin is driven by events then it might be a good idea to provide a callback capability for each event. Plus, you can create your own custom events and then provide callbacks for those. In this gallery plugin it might make sense to add an 'onImageShow' callback.

JavaScript:
  1. var defaults = {
  2.  
  3.     onImageShow : function(){}, // we define an empty anonymous function
  4.                                 // so that we don't need to check its
  5.                                 // existence before calling it.
  6.    
  7.     // ... rest of settings ...
  8.    
  9. };
  10.  
  11. // Later on in the plugin:
  12.  
  13. $nextButton.bind('click', showNextImage);
  14.  
  15. function showNextImage() {
  16.     // DO stuff to show the image here...
  17.     // ...
  18.     // Here's the callback:
  19.     settings.onImageShow.call(this);
  20. }

Instead of initiating the callback via traditional means (adding parenthesis) we're calling it in the context of 'this' which will be a reference to the image node. This means that you have access to the actual image node through the 'this' keyword within the callback:

JavaScript:
  1. $('ul.imgs li').superGallery({
  2.  
  3.     onImageShow : function() {
  4.         $(this)
  5.             .after('<span>' + $(this).attr('longdesc') + '</span>');
  6.     },
  7.    
  8.     // ... other options ...
  9.     // ...
  10.    
  11. });

Similarily you could add an "onImageHide" callback and numerous other ones...

The point with callbacks is to give plugin users an easy way to add additional functionality without digging around in the source.

Remember, it's a compromise

Your plugin is not going to be able to work in every situation. And equally, it's not going to be very useful if you offer no or very few methods of control. So, remember, it's always going to be a compromise. Three things you must always take into account are:

  • Flexibility: How many situations will your plugin be able to deal with?
  • Size: Does the size of your plugin correspond to its level of functionality? I.e. Would you use a very basic tooltip plugin if it was 20k in size? - Probably not!
  • Performance: Does your plugin heavily process the options in any way? Does this effect speed? Is the overhead caused worth it for the end user?


comment feed

17 comments

  1. I like this article. Here's why:

    Having searched, let me assure you there doesn't appear to be much, if any good documentation on callback best practices. I'm glad to see you've included some mention of this here. Perhaps callback best practices would be a good topic for a future article?

    Secondly, a question: in a related (excellent) article here, http://www.learningjquery.com/2007/10/a-plugin-development-pattern , Mike Alsup suggests making the defaults a bona-fide member of the plugin, as opposed to declaring a run-time var. This feels semantically better to me. Thoughts?

    Cheers!

    • I remember reading that and it does seem like a good way of providing additional control. Essentially it allows plugin users to override the defaults whenever they feel necessary, this can be very useful depending on what the plugin does, although probably isn't a necessary measure all of the time.

      Thanks for your comment Steven!

  2. This article is a must read for all plugin developer. I would be happy if books out there had tips like this one.
    Many thanks and keep the great work!

  3. Great article! One other item I've come across in the course of my own plugin development has been to allow the customization of animations by opening up the options object for the animate() method instead of using the short hands, ie. slideDown(), etc. If your plugin uses animation then it opens the whole gammit of jQuery possibilities to the user. Lastly, I've personally found it good to open up the defaults property so that it can be set unilaterally for the whole plugin, this is especially handy when multiple calls to a plugin are going to be used. Thanks for the great article, keep up the good work!

  4. Erik

    Great article!
    Really helpful for inexperienced plug-in developers like me..

    Cheers,
    Erik

  5. this is an amazing pattern! thanx
    guess what i am just writing my own image gallery plugin :)

    thanx

  6. Ren

    Great article - if only everyone did it like this. I am currently learning jquery by the day - but I still find it very hard to understand and tweak code to make it like I want it to be..

    Like the Apple Menu Jquery script from Kriesi. I really love it.. but I just don't understand it enough to be able to change it like I want to..

    I would like to make a menu option -when active/clicked- stay “open” so that I could have submenu items. So in other words - now you hover over a item and you see the whole menu item - when I click a certain menu item I want it to stay like that ..

    Obviously I've been trying to accomplish this by try&error in the custom.js file but my jquery knowlegde isn’t that good yet.. Anyone here who can help or hint me in the right direction ?

    Hope anyone would be so kind to help me.. I would really appreciate it ! I am off to learn more jquery :)

  7. Karl, I've learned a lot from this website and your book, thanks!!
    radek

  8. NYWebTeam

    Awesome article, really helpful. It's a must read for plugin developers.
    Thank you.

    F.

  9. WebDevVote.com

    You are voted!
    Track back from WebDevVote.com.

  10. maz

    Nice post! But i do not like the "callback way" for serveral reasons.
    - I have to install a callback for every instance of the plugin.
    - There can be only one function set for callback.
    - Calling a callback from a plugin with different 'this' contexts might be annoying. (What the heck, where is 'this' pointing to this time? And: With 'this' i have access to some element but how do i access the plugin.)
    I think it is more fun, if plugins would fire custom events instead of calling callbacks. The custom events should hold all related data. And 'this' should always point to the plugin. With events i am able to register an event handler for the document and can handle multiple plugins with a single event listener.

  11. Nice thanks for the post but how would i add this to the Watermark plugin

  12. Thanks for the great blog! Just one thing that I like to do with my plugins is provide an "unnamed" callback kind of like in the jQuery core. Then you have your plugin signature as $.fn.superGallery = function(options, callback) with the callback as the second parameter. When there are no options, you check to see if "options" are options or really the callback (this looks like how they do it in jQuery)

    var defaults = { delay : 80 };
    if ( jQuery.isFunction( options ) ) {
    	callback = options;
    	options = {};
    }
    var settings = $.extend({}, defaults, options);

    Something like that. Then you can call the callback like before (but check if you have to):
    if ( callback ) callback.call(this);
    Then you can use the plugin without options:
    $('ul.imgs li').superGallery( function(){ /* callback */ } );

  13. Vincent

    Hi, really nice article,
    I've just a question regarding JSLINT. It suggests this :

    /////////
    Move the invocation into the parens that contain the function.
    .....
    ....})(jQuery);
    /////////

    and now we get this :

    /////////
    ....
    ....}(jQuery));
    /////////

    do you see something dangerous with this transformation?

    Thank you very much.

    Vincent

  14. Thanks so much for this article! I'm just starting to delve into jquery plugins and it's hard to find clear, concise examples of what is good practice. Your callback code was very very useful too :)

  15. sfsf
    
    (function($){
        $.fn.extend({
            //plugin name - animatemenu
            test: function(options) {
                var defaults = {
    				msg1:'xxx',
    				msg2:'yyy',
    				view:function(){ return 'this is me'; }
                };
                var options = $.extend(defaults, options);
                return this.each(function() {
                      var o =options;
                      var obj = $(this);
    				  $(this).click(function(){
    				  	st=o.view();
    					alert(st);
    				  });                
                });//return
            }//test
        });
    })(jQuery); 
    
    ////
    $(document).ready(function(){
    	$('#a1').test(
    	);
    	$('#a2').test({
    		view:function(){
    			return $(this).text();//this is not run, why?? help me
    		}
    	});
    });
    
  16. Thanks a lot for this post... I'm with Steven Black - there's hardly any good info out there on how to add callbacks to events in a plugin, and this post helped me out enormously... Cheers!

13 Pings

  1. [...] Padolsey at learningjquery.com has shown us how to achieve the “optimum level” of customization to jQuery plugin, with [...]

  2. [...] Making a jQuery Plugin Truly Customizable » Learning jQuery - Tips, Techniques, Tutorials (tags: tutorial javascript jquery plugin) [...]

  3. [...] Making a jQuery Plugin Truly Customizable [...]

  4. progg.ru

    Making a jQuery Plugin Truly Customizable...

    Thank you for submitting this cool story - Trackback from progg.ru...

  5. [...] Making a jQuery Plugin Truly Customizable » Learning jQuery - Tips, Techniques, Tutorials [...]

  6. Bookmarks for March 29th, 2009 | vitali software

    [...] Making a jQuery Plugin Truly Customizable » Learning jQuery - Tips, Techniques, Tutorials - Most if not all of the jQuery plugins out there have some level of customization. But very few of the plugin authors have mastered the very particular art involved. Achieving the "optimum level" of customization is a bit of a balancing act… go too far either way and you've got an unusable plugin!… (More…) [...]

  7. Links: March 8th - March 12th | The World According to Buchs

    [...] Making a jQuery Plugin Truly Customizable » Learning jQuery - Tips, Techniques, Tutorials [...]

  8. [...] Learning jQuery: Making a jQuery Plugin Truly Customizable [...]

  9. wheresrhys » Blog Archive » My jQuery plugin writing tips

    [...] jQuery. There still is, but one thing that struck me is that there are a lot of great tutorials on writing jQuery plugins out there but none of them individually captures all the things that took my plugins [...]

  10. [...] Making a jQuery Plugin Truly Customizable [...]

  11. [...] Making a j&#81u&#101ry Plugin Truly Custo&#109izabl&#101 [...]

  12. [...] Making a jQuery Plugin Truly Customizable [...]

  13. [...] M&#97king &#97 jQuery Plugin Truly Cus&#116o&#109iz&#97ble [...]

Sorry, but comments for this entry are now closed.