Advertisement

jQuery Succinctly: jQuery Plugins

by
This post is part of a series called jQuery Succinctly.
jQuery Succinctly: jQuery and the Web Browser
jQuery Succinctly: jQuery Effects

Use the $ Alias When Constructing a Plugin

When writing a jQuery plugin, the same conflict prevention routine used with regular, old jQuery code should be implemented. With this in mind, all plugins should be contained inside a private scope where the $ alias can be used without fear of conflicts or surprising results.

The coding structure below should look familiar as it is used in almost every code example in this session.

<!DOCTYPE html>
<html lang="en">
<body>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>  alert(jQuery(document).jquery);
        // Don't use $ here. It is not reliable. 
        (function ($) {
            // Can use $ without fear of conflicts     
            alert($(document).jquery);
        })(jQuery);  </script>
</body>
</html>

New Plugins Attach to jQuery.fn Object to Become jQuery Methods

New plugins are attached to the jQuery.fn object, as this is a shortcut or alias for jQuery.prototype. In our coding example below, we are adding the count plugin to the jQuery.fn object. By doing this, we are creating our own custom jQuery method that can be used on a wrapped set of DOM elements.

Basically, a plugin attached to jQuery.fn allows us to create our own custom methods similar to any found in the API. This is because when we attach our plugin function to jQuery.fn, our function is included in the prototype chain-$.fn.count = function(){}-for jQuery objects created using the jQuery function. If that blows your mind, just remember that adding a function to jQuery.fn  means that the keyword this inside of the plugin function will refer to the jQuery object itself.

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="counter1"></div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>  (function ($) {
$.fn.count = function () {
      var $this = $(this); // "this" is the jQuery object    
      $this.text('0'); // Sets the counter start number to zero    
      var myInterval = window.setInterval(function () {
          // Interval for counting        
          var currentCount = parseFloat($this.text()); var newCount = currentCount + 1; $this.text(newCount + '');
      }, 1000);
  };
  })(jQuery); jQuery('#counter1').count();  </script>
</body>
</html>

Notes: By adding a plugin to the jQuery.fn object, we are essentially saying that our plugin would like to use the jQuery function to select a context (DOM elements). If your plugin does not require a specific context (in other words a set of DOM elements) in which it needs to operate, you might not need to attach this plugin to the $.fn. It might make more sense to add it as a utility function in the jQuery namespace.


Inside a Plugin, this Is a Reference to the Current jQuery Object

When you attach a plugin to the jQuery.fn object, the keyword this used inside of the attached plugin function will refer to the current jQuery object.

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="counter1"></div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>  (function ($) {
$.fn.count = function () {
      // "this" is equal to jQuery('#counter1')    
      alert(this); // Alerts jQuery object    
      alert(this[0]); // Alerts div element     
      alert(this[0].id); // Alerts "counter1"
  };
  })(jQuery); jQuery('#counter1').count();  </script>
</body>
</html>

It is critical that you grok exactly what the keyword this is referring to in the plugin function.


Using each() to Iterate Over the jQuery Object and Provide a Reference to Each Element In the Object Using the this Keyword

Using each(), we can create an implicit iteration for our plugin. This means that if the wrapper set contains more than one element, our plugin method will be applied to each element in the wrapper set.

To do this, we use the jQuery utility each() function, which is a generic iterator for both objects and arrays, basically simplifying looping. In the code example below, we use the function to iterate over the jQuery object itself. Inside of the function that is passed to each(), the keyword this will refer to the elements in the jQuery wrapper set.

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="counter1"></div>
    <div id="counter2"></div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>   (function ($) {
       $.fn.count = function () {
           this.each(function () {
               // "this" is the current jQuery object            
               var $this = $(this);
               $this.text('0'); // Sets the counter start number            
               var myInterval = window.setInterval(function () {
                   // Interval for counting                
                   var currentCount = parseFloat($this.text()); var newCount = currentCount + 1; $this.text(newCount + '');
               }, 1000);
           });
       };
   })(jQuery); jQuery('#counter1, #counter2').count();  </script>
</body>
</html>

Using the each() function is critical if you would like a plugin to employ implicit iteration.


Plugin Returning jQuery Object So jQuery Methods or Other Plugins Can Be Chained After Using Plugin

Typically, most plugins return the jQuery object itself so that the plugin does not break the chain. In other words, if a plugin does not specifically need to return a value, it should continue the chain so that additional methods can be applied to the wrapper set. In the code below, we are returning the jQuery object with the return this; statement so that chaining will not be broken. Notice that I am chaining on the parent() and append() methods after I call the count() plugin.

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="counter1"></div>
    <div id="counter2"></div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>   (function ($) {
$.fn.count = function () {
       return this.each(function () {
           // Return the jQuery object, or "this" after each()            
           var $this = $(this);
           $this.text('0');
           var myInterval = window.setInterval(function () { var currentCount = parseFloat($this.text()); var newCount = currentCount + 1; $this.text(newCount + ''); }, 1000);
       });
   };
   })(jQuery); jQuery('#counter1, #counter2').count().parent() // Chaining continues because jQuery object is returned    
       .append('<p>Chaining still works!</p>');   </script>
</body>
</html>

Notes: It is possible to make the plugin a destructive method by simply not returning the jQuery object.


Default Plugin Options

Plugins typically contain default options that will act as the baseline default configuration for the plugins' logic. These options are used when the plugin is invoked. In the code below I am creating a defaultOptions object containing a single property (startCount) and value (0). This object is stored on the count function $.fn.count.defaultOptions. We do this so the options are configurable from outside the plugin.

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="counter1"></div>
    <div id="counter2"></div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>   (function ($) {
$.fn.count = function () {
       return this.each(function () {
           var $this = $(this);             // Sets the counter start number to zero            
           $this.text($.fn.count.defaultOptions.startCount + '');
           var myInterval = window.setInterval(function () { var currentCount = parseFloat($this.text()); var newCount = currentCount + 1; $this.text(newCount + ''); }, 1000);
       });
   }; $.fn.count.defaultOptions = { startCount: 100 };
   })(jQuery); jQuery('#counter1, #counter2').count();    </script>
