jQuery - Plugin Template
Updated at 2012-11-22 15:30
jQuery plugins are components that extend jQuery's functionality by providing extra functions for $ jQuery super object. jQuery plugins should follow the community standards as does this template. I have included annotations about almost every line of code.
// Following function definition style is called immediately invoked
// function expression a.k.a. IIFE.
// Parenthesis wrapping ensures that if a malformed plugin forgets to
// includea final semicolon ; there aren't errors in production when
// the scripts get concatenated and minimized.
// Taking window, etc... as parameters helps compressing and performance.
// You can refer document with 'doc' etc.
// Taking jQuery helps to avoid conflicts with other libraries that
// use dollar sign $.
// Note that undefined is asked, although it is not given, thus it is
// the 'real' undefined.
/**
* Plugin Name
*
* Plugin description followed by usage examples.
*
* @example
* // Initialization
* $('#branch-list').ruksiPlugin();
*
*/
(function($, window, document, undefined) {
// Run in strict mode e.g. throws exceptions on global usage.
'use strict';
// Variable that is same for all instances of this plugin.
// Does not get re-initialized when new instance is created.
// Useful if you require some communication between different
// instances of this plugin. Even extending libraries cannot get
// to this variable by default. You can also save variables to
// $.fn[] like default options are, they can be extended.
var sharedVariable = {};
/**
* Ruksi Plugin
*
* This is comment for plugin constructor.
* This function is always ran before any function calls.
* The full plugin is defined by extending the prototype of this
* function. All extending functions should be commented.
*
* @param {Object} element
* DOM element e.g. div or ul.
* @param {Object} options
* Construction options.
*
* @constructor
*/
function RuksiPlugin(element, options) {
// Raw DOM element object for this instance.
this.element = element;
// jQuery wrapped DOM element object.
// All jQuery functions require jQuery wrapped object.
this.$element = $(element);
// Creates final options to be used.
// Fetches possible options from multiple sources and fills options
// with default values.
this.options = this._createOptions(options);
// Variable that is different for all instances of this plugin.
this.instanceVariable = {};
}
// Make prototype shorter to write.
var thisProto = RuksiPlugin.prototype;
// If you would like to inherit some other plugin, pass it's constructor
// to IIFE and create new instance of it to use as prototype.
// thisProto = new OtherPlugin();
/**
* Create Options
*
* Create final options that will be used for this instance.
*
* @param {Object|undefined} options
* Option object given to constructor.
*
* @return {Object}
*
* @private
*/
thisProto._createOptions = function( options ) {
// This function is prefixed with _ to mean
// that it should not be called outside this plugin a.k.a. private.
// Merges together:
// - An empty object, $.extend mangles this.
// - Default options saved in jQuery.
// - Options object given to the constructor.
// - All 'data-*' HTML attributes in the DOM element.
// You could also validate or manipulate options before returning
// them.
var defaults = $.ruksiPlugin.defaultOptions;
var dataAttributes = this.$element.data();
return $.extend({}, defaults, options, dataAttributes);
};
/**
* Next follows few functions to understand how to write functions
* that can be accessed outside this plugin and what breaks jQuery
* chaining and what does not.
*
* As a rule, all functions that return a value should break chaining.
* This should include all functions that start with 'get'.
*/
// ALLOWS chaining to continue because returns itself.
thisProto.echoParameter = function(parameter) {
console.log(parameter);
var x = 23;
};
thisProto.echoOptions = function() {
console.log(this.options);
return;
};
thisProto.echoThis = function() {
console.log(this);
};
thisProto.doSomethingToOptions = function() {
this.options.something = 'was done';
return this;
};
// BREAKS chaining because returns something else than itself.
thisProto.getParameter = function(parameter) {
return parameter;
};
thisProto.getOptions = function() {
return this.options;
};
thisProto.getElement = function() {
return this.element;
};
thisProto.get$Element = function() {
return this.$element;
};
/**
* jQuery plugin calling logic.
*
* If first parameter is object AND not yet constructed:
* - This is a constructor call and first parameter is constructor
* options.
*
* If first parameter is undefined AND not yet constructed:
* - This is a constructor call and no constructor options are
* specified.
*
* If first parameter is string AND not yet constructed:
* - This is a function call but construct the object without
* options first.
*
* If first parameter is string:
* - This is a plugin function call and all possible following
* parameters are parameters for the plugin function call.
*
* @param {String|Object|undefined} parameter
* Specify what this plugin call is about.
*
* @return {Object|*}
* Matching jQuery objects for chaining if call does not return
* anything. If plugin function call returns anything, return first
* value received, even if we will invoke the function on all
* matching jQuery objects.
*/
$.fn.ruksiPlugin = function(parameter) {
// Save all extra arguments after 'parameter' for the possible
// function call. We could check if parameter is a string but
// this is more clear.
var extraArguments = Array.prototype.slice.call(arguments, 1);
// The action (construction and/or function call) should be done to
// all elements that match the used jQuery selector.
// If any actions returns anything except themselves, this function
// call should return only value of the first function response.
// That is how jQuery functions work, e.g. '$('div').attr('id')'
// only returns id of __the first element__ that matches the
// selector.
// If the invoked plugin functions do not return anything except
// themselves, we can return collection of the matching elements
// to maintain jQuery's famous chaining.
var firstResponse = undefined;
// For each jQuery element that was selected...
// Also return all of the selected jQuery objects to
// maintain chaining.
var matchingElements = this.each( function() {
// DOM element object wrapped in jQuery
// and jQuery data of our plugin for this specific element.
var $element = $(this);
var jqData = $element.data('ruksiPlugin');
// If jQuery does not have any this plugin specific data saved
// for this DOM element, the plugin has not been initialize
// for this specific element.
if ( ! jqData ) {
var options = {};
// If 'parameter' is object, they are options for
// the constructor.
if ( $.isPlainObject(parameter) ) {
options = parameter;
}
// Save the new plugin object instance to jQuery data for
// this DOM element. Also make sure that 'jqData' variable
// has the freshly created object as we might call it below.
jqData = new RuksiPlugin(this, options);
$element.data('ruksiPlugin', jqData);
}
// If 'parameter' is a string, this should be a function call.
// The object was created above if it was not existing before.
if ( typeof parameter === 'string' ) {
if ( $.isFunction(jqData[parameter]) ) {
var response = jqData[parameter].apply(jqData, extraArguments);
// If function returns anything else than itself
// and we do not yet have a first response, save it
// to be returned.
if (response !== jqData && firstResponse === undefined) {
firstResponse = response;
}
}
else {
var message = parameter + ' not function of ruksiPlugin.';
console.error(msg);
}
}
});
// Determine what to return to the plugin caller.
// If no function returned anything else than themselves,
// return all matching jQuery elements for chaining.
// If any of the matching jQuery objects returned anything else
// than themselves on the function call, return the first value
// that was returned.
return (firstResponse === undefined ? matchingElements : firstResponse);
};
/**
* Default options for this plugin.
* If nothing else is found, these values are used.
* Default options should be saved to the $.fn[] so they can be globally
* read or modified at runtime e.g if someone wants to change global
* defaults or extend the plugin.
*
* @type {Object}
*/
$.fn.ruksiPlugin.defaultOptions = {
variableNamesShouldNotBeReservedWords: 'Will cause problems in IE',
klass: 'not class because it is reserved'
};
// Immediate function is called with these arguments...
}(window.jQuery, window, document));
/**
* Usage
*/
$('body').ruksiPlugin(); // Optional, will initialize on first func call.
$('body').ruksiPlugin('echoOptions');
$('body').ruksiPlugin('getParameter', 'this is the parameter');
// Functions that do not return anything allow jQuery chaining.
$('div').ruksiPlugin('echoOptions').hide();
$('div').ruksiPlugin('doSomethingToOptions').show();