/* Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype (function(){ var initializing = false; // The base JQClass implementation (does nothing) window.JQClass = function(){}; // Collection of derived classes JQClass.classes = {}; // Create a new JQClass that inherits from this class JQClass.extend = function extender(prop) { var base = this.prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor) initializing = true; var prototype = new this(); initializing = false; // Copy the properties over onto the new prototype for (var name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == 'function' && typeof base[name] == 'function' ? (function(name, fn){ return function() { var __super = this._super; // Add a new ._super() method that is the same method // but on the super-class this._super = function(args) { return base[name].apply(this, args || []); }; var ret = fn.apply(this, arguments); // The method only need to be bound temporarily, so we // remove it when we're done executing this._super = __super; return ret; }; })(name, prop[name]) : prop[name]; } // The dummy class constructor function JQClass() { // All construction is actually done in the init method if (!initializing && this._init) { this._init.apply(this, arguments); } } // Populate our constructed prototype object JQClass.prototype = prototype; // Enforce the constructor to be what we expect JQClass.prototype.constructor = JQClass; // And make this class extendable JQClass.extend = extender; return JQClass; }; })(); (function($) { // Ensure $, encapsulate /** Abstract base class for collection plugins v1.0.1. Written by Keith Wood (kbwood{at}iinet.com.au) December 2013. Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license. @module $.JQPlugin @abstract */ JQClass.classes.JQPlugin = JQClass.extend({ /** Name to identify this plugin. @example name: 'tabs' */ name: 'plugin', /** Default options for instances of this plugin (default: {}). @example defaultOptions: { selectedClass: 'selected', triggers: 'click' } */ defaultOptions: {}, /** Options dependent on the locale. Indexed by language and (optional) country code, with '' denoting the default language (English/US). @example regionalOptions: { '': { greeting: 'Hi' } } */ regionalOptions: {}, /** Names of getter methods - those that can't be chained (default: []). @example _getters: ['activeTab'] */ _getters: [], /** Retrieve a marker class for affected elements. @private @return {string} The marker class. */ _getMarker: function() { return 'is-' + this.name; }, /** Initialise the plugin. Create the jQuery bridge - plugin name xyz produces $.xyz and $.fn.xyz. */ _init: function() { // Apply default localisations $.extend(this.defaultOptions, (this.regionalOptions && this.regionalOptions['']) || {}); // Camel-case the name var jqName = camelCase(this.name); // Expose jQuery singleton manager $[jqName] = this; // Expose jQuery collection plugin $.fn[jqName] = function(options) { var otherArgs = Array.prototype.slice.call(arguments, 1); if ($[jqName]._isNotChained(options, otherArgs)) { return $[jqName][options].apply($[jqName], [this[0]].concat(otherArgs)); } return this.each(function() { if (typeof options === 'string') { if (options[0] === '_' || !$[jqName][options]) { throw 'Unknown method: ' + options; } $[jqName][options].apply($[jqName], [this].concat(otherArgs)); } else { $[jqName]._attach(this, options); } }); }; }, /** Set default values for all subsequent instances. @param options {object} The new default options. @example $.plugin.setDefauls({name: value}) */ setDefaults: function(options) { $.extend(this.defaultOptions, options || {}); }, /** Determine whether a method is a getter and doesn't permit chaining. @private @param name {string} The method name. @param otherArgs {any[]} Any other arguments for the method. @return {boolean} True if this method is a getter, false otherwise. */ _isNotChained: function(name, otherArgs) { if (name === 'option' && (otherArgs.length === 0 || (otherArgs.length === 1 && typeof otherArgs[0] === 'string'))) { return true; } return $.inArray(name, this._getters) > -1; }, /** Initialise an element. Called internally only. Adds an instance object as data named for the plugin. @param elem {Element} The element to enhance. @param options {object} Overriding settings. */ _attach: function(elem, options) { elem = $(elem); if (elem.hasClass(this._getMarker())) { return; } elem.addClass(this._getMarker()); options = $.extend({}, this.defaultOptions, this._getMetadata(elem), options || {}); var inst = $.extend({name: this.name, elem: elem, options: options}, this._instSettings(elem, options)); elem.data(this.name, inst); // Save instance against element this._postAttach(elem, inst); this.option(elem, options); }, /** Retrieve additional instance settings. Override this in a sub-class to provide extra settings. @param elem {jQuery} The current jQuery element. @param options {object} The instance options. @return {object} Any extra instance values. @example _instSettings: function(elem, options) { return {nav: elem.find(options.navSelector)}; } */ _instSettings: function(elem, options) { return {}; }, /** Plugin specific post initialisation. Override this in a sub-class to perform extra activities. @param elem {jQuery} The current jQuery element. @param inst {object} The instance settings. @example _postAttach: function(elem, inst) { elem.on('click.' + this.name, function() { ... }); } */ _postAttach: function(elem, inst) { }, /** Retrieve metadata configuration from the element. Metadata is specified as an attribute: data-<plugin name>="<setting name>: '<value>', ...". Dates should be specified as strings in this format: 'new Date(y, m-1, d)'. @private @param elem {jQuery} The source element. @return {object} The inline configuration or {}. */ _getMetadata: function(elem) { try { var data = elem.data(this.name.toLowerCase()) || ''; data = data.replace(/'/g, '"'); data = data.replace(/([a-zA-Z0-9]+):/g, function(match, group, i) { var count = data.substring(0, i).match(/"/g); // Handle embedded ':' return (!count || count.length % 2 === 0 ? '"' + group + '":' : group + ':'); }); data = $.parseJSON('{' + data + '}'); for (var name in data) { // Convert dates var value = data[name]; if (typeof value === 'string' && value.match(/^new Date\((.*)\)$/)) { data[name] = eval(value); } } return data; } catch (e) { return {}; } }, /** Retrieve the instance data for element. @param elem {Element} The source element. @return {object} The instance data or {}. */ _getInst: function(elem) { return $(elem).data(this.name) || {}; }, /** Retrieve or reconfigure the settings for a plugin. @param elem {Element} The source element. @param name {object|string} The collection of new option values or the name of a single option. @param [value] {any} The value for a single named option. @return {any|object} If retrieving a single value or all options. @example $(selector).plugin('option', 'name', value) $(selector).plugin('option', {name: value, ...}) var value = $(selector).plugin('option', 'name') var options = $(selector).plugin('option') */ option: function(elem, name, value) { elem = $(elem); var inst = elem.data(this.name); if (!name || (typeof name === 'string' && value == null)) { var options = (inst || {}).options; return (options && name ? options[name] : options); } if (!elem.hasClass(this._getMarker())) { return; } var options = name || {}; if (typeof name === 'string') { options = {}; options[name] = value; } this._optionsChanged(elem, inst, options); $.extend(inst.options, options); }, /** Plugin specific options processing. Old value available in inst.options[name], new value in options[name]. Override this in a sub-class to perform extra activities. @param elem {jQuery} The current jQuery element. @param inst {object} The instance settings. @param options {object} The new options. @example _optionsChanged: function(elem, inst, options) { if (options.name != inst.options.name) { elem.removeClass(inst.options.name).addClass(options.name); } } */ _optionsChanged: function(elem, inst, options) { }, /** Remove all trace of the plugin. Override _preDestroy for plugin-specific processing. @param elem {Element} The source element. @example $(selector).plugin('destroy') */ destroy: function(elem) { elem = $(elem); if (!elem.hasClass(this._getMarker())) { return; } this._preDestroy(elem, this._getInst(elem)); elem.removeData(this.name).removeClass(this._getMarker()); }, /** Plugin specific pre destruction. Override this in a sub-class to perform extra activities and undo everything that was done in the _postAttach or _optionsChanged functions. @param elem {jQuery} The current jQuery element. @param inst {object} The instance settings. @example _preDestroy: function(elem, inst) { elem.off('.' + this.name); } */ _preDestroy: function(elem, inst) { } }); /** Convert names from hyphenated to camel-case. @private @param value {string} The original hyphenated name. @return {string} The camel-case version. */ function camelCase(name) { return name.replace(/-([a-z])/g, function(match, group) { return group.toUpperCase(); }); } /** Expose the plugin base. @namespace "$.JQPlugin" */ $.JQPlugin = { /** Create a new collection plugin. @memberof "$.JQPlugin" @param [superClass='JQPlugin'] {string} The name of the parent class to inherit from. @param overrides {object} The property/function overrides for the new class. @example $.JQPlugin.createPlugin({ name: 'tabs', defaultOptions: {selectedClass: 'selected'}, _initSettings: function(elem, options) { return {...}; }, _postAttach: function(elem, inst) { ... } }); */ createPlugin: function(superClass, overrides) { if (typeof superClass === 'object') { overrides = superClass; superClass = 'JQPlugin'; } superClass = camelCase(superClass); var className = camelCase(overrides.name); JQClass.classes[className] = JQClass.classes[superClass].extend(overrides); new JQClass.classes[className](); } }; })(jQuery);