/* globals JQClass */ /*! Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype (function(){ 'use strict'; 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) { // jshint loopfunc:true // Check if we're overwriting an existing function if (typeof prop[name] === 'function' && typeof base[name] === 'function') { prototype[name] = (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 needs to be bound temporarily, so we remove it when we're done executing this._super = __super; return ret; }; })(name, prop[name]); // Check if we're overwriting existing default options. } else if (typeof prop[name] === 'object' && typeof base[name] === 'object' && name === 'defaultOptions') { var obj1 = base[name]; var obj2 = prop[name]; var obj3 = {}; var key; for (key in obj1) { // jshint forin:false obj3[key] = obj1[key]; } for (key in obj2) { // jshint forin:false obj3[key] = obj2[key]; } prototype[name] = obj3; } else { prototype[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; }; })(); /*! Abstract base class for collection plugins v1.0.2. Written by Keith Wood (wood.keith{at}optusnet.com.au) December 2013. Licensed under the MIT license (http://keith-wood.name/licence.html). */ (function($) { // Ensure $, encapsulate 'use strict'; /**

Abstract base class for collection plugins v1.0.2.

Written by Keith Wood (wood.keith{at}optusnet.com.au) December 2013.

Licensed under the MIT license (http://keith-wood.name/licence.html).

Use {@link $.JQPlugin.createPlugin} to create new plugins using this framework.

This base class provides common functionality such as:

@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). Normally additional languages would be provided as separate files to all them to be included as needed. @example regionalOptions: { '': { greeting: 'Hi' } } */ regionalOptions: {}, /** Whether or not a deep merge should be performed when accumulating options. The default is true but can be overridden in a sub-class. */ deepMerge: true, /** Retrieve a marker class for affected elements. In the format: is-<pluginName>. @protected @return {string} The marker class. */ _getMarker: function() { return 'is-' + this.name; }, /** Initialise the plugin. Create the jQuery bridge - plugin name xyz produces singleton $.xyz and collection function $.fn.xyz. @protected */ _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); var inst = this; var returnValue = this; this.each(function () { if (typeof options === 'string') { if (options[0] === '_' || !$[jqName][options]) { throw 'Unknown method: ' + options; } var methodValue = $[jqName][options].apply($[jqName], [this].concat(otherArgs)); if (methodValue !== inst && methodValue !== undefined) { returnValue = methodValue; return false; } } else { $[jqName]._attach(this, options); } }); return returnValue; }; }, /** Set default options for all subsequent instances. @param {object} options The new default options. @example $.pluginName.setDefaults({name: value, ...}) */ setDefaults: function(options) { $.extend(this.defaultOptions, options || {}); }, /** Initialise an element. Called internally only. Adds an instance object as data named for the plugin. Override {@linkcode module:JQPlugin~_postAttach|_postAttach} for plugin-specific processing. @private @param {Element} elem The element to enhance. @param {object} options Overriding settings. */ _attach: function(elem, options) { elem = $(elem); if (elem.hasClass(this._getMarker())) { return; } elem.addClass(this._getMarker()); options = $.extend(this.deepMerge, {}, 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. These are added directly to the instance object. Default attributes of an instance object are shown as properties below: @protected @param {jQuery} elem The current jQuery element. @param {object} options The instance options. @return {object} Any extra instance values. @property {Element} elem The element to which this instance applies. @property {string} name The name of this plugin. @property {object} options The accumulated options for this instance. @example _instSettings: function(elem, options) { return {nav: elem.find(options.navSelector)}; } */ _instSettings: function(elem, options) { // jshint unused:false return {}; }, /** Plugin specific post initialisation. Override this in a sub-class to perform extra activities. This is where you would implement your plugin's main functionality. @protected @param {jQuery} elem The current jQuery element. @param {object} inst The instance settings. @example _postAttach: function(elem, inst) { elem.on('click.' + this.name, function() { ... }); } */ _postAttach: function(elem, inst) { // jshint unused:false }, /** Retrieve metadata configuration from the element. Metadata is specified as an attribute: data-<pluginName>="<option name>: '<value>', ...". Dates should be specified as strings in this format: 'new Date(y, m-1, d)'. @private @param {jQuery} elem The source element. @return {object} The inline configuration or {}. */ _getMetadata: function(elem) { try { var data = elem.data(this.name.toLowerCase()) || ''; data = data.replace(/(\\?)'/g, function(e, t) { return t ? '\'' : '"'; }).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 + ':'); }).replace(/\\:/g, ':'); data = $.parseJSON('{' + data + '}'); for (var key in data) { if (data.hasOwnProperty(key)) { var value = data[key]; if (typeof value === 'string' && value.match(/^new Date\(([-0-9,\s]*)\)$/)) { // Convert dates data[key] = eval(value); // jshint ignore:line } } } return data; } catch (e) { return {}; } }, /** Retrieve the instance data for element. @protected @param {Element} elem The source element. @return {object} The instance data or {} if none. */ _getInst: function(elem) { return $(elem).data(this.name) || {}; }, /** Retrieve or reconfigure the settings for a plugin. If new settings are provided they are applied to the instance options. If an option name only is provided the value of that option is returned. If no name or value is provided, all options are returned. Override {@linkcode module:JQPlugin~_optionsChanged|_optionsChanged} for plugin-specific processing when option values change. @param {Element} elem The source element. @param {object|string} [name] The collection of new option values or the name of a single option. @param {any} [value] The value for a single named option. @return {any|object} If retrieving a single value or all options. @example $(selector).plugin('option', 'name', value) // Set one option $(selector).plugin('option', {name: value, ...}) // Set multiple options var value = $(selector).plugin('option', 'name') // Get one option var options = $(selector).plugin('option') // Get all options */ option: function(elem, name, value) { elem = $(elem); var inst = elem.data(this.name); var options = name || {}; if (!name || (typeof name === 'string' && typeof value === 'undefined')) { options = (inst || {}).options; return (options && name ? options[name] : options); } if (!elem.hasClass(this._getMarker())) { return; } 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. @protected @param {jQuery} elem The current jQuery element. @param {object} inst The instance settings. @param {object} options 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) { // jshint unused:false }, /** Remove all trace of the plugin. Override {@linkcode module:JQPlugin~_preDestroy|_preDestroy} for plugin-specific processing. @param {Element} elem 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. It is invoked as part of the {@linkcode module:JQPlugin~destroy|destroy} processing. Override this in a sub-class to perform extra activities and undo everything that was done in the {@linkcode module:JQPlugin~_postAttach|_postAttach} or {@linkcode module:JQPlugin~_optionsChanged|_optionsChanged} functions. @protected @param {jQuery} elem The current jQuery element. @param {object} inst The instance settings. @example _preDestroy: function(elem, inst) { elem.off('.' + this.name); } */ _preDestroy: function(elem, inst) { // jshint unused:false } }); /** Convert names from hyphenated to camel-case. @private @param {string} value 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 {string} [superClass='JQPlugin'] The name of the parent class to inherit from. @param {object} overrides The property/function overrides for the new class. See {@link module:JQPlugin|JQPlugin} for the base functionality. @example $.JQPlugin.createPlugin({ // Define the plugin name: 'tabs', defaultOptions: {selectedClass: 'selected'}, _initSettings: function(elem, options) { return {...}; }, _postAttach: function(elem, inst) { ... } }); $('selector').tabs(); // And instantiate it */ 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](); // jshint ignore:line } }; })(jQuery);