Skip to content

How did we get here?

Daniel Kahn edited this page Sep 23, 2015 · 24 revisions

How did we get here?

Attention: This is not finished yet, so if you want to help, go ahead and change this.

Have you got in this repo and still not sure about using this boilerplate?

Well, extending jQuery with plugins and methods is very powerful and can save you and your peers a lot of development time by abstracting your most clever functions into plugins.

The following guide, adapted from jQuery Plugins/Authoring, will outline the basics, best practices, and common pitfalls to watch out for as you begin writing your plugin.

Getting Started

To write a jQuery plugin, start by adding a new function property to the jQuery.fn object where the name of the property is the name of your plugin:

jQuery.fn.myPlugin = function() {
  // Do your awesome plugin stuff here
};

But wait! Where's my awesome dollar sign that I know and love? It's still there, however to make sure that your plugin doesn't collide with other libraries that might use the dollar sign, it's a best practice to pass jQuery to an IIFE (Immediately Invoked Function Expression) that maps it to the dollar sign so it can't be overwritten by another library in the scope of its execution.

(function( $ ) {
  $.fn.myPlugin = function() {
    // Do your awesome plugin stuff here
  };
})( jQuery );

Ah, that's better. Now within that closure, we can use the dollar sign in place of jQuery as much as we like. But we can improve it even more.

Let's add a semi-colon before function invocation as a safety net against concatenated scripts and/or other plugins which may not be closed properly.

Also, you can see that undefined will be used here as the undefined global variable in ECMAScript 3 is mutable (ie. it can be changed by someone else). Undefined isn't really being passed in so we can ensure the value of it is truly undefined. In ES5, undefined can no longer be modified.

And window will be passed through as local variables rather than globals as this (slightly) quickens the resolution process and can be more efficiently minified (especially when both are regularly referenced in your plugin).

;(function ( $, window, undefined ) {
  $.fn.myPlugin = function() {
    // Do your awesome plugin stuff here
  };
}(jQuery, window));

Context

Now that we have our shell we can start writing our actual plugin code. But before we do that, I'd like to say a word about context. In the immediate scope of the plugin function, the this keyword refers to the jQuery object the plugin was invoked on. This is a common slip up due to the fact that in other instances where jQuery accepts a callback, the this keyword refers to the native DOM element. This often leads to developers unnecessarily wrapping the this keyword (again) in the jQuery function.

(function( $ ){

  $.fn.myPlugin = function() {
  
    // there's no need to do $(this) because
    // "this" is already a jquery object

    // $(this) would be the same as $($('#element'));
        
    this.fadeIn('normal', function(){
      // the this keyword is a DOM element
    });

  };
})( jQuery );
$('#element').myPlugin();

The Basics

Now that we understand the context of jQuery plugins, let's write a plugin that actually does something.

(function( $ ){

  $.fn.maxHeight = function() {
  
    var max = 0;

    this.each(function() {
      max = Math.max( max, $(this).height() );
    });

    return max;
  };
})( jQuery );
var tallest = $('div').maxHeight(); // Returns the height of the tallest div

This is a simple plugin that leverages .height() to return the height of the tallest div in the page.

Maintaining Chainability

The previous example returns an integer value of the tallest div on the page, but often times the intent of a plugin is simply to modify the collection of elements in some way, and pass them along to the next method in the chain. This is the beauty of jQuery's design and is one of the reasons jQuery is so popular. So to maintain chainability in a plugin, you must make sure your plugin returns the this keyword.

(function( $ ){

  $.fn.lockDimensions = function( type ) {  

    return this.each(function() {

      var $this = $(this);

      if ( !type || type == 'width' ) {
        $this.width( $this.width() );
      }

      if ( !type || type == 'height' ) {
        $this.height( $this.height() );
      }

    });

  };
})( jQuery );
$('div').lockDimensions('width').css('color', 'red');

Because the plugin returns the this keyword in its immediate scope, it maintains chainability and the jQuery collection can continue to be manipulated by jQuery methods, such as .css. So if your plugin doesn't return an intrinsic value, you should always return the this keyword in the immediate scope of the plugin function. Also, as you might assume, arguments you pass in your plugin invocation get passed to the immediate scope of the plugin function. So in the previous example, the string 'width' becomes the type argument for the plugin function.

Defaults and Options

For more complex and customizable plugins that provide many options, it's a best practice to have default settings that can get extended (using $.extend) when the plugin is invoked. So instead of calling a plugin with a large number of arguments, you can call it with one argument which is an object literal of the settings you would like to override. Here's how you do it.

(function( $ ){

  $.fn.tooltip = function( options ) {  

    // Create some defaults, extending them with any options that were provided
    var settings = $.extend( {
      'location'         : 'top',
      'background-color' : 'blue'
    }, options);

    return this.each(function() {        

      // Tooltip plugin code here

    });

  };
})( jQuery );
$('div').tooltip({
  'location' : 'left'
});

In this example, after calling the tooltip plugin with the given options, the default location setting gets overridden to become left, while the background-color setting remains the default blue. So the final settings object ends up looking like this:

{
  'location'         : 'left',
  'background-color' : 'blue'
}

This is a great way to offer a highly configurable plugin without requiring the developer to define all available options.

See also