338 lines
12 KiB
JavaScript
338 lines
12 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
import $ from 'jquery';
|
||
|
import { GetYoDigits } from './foundation.core.utils';
|
||
|
import { MediaQuery } from './foundation.util.mediaQuery';
|
||
|
|
||
|
var FOUNDATION_VERSION = '6.5.1';
|
||
|
|
||
|
// Global Foundation object
|
||
|
// This is attached to the window, or used as a module for AMD/Browserify
|
||
|
var Foundation = {
|
||
|
version: FOUNDATION_VERSION,
|
||
|
|
||
|
/**
|
||
|
* Stores initialized plugins.
|
||
|
*/
|
||
|
_plugins: {},
|
||
|
|
||
|
/**
|
||
|
* Stores generated unique ids for plugin instances
|
||
|
*/
|
||
|
_uuids: [],
|
||
|
|
||
|
/**
|
||
|
* Defines a Foundation plugin, adding it to the `Foundation` namespace and the list of plugins to initialize when reflowing.
|
||
|
* @param {Object} plugin - The constructor of the plugin.
|
||
|
*/
|
||
|
plugin: function(plugin, name) {
|
||
|
// Object key to use when adding to global Foundation object
|
||
|
// Examples: Foundation.Reveal, Foundation.OffCanvas
|
||
|
var className = (name || functionName(plugin));
|
||
|
// Object key to use when storing the plugin, also used to create the identifying data attribute for the plugin
|
||
|
// Examples: data-reveal, data-off-canvas
|
||
|
var attrName = hyphenate(className);
|
||
|
|
||
|
// Add to the Foundation object and the plugins list (for reflowing)
|
||
|
this._plugins[attrName] = this[className] = plugin;
|
||
|
},
|
||
|
/**
|
||
|
* @function
|
||
|
* Populates the _uuids array with pointers to each individual plugin instance.
|
||
|
* Adds the `zfPlugin` data-attribute to programmatically created plugins to allow use of $(selector).foundation(method) calls.
|
||
|
* Also fires the initialization event for each plugin, consolidating repetitive code.
|
||
|
* @param {Object} plugin - an instance of a plugin, usually `this` in context.
|
||
|
* @param {String} name - the name of the plugin, passed as a camelCased string.
|
||
|
* @fires Plugin#init
|
||
|
*/
|
||
|
registerPlugin: function(plugin, name){
|
||
|
var pluginName = name ? hyphenate(name) : functionName(plugin.constructor).toLowerCase();
|
||
|
plugin.uuid = GetYoDigits(6, pluginName);
|
||
|
|
||
|
if(!plugin.$element.attr(`data-${pluginName}`)){ plugin.$element.attr(`data-${pluginName}`, plugin.uuid); }
|
||
|
if(!plugin.$element.data('zfPlugin')){ plugin.$element.data('zfPlugin', plugin); }
|
||
|
/**
|
||
|
* Fires when the plugin has initialized.
|
||
|
* @event Plugin#init
|
||
|
*/
|
||
|
plugin.$element.trigger(`init.zf.${pluginName}`);
|
||
|
|
||
|
this._uuids.push(plugin.uuid);
|
||
|
|
||
|
return;
|
||
|
},
|
||
|
/**
|
||
|
* @function
|
||
|
* Removes the plugins uuid from the _uuids array.
|
||
|
* Removes the zfPlugin data attribute, as well as the data-plugin-name attribute.
|
||
|
* Also fires the destroyed event for the plugin, consolidating repetitive code.
|
||
|
* @param {Object} plugin - an instance of a plugin, usually `this` in context.
|
||
|
* @fires Plugin#destroyed
|
||
|
*/
|
||
|
unregisterPlugin: function(plugin){
|
||
|
var pluginName = hyphenate(functionName(plugin.$element.data('zfPlugin').constructor));
|
||
|
|
||
|
this._uuids.splice(this._uuids.indexOf(plugin.uuid), 1);
|
||
|
plugin.$element.removeAttr(`data-${pluginName}`).removeData('zfPlugin')
|
||
|
/**
|
||
|
* Fires when the plugin has been destroyed.
|
||
|
* @event Plugin#destroyed
|
||
|
*/
|
||
|
.trigger(`destroyed.zf.${pluginName}`);
|
||
|
for(var prop in plugin){
|
||
|
plugin[prop] = null;//clean up script to prep for garbage collection.
|
||
|
}
|
||
|
return;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @function
|
||
|
* Causes one or more active plugins to re-initialize, resetting event listeners, recalculating positions, etc.
|
||
|
* @param {String} plugins - optional string of an individual plugin key, attained by calling `$(element).data('pluginName')`, or string of a plugin class i.e. `'dropdown'`
|
||
|
* @default If no argument is passed, reflow all currently active plugins.
|
||
|
*/
|
||
|
reInit: function(plugins){
|
||
|
var isJQ = plugins instanceof $;
|
||
|
try{
|
||
|
if(isJQ){
|
||
|
plugins.each(function(){
|
||
|
$(this).data('zfPlugin')._init();
|
||
|
});
|
||
|
}else{
|
||
|
var type = typeof plugins,
|
||
|
_this = this,
|
||
|
fns = {
|
||
|
'object': function(plgs){
|
||
|
plgs.forEach(function(p){
|
||
|
p = hyphenate(p);
|
||
|
$('[data-'+ p +']').foundation('_init');
|
||
|
});
|
||
|
},
|
||
|
'string': function(){
|
||
|
plugins = hyphenate(plugins);
|
||
|
$('[data-'+ plugins +']').foundation('_init');
|
||
|
},
|
||
|
'undefined': function(){
|
||
|
this['object'](Object.keys(_this._plugins));
|
||
|
}
|
||
|
};
|
||
|
fns[type](plugins);
|
||
|
}
|
||
|
}catch(err){
|
||
|
console.error(err);
|
||
|
}finally{
|
||
|
return plugins;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Initialize plugins on any elements within `elem` (and `elem` itself) that aren't already initialized.
|
||
|
* @param {Object} elem - jQuery object containing the element to check inside. Also checks the element itself, unless it's the `document` object.
|
||
|
* @param {String|Array} plugins - A list of plugins to initialize. Leave this out to initialize everything.
|
||
|
*/
|
||
|
reflow: function(elem, plugins) {
|
||
|
|
||
|
// If plugins is undefined, just grab everything
|
||
|
if (typeof plugins === 'undefined') {
|
||
|
plugins = Object.keys(this._plugins);
|
||
|
}
|
||
|
// If plugins is a string, convert it to an array with one item
|
||
|
else if (typeof plugins === 'string') {
|
||
|
plugins = [plugins];
|
||
|
}
|
||
|
|
||
|
var _this = this;
|
||
|
|
||
|
// Iterate through each plugin
|
||
|
$.each(plugins, function(i, name) {
|
||
|
// Get the current plugin
|
||
|
var plugin = _this._plugins[name];
|
||
|
|
||
|
// Localize the search to all elements inside elem, as well as elem itself, unless elem === document
|
||
|
var $elem = $(elem).find('[data-'+name+']').addBack('[data-'+name+']');
|
||
|
|
||
|
// For each plugin found, initialize it
|
||
|
$elem.each(function() {
|
||
|
var $el = $(this),
|
||
|
opts = {};
|
||
|
// Don't double-dip on plugins
|
||
|
if ($el.data('zfPlugin')) {
|
||
|
console.warn("Tried to initialize "+name+" on an element that already has a Foundation plugin.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if($el.attr('data-options')){
|
||
|
var thing = $el.attr('data-options').split(';').forEach(function(e, i){
|
||
|
var opt = e.split(':').map(function(el){ return el.trim(); });
|
||
|
if(opt[0]) opts[opt[0]] = parseValue(opt[1]);
|
||
|
});
|
||
|
}
|
||
|
try{
|
||
|
$el.data('zfPlugin', new plugin($(this), opts));
|
||
|
}catch(er){
|
||
|
console.error(er);
|
||
|
}finally{
|
||
|
return;
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
getFnName: functionName,
|
||
|
|
||
|
addToJquery: function($) {
|
||
|
// TODO: consider not making this a jQuery function
|
||
|
// TODO: need way to reflow vs. re-initialize
|
||
|
/**
|
||
|
* The Foundation jQuery method.
|
||
|
* @param {String|Array} method - An action to perform on the current jQuery object.
|
||
|
*/
|
||
|
var foundation = function(method) {
|
||
|
var type = typeof method,
|
||
|
$noJS = $('.no-js');
|
||
|
|
||
|
if($noJS.length){
|
||
|
$noJS.removeClass('no-js');
|
||
|
}
|
||
|
|
||
|
if(type === 'undefined'){//needs to initialize the Foundation object, or an individual plugin.
|
||
|
MediaQuery._init();
|
||
|
Foundation.reflow(this);
|
||
|
}else if(type === 'string'){//an individual method to invoke on a plugin or group of plugins
|
||
|
var args = Array.prototype.slice.call(arguments, 1);//collect all the arguments, if necessary
|
||
|
var plugClass = this.data('zfPlugin');//determine the class of plugin
|
||
|
|
||
|
if(typeof plugClass !== 'undefined' && typeof plugClass[method] !== 'undefined'){//make sure both the class and method exist
|
||
|
if(this.length === 1){//if there's only one, call it directly.
|
||
|
plugClass[method].apply(plugClass, args);
|
||
|
}else{
|
||
|
this.each(function(i, el){//otherwise loop through the jQuery collection and invoke the method on each
|
||
|
plugClass[method].apply($(el).data('zfPlugin'), args);
|
||
|
});
|
||
|
}
|
||
|
}else{//error for no class or no method
|
||
|
throw new ReferenceError("We're sorry, '" + method + "' is not an available method for " + (plugClass ? functionName(plugClass) : 'this element') + '.');
|
||
|
}
|
||
|
}else{//error for invalid argument type
|
||
|
throw new TypeError(`We're sorry, ${type} is not a valid parameter. You must use a string representing the method you wish to invoke.`);
|
||
|
}
|
||
|
return this;
|
||
|
};
|
||
|
$.fn.foundation = foundation;
|
||
|
return $;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Foundation.util = {
|
||
|
/**
|
||
|
* Function for applying a debounce effect to a function call.
|
||
|
* @function
|
||
|
* @param {Function} func - Function to be called at end of timeout.
|
||
|
* @param {Number} delay - Time in ms to delay the call of `func`.
|
||
|
* @returns function
|
||
|
*/
|
||
|
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);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
window.Foundation = Foundation;
|
||
|
|
||
|
// Polyfill for requestAnimationFrame
|
||
|
(function() {
|
||
|
if (!Date.now || !window.Date.now)
|
||
|
window.Date.now = Date.now = function() { return new Date().getTime(); };
|
||
|
|
||
|
var vendors = ['webkit', 'moz'];
|
||
|
for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
|
||
|
var vp = vendors[i];
|
||
|
window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
|
||
|
window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
|
||
|
|| window[vp+'CancelRequestAnimationFrame']);
|
||
|
}
|
||
|
if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent)
|
||
|
|| !window.requestAnimationFrame || !window.cancelAnimationFrame) {
|
||
|
var lastTime = 0;
|
||
|
window.requestAnimationFrame = function(callback) {
|
||
|
var now = Date.now();
|
||
|
var nextTime = Math.max(lastTime + 16, now);
|
||
|
return setTimeout(function() { callback(lastTime = nextTime); },
|
||
|
nextTime - now);
|
||
|
};
|
||
|
window.cancelAnimationFrame = clearTimeout;
|
||
|
}
|
||
|
/**
|
||
|
* Polyfill for performance.now, required by rAF
|
||
|
*/
|
||
|
if(!window.performance || !window.performance.now){
|
||
|
window.performance = {
|
||
|
start: Date.now(),
|
||
|
now: function(){ return Date.now() - this.start; }
|
||
|
};
|
||
|
}
|
||
|
})();
|
||
|
if (!Function.prototype.bind) {
|
||
|
Function.prototype.bind = function(oThis) {
|
||
|
if (typeof this !== 'function') {
|
||
|
// closest thing possible to the ECMAScript 5
|
||
|
// internal IsCallable function
|
||
|
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
|
||
|
}
|
||
|
|
||
|
var aArgs = Array.prototype.slice.call(arguments, 1),
|
||
|
fToBind = this,
|
||
|
fNOP = function() {},
|
||
|
fBound = function() {
|
||
|
return fToBind.apply(this instanceof fNOP
|
||
|
? this
|
||
|
: oThis,
|
||
|
aArgs.concat(Array.prototype.slice.call(arguments)));
|
||
|
};
|
||
|
|
||
|
if (this.prototype) {
|
||
|
// native functions don't have a prototype
|
||
|
fNOP.prototype = this.prototype;
|
||
|
}
|
||
|
fBound.prototype = new fNOP();
|
||
|
|
||
|
return fBound;
|
||
|
};
|
||
|
}
|
||
|
// Polyfill to get the name of a function in IE9
|
||
|
function functionName(fn) {
|
||
|
if (typeof Function.prototype.name === 'undefined') {
|
||
|
var funcNameRegex = /function\s([^(]{1,})\(/;
|
||
|
var results = (funcNameRegex).exec((fn).toString());
|
||
|
return (results && results.length > 1) ? results[1].trim() : "";
|
||
|
}
|
||
|
else if (typeof fn.prototype === 'undefined') {
|
||
|
return fn.constructor.name;
|
||
|
}
|
||
|
else {
|
||
|
return fn.prototype.constructor.name;
|
||
|
}
|
||
|
}
|
||
|
function parseValue(str){
|
||
|
if ('true' === str) return true;
|
||
|
else if ('false' === str) return false;
|
||
|
else if (!isNaN(str * 1)) return parseFloat(str);
|
||
|
return str;
|
||
|
}
|
||
|
// Convert PascalCase to kebab-case
|
||
|
// Thank you: http://stackoverflow.com/a/8955580
|
||
|
function hyphenate(str) {
|
||
|
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||
|
}
|
||
|
|
||
|
export {Foundation};
|