jQuery Plugin Authoring and Namespacing


I'm used to writing plugins like so:

;(function($){jQuery.fn.myPlugin=function(options){
    var defaults={
        'property':value
    },
    o=$.extend({},defaults,options||{});

   // INSERT AND CACHE ELEMENTS
   var $Element=$('<div></div>');
   $Element.appendTo($('body'));

function funFunction(){
  // I have access to $Element!
 $Element.hide(500);
};

this.each(function(i){
     var $this=$(this);
});
return this;
});};})(jQuery);

I know it's not perfect, which is why I'm now trying to properly learn namespacing, better plugin structure/patterns. The past couple of books I've read unfortunately reference the jQuery plugin authoring tutorial word for word, so haven't been much help. The tutorial seems to split everything up and doesn't show a good example of a combination, which is why I'm confused. In the tutorial, it shows the namespacing example.

jQuery Plugin Namespacing Tutorial

(function( $ ){
  var methods = {
    init : function( options ) { 
    },
    show : function( ) {
    },
    hide : function( ) { 
    },
    update : function( content ) { 
    }
  };

  $.fn.tooltip = function( method ) {
    // Method calling logic
    if ( methods[method] ) {
      return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return methods.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
    }    
  };
})( jQuery );
// calls the init method
$('div').tooltip(); 

I understand the structure and how to access namespaced objects, however it shows the other example for defaults/options excluding any namespacing... So in an effort to write the beginning of a plugin that is properly namespaced, has defaults/options and caches the HTML elements I'm inserting for use throughout the entire plugin, I've come up with the following.

Correct Combo?

;(function($,window,document,undefined){
var myPlugin={
    // METHODS
    init:function(options){

    },
    buildElements:function(){ 
        var $Elements=$('<div id="myElem"></div>')
                    .appendTo($('body'));
       }
};

$.fn.myPlugin=function(method,options){
    var defaults={

    },
    options=$.extend({},defaults,options||{});

    myPlugin.buildElements();

    return this.each(function(){
        var $this=$(this);
        if(myPlugin[method]){
          return myPlugin[method].apply(this,Array.prototype.slice.call(arguments,1));
        }else if(typeof method==='object'||!method){
          return myPlugin.init.apply(this,arguments);
        }else{$.error('Method '+method+' does not exist on jQuery.myPlugin');};
    });
};})(jQuery);
  1. Obviously, when I build/insert myElem it will only be available inside that method and not inside any others.... am I building it in the wrong place?

  2. Is the defaults/extend in the correct place?

  3. If I'm not wanting to access methods from outside of the plugin do I need the method logic section?

  4. Are there any benefits to using .prototype vs .fn?

Thanks so much to anyone and everyone! :)

Look at the "tooltip" example plugin more carefully. It's a truly GREAT pattern.

It does all the namespacing you'll ever need and is already of the type you're used to, at least the generalised "supervisor" block at the bottom is - ie this part :

$.fn.tooltip = function( method ) {
    // Method calling logic
    if ( methods[method] ) {
      return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return methods.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
    }    
};

methods is a private variable in strightforward javascript terms but its properties are exposed as methods of the plugin in a very clever, unconventional way by the supervisor.

Pleeeease don't try to move the defaults/options code out of the init method. This will screw everything sideways! Follow the tried and trusted pattern and all will be fine.

EDIT:

Be sure to adhere to other aspects of the pattern :

  • To maintain jQuery object chainability, use a return this.each(function(){...}) structure in every method that doesn't return a specific result.
  • (Generally), in init, establish a .data('pluninName', {...}) object to accommodate any data that is established on initialization, and that needs to be accessed/modified/augmented later by other plugin methods.

The pattern provides only a single closure for the plugin itself (containing the methods object); the closure's namespace cannot be used for element-specific data (including initialization options), hence the need to use .data('pluninName', ...).

These are not just conventions - they are absolutely key to making the pattern work as intended.