(function (factory) {
if (typeof define === 'function' && define.amd) {
Every module - even the base - is wrapped in an UMD definition, making it easier to integrate with your development process
(function (factory) {
if (typeof define === 'function' && define.amd) {
define([], function(){
return (window.mink = factory());
});
} else if (typeof exports === 'object') {
window.mink = module.exports = factory();
} else {
window.mink = factory();
}
}(function () {
'use strict';
Gets the helper API from the current script element’s data attribute. This way, you can define a specific helper without having to create a script tag before mink.
function currentScript() {
var cs = document.currentScript;
if(!cs){
@TODO: This approach has a shortcoming with AMD loading.
var scripts = document.getElementsByTagName('script');
cs = scripts[scripts.length - 1];
}
return window[cs.getAttribute('data-helper')];
}
If mink is previously defined, we use that definition.
var mink = window.mink || {};
Programmatical definition comes first. Definition in data-attribute comes second. Zepto comes third for mobile first as a default, if present. Ender comes after as our packaged helper. jQuery after that. kInk and $ if all else fails.
mink.helper = mink.$ = mink.$ || currentScript() || window.Zepto || window.ender || window.jQuery || window.kink || window.$ || false;
Warn the user if no helper is defined
if (!mink.$) {
console.warn('No helper ($) available!');
}
This property will contain pointers to all mink module constructors
mink.fn = mink.fn || {};
This property will contain pointers to all mink module methods in the helper library
mink.$fn = mink.$fn || {};
A pointer for mink inside the helper library.
mink.$.fn.mink = mink;
mink modules default settings. These sensible defaults will be overriden by module defaults and runtime options
mink.defaults = mink.defaults || {
The name of the module
name: 'mink',
A unique identifier used to namespace events, and preserve the module instance
namespace: 'minkModule',
Whether debug content should be outputted to console
debug: false,
Whether extra debug content should be outputted
verbose: false,
Whether to track performance data
performance: false,
A flag to auto initialize the module
autoInit: true,
A flag to mark the module as being autoloaded
autoloaded: false
};
mink._registry = mink._registry || {};
mink.factory = function(defaults) {
defaults = defaults || {};
function mod(element, options){
var
$ = mink.$,
Extend settings to merge run-time settings with defaults
settings = $.extend({}, $.fn.mink.defaults, defaults, options || {}),
Alias settings object for convenience and performance
namespace = settings.namespace,
performance = [],
module = this;
Initialize the registry key for the modules if it’s not set yets
mink._registry[settings.name] = mink._registry[settings.name] || [];
this.queryArguments = [].slice.call(arguments, 3);
this.moduleNamespace = namespace;
this.eventNamespace = '.' + namespace;
A reference to all modules instances of the same type. This is useful if your module wants to interact with other instances in the page
this.allModules = mink._registry[settings.name];
this.moduleId = this.allModules.push(this) - 1;
this.element = element;
this.$element = $(element);
this.settings = settings;
this.attrs = this.$element.data();
this.instance = this.$element.data(this.moduleNamespace);
this.performance = {
log: function (message) {
var
currentTime,
executionTime,
previousTime;
if (module.settings.performance) {
currentTime = new Date().getTime();
previousTime = module.time || currentTime;
executionTime = currentTime - previousTime;
module.time = currentTime;
performance.push({
'Element': element,
'Name': message[0],
'Arguments': [].slice.call(message, 1)[0] || '',
'Execution Time': executionTime
});
}
clearTimeout(module.performance.timer);
module.performance.timer = setTimeout(module.performance.display, 100);
},
display: function () {
var
title = module.settings.name + ':',
totalTime = 0;
module.time = false;
clearTimeout(module.performance.timer);
$.each(performance, function (index, data) {
totalTime += data['Execution Time'];
});
title += ' ' + totalTime + 'ms';
if (this.moduleSelector) {
title += ' \'' + this.moduleSelector + '\'';
}
if ((console.group !== undefined || console.table !== undefined) && performance.length > 0) {
console.groupCollapsed(title);
if (console.table) {
console.table(performance);
} else {
$.each(performance, function (index, data) {
console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms');
});
}
console.groupEnd();
}
performance = [];
}
};
Initialize module if autoInit is set to true
if(settings.autoInit) {
this.initialize();
}
return this;
}
mod.prototype = {
This method is the first one to be called once a module is instantiated Every module should implement their own method.
initialize: function () {
console.warn('Your module is lacking an initialize method');
},
Every module should implement their own destroy method This method is responsible to restore the DOM to the same state as it was before the module was instantiated. This includes - but is not limited to - removing the following:
destroy: function () {
console.warn('Your module is lacking a destroy method');
},
setting: function (name, value) {
if ($.isPlainObject(name)) {
$.extend(this.settings, name);
} else if (value !== undefined) {
this.settings[name] = value;
} else {
return this.settings[name];
}
},
internal: function (name, value) {
if ($.isPlainObject(name)) {
$.extend(this, name);
} else if (value !== undefined) {
this[name] = value;
} else {
return this[name];
}
},
debug: function () {
if (this.settings.debug) {
if (this.settings.performance) {
this.performance.log(arguments);
} else {
this.debug = Function.prototype.bind.call(console.info, console, this.settings.name + ':');
this.debug.apply(console, arguments);
}
}
},
Calling verbose internally allows for additional data to be logged which can assist in debugging
verbose: function () {
if (this.settings.verbose && this.settings.debug) {
if (this.settings.performance) {
this.performance.log(arguments);
} else {
this.verbose = Function.prototype.bind.call(console.info, console, this.settings.name + ':');
this.verbose.apply(console, arguments);
}
}
},
Error allows for the module to report named error messages, it may be useful to modify this to push error messages to the user. Error messages are defined in the modules settings object.
error: function () {
this.error = Function.prototype.bind.call(console.error, console, this.settings.name + ':');
this.error.apply(console, arguments);
},
Invoke is used to match internal functions to string lookups.
$('.foo').example('invoke', 'set text', 'Foo')
Method lookups are lazy, looking for many variations of a search string
For example ‘set text’, will look for both setText : function(){}
, set: { text: function(){} }
Invoke attempts to preserve the ‘this’ chaining unless a value is returned.
If multiple values are returned an array of values matching up to the length of the selector is returned
invoke: function (query, passedArguments, context) {
var
maxDepth,
found,
response;
passedArguments = passedArguments || this.queryArguments;
context = context || this.instance || this;
var module = this;
if (typeof query === 'string' && module.instance !== undefined) {
query = query.split(/[\. ]/);
maxDepth = query.length - 1;
$.each(query, function (depth, value) {
var camelCaseValue = (depth !== maxDepth) ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) : query;
if ($.isPlainObject(module.instance[value]) && (depth !== maxDepth)) {
module.instance = module.instance[value];
} else if ($.isPlainObject(module.instance[camelCaseValue]) && (depth !== maxDepth)) {
module.instance = module.instance[camelCaseValue];
} else if (module.instance[value] !== undefined) {
found = module.instance[value];
return false;
} else if (module.instance[camelCaseValue] !== undefined) {
found = module.instance[camelCaseValue];
return false;
} else {
module.error('The method you called is not defined.', query);
return false;
}
});
}
if ( $.isFunction(found) ) {
response = found.apply(context, [passedArguments]);
}
else if (found !== undefined) {
response = found;
}
return response;
},
dataAttributes: function () {
this.debug('Setting data attributes settings');
if(this.settings.metadata){
var module = this;
$.each(this.settings.metadata, function (index, value) {
if (module.attrs[index] !== undefined) {
module.settings[index] = module.attrs[index];
}
});
}
},
throttle: function(func, delay) {
var timer = null;
return function () {
var context = this, args = arguments;
if (timer == null) {
timer = setTimeout(function () {
func.apply(context, args);
timer = null;
}, delay);
}
};
}
};
Expose the module defaults as a property of the constructor
mod._defaults = defaults;
return mod;
};
Expose module in the window, inside the mink object and as a method of the helper API
mink.expose = function (name, Constructor) {
Saves the old module definition
var old = mink.$.fn[name];
mink.fn[name] = Constructor;
Constructor._name = name;
mink.$fn[name] = mink.$.fn[name] = function (parameters) {
var
Store a reference to the module group, this can be useful to refer to other modules inside each module
$allModules = $(this),
Preserve original arguments to determine if a method is being invoked
query = arguments[0],
Check if we’ve invoked a method
methodInvoked = (typeof query === 'string'),
returnedValue;
Iterate over all elements to initialize module
$allModules
.each(function () {
var settings = ($.isPlainObject(parameters)) ? parameters : {};
settings.autoInit = false;
var module = new Constructor(this, settings);
if (methodInvoked) {
if (module.instance === undefined) {
module.initialize();
}
var response = module.invoke(query);
If a user passes in multiple elements invoke will be called for each element and the value will be returned in an array
For example $('.things').example('has text')
with two elements might return [true, false]
and for one element true
if ($.isArray(returnedValue)) {
returnedValue.push(response);
} else if (returnedValue !== undefined) {
returnedValue = [returnedValue, response];
} else if (response !== undefined) {
returnedValue = response;
}
}
if no method call is required we simply initialize the plugin, destroying it if it exists already
else {
if (module.instance !== undefined) {
module.destroy();
}
module.initialize();
}
});
if ( returnedValue !== undefined ){
return returnedValue;
} else {
return this;
}
};
Expose the constructor
mink.$.fn[name].constructor = Constructor;
mink.$.fn[name].noConflict = function () {
mink.$.fn[name] = old;
return this;
};
};
return mink;
}));