3

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! :)

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Aaron
  • 2,482
  • 1
  • 26
  • 56

1 Answers1

7

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.

Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
  • 1
    Thanks so much for the reply! So just to make sure I understand: keep the options/defaults inside the init method then call it in fn.name function. Where would I build and cache plugin elements, for example the actual tooltip div? Thanks! :) – Aaron Sep 28 '12 at 02:51
  • 2
    With this pattern, after execution of the plugin code, `jquery` has *one* new custom method - "tooltip" in the example. In use the plugin is invoked in the same way as other methods, eg. `$(selector).tooltip(...);` but with the difference that the required "internal method" of the plugin is specified as a first string argument `$(selector).tooltip('methodName');` (defaulting to 'init'). Further parameters may be passed, as required by each "internal method". This schema is designed to help avoid namespace collisions in `$.fn`, whilst still allowing plugins to have a rich collection of methods. – Beetroot-Beetroot Sep 28 '12 at 03:18
  • 1
    Thanks again for your detailed answer, I've upvoted and marked as the answer :) As soon as I get this worked out I'll post the full template – Aaron Sep 28 '12 at 15:22