</body>
</html>

Custom Plugin Options

Typically, the default plugin options can be overwritten with custom options. In the code below, I pass in a customOptions object as a parameter to the plugin function. This object is combined with the defaultOptions object to create a single options object. We use the jQuery utility method extend() to combine multiple objects into a single object. The extend() method provides the perfect utility for overwriting an object with new properties. With this code in place, the plugin can now be customized when invoked. In the example, we pass the count plugin a custom number (500) to be used as the starting point for the count. This custom option overrides the default option (0).

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="counter1"></div>
    <div id="counter2"></div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>   (function ($) {
       $.fn.count = function (customOptions) {
           // Create new option, extend object with defaultOptoins and customOptions        
           var options = $.extend({}, $.fn.count.defaultOptions, customOptions);
           return this.each(function () {
               var $this = $(this);             // Sets the counter start number to the default option value            
               // or to a custom option value if it is passed to the plugin            
               $this.text(options.startCount + '');
               var myInterval = window.setInterval(function () { var currentCount = parseFloat($this.text()); var newCount = currentCount + 1; $this.text(newCount + ''); }, 1000);
           });
       }; $.fn.count.defaultOptions = { startCount: 100 };
   })(jQuery);  // Passing a custom option overrides default
        jQuery('#counter1, #counter2').count({ startCount: 500 });
 
    </script>
</body>
</html>

Overwriting Default Options Without Altering Original Plugin Code

Since default options are accessible from outside a plugin, it is possible to reset the default options before invoking the plugin. This can be handy when you want to define your own options without altering the plugin code itself. Doing so can simplify plugin invocations because you can, in a sense, globally set up the plugin to your liking without forking the original plugin code itself.

<!DOCTYPE html>
<html lang="en">
<body>
    <div id="counter1"></div>
    <div id="counter2"></div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>   (function ($) {
       $.fn.count = function (customOptions) {
           var options = $.extend({}, $.fn.count.defaultOptions, customOptions);
           return this.each(function () {
               var $this = $(this); $this.text(options.startCount + '');
               var myInterval = window.setInterval(function () { var currentCount = parseFloat($this.text()); var newCount = currentCount + 1; $this.text(newCount + ''); }, 1000);
           });
       }; $.fn.count.defaultOptions = { startCount: 100 };
   })(jQuery);  // Overwrite default options
        jQuery.fn.count.defaultOptions.startCount = 200; jQuery('#counter1').count(); // Will use startCount: 200, instead of startCount:0
        jQuery('#counter2').count({ startCount: 500 }); // Will overwrite any default values     </script>
</body>
</html>

Create Elements on the Fly, Invoke Plugins Programmatically

Depending on the nature of the plugin, it can be critical that a plugin be called both normally (via DOM elements and events) as well as programmatically. Consider a dialog plugin. There will be times that the modal/dialog will open based on user events. Other times, a dialog will need to open based on environmental or system events. In these situations, you can still invoke your plugin without any elements in the DOM by creating an element on the fly in order to invoke the plugin. In the code below, I invoke the dialog() plugin on page load by first creating an element to invoke my plugin.

<!DOCTYPE html>
<html lang="en">
<body>
    <a href="#" title="Hi">dialog, say hi</a> <a href="#" title="Bye">dialog, say
    bye</a>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>   (function ($) {
       $.fn.dialog = function (options) { var text = this.attr('title') || this.text(); alert(text); };
   })(jQuery);
        jQuery('a').click(function () { // Invoked by user event     
            $(this).dialog(); return false;
        });
        $(window).load(function () {     // Create DOM element to invoke the plugin    
            jQuery("<a></a>").attr('title', 'I say hi when invoked!').dialog(); // Run immediately
        });  </script>
</body>
</html>

Obviously, there could be a lot of variation of this pattern depending on the options, complexity, and functionality of the plugin. The point here is that plugins can be called via existing DOM elements, as well as those created on the fly.


Providing Callbacks and Passing Context

When authoring jQuery plugins, it is a good idea to provide callback functions as an option, and to pass these functions the context of this when the callback is invoked. This provides a vehicle for additional treatment to elements in a wrapper set. In the code below, we are passing a custom option to the outAndInFade() plugin method that is a function and should be called once the animation is complete. The callback function is being passed the value of this when it's being invoked. This allows us to then use the this value inside the function we defined. When the callback function is invoked, the keyword this will refer to one of the DOM elements contained within the wrapper set.

<!DOCTYPE html>
<html lang="en">
<body>
    <div>Out And In Fade</div>
    <div>Out And In Fade</div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>   (function ($) {
$.fn.outAndInFade = function (customOptions) {
       var options = $.extend({}, $.fn.outAndInFade.defaultOptions, customOptions || {});
       return this.each(function () {
           $(this).fadeOut().fadeIn('normal', function () { // Callback for fadeIn()                
               // Call complete() function, pass it "this"               
               if ($.isFunction(options.complete)) {
                   options.complete.apply(this);
               }
           });
       });
   };
       $.fn.outAndInFade.defaultOptions = {
           complete: null // No default function    
       };
   })(jQuery); jQuery('div').outAndInFade({
       // Change background-color of the element being animated on complete.    
       // Note: "this" will refer to the DOM element in the wrapper set.    
       complete: function () { $(this).css('background', '#ff9'); }
   });  </script>
</body>
</html>
Advertisement