386 lines
13 KiB
JavaScript
386 lines
13 KiB
JavaScript
|
!function ($) {
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
var FOUNDATION_VERSION = '6.2.2';
|
||
|
|
||
|
// 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: [],
|
||
|
|
||
|
/**
|
||
|
* Returns a boolean for RTL support
|
||
|
*/
|
||
|
rtl: function () {
|
||
|
return $('html').attr('dir') === 'rtl';
|
||
|
},
|
||
|
/**
|
||
|
* 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 = this.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;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* returns a random base-36 uid with namespacing
|
||
|
* @function
|
||
|
* @param {Number} length - number of random base-36 digits desired. Increase for more random strings.
|
||
|
* @param {String} namespace - name of plugin to be incorporated in uid, optional.
|
||
|
* @default {String} '' - if no plugin name is provided, nothing is appended to the uid.
|
||
|
* @returns {String} - unique id
|
||
|
*/
|
||
|
GetYoDigits: function (length, namespace) {
|
||
|
length = length || 6;
|
||
|
return Math.round(Math.pow(36, length + 1) - Math.random() * Math.pow(36, length)).toString(36).slice(1) + (namespace ? '-' + namespace : '');
|
||
|
},
|
||
|
/**
|
||
|
* 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,
|
||
|
transitionend: function ($elem) {
|
||
|
var transitions = {
|
||
|
'transition': 'transitionend',
|
||
|
'WebkitTransition': 'webkitTransitionEnd',
|
||
|
'MozTransition': 'transitionend',
|
||
|
'OTransition': 'otransitionend'
|
||
|
};
|
||
|
var elem = document.createElement('div'),
|
||
|
end;
|
||
|
|
||
|
for (var t in transitions) {
|
||
|
if (typeof elem.style[t] !== 'undefined') {
|
||
|
end = transitions[t];
|
||
|
}
|
||
|
}
|
||
|
if (end) {
|
||
|
return end;
|
||
|
} else {
|
||
|
end = setTimeout(function () {
|
||
|
$elem.triggerHandler('transitionend', [$elem]);
|
||
|
}, 1);
|
||
|
return 'transitionend';
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// 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,
|
||
|
$meta = $('meta.foundation-mq'),
|
||
|
$noJS = $('.no-js');
|
||
|
|
||
|
if (!$meta.length) {
|
||
|
$('<meta class="foundation-mq">').appendTo(document.head);
|
||
|
}
|
||
|
if ($noJS.length) {
|
||
|
$noJS.removeClass('no-js');
|
||
|
}
|
||
|
|
||
|
if (type === 'undefined') {
|
||
|
//needs to initialize the Foundation object, or an individual plugin.
|
||
|
Foundation.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 (plugClass !== undefined && 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;
|
||
|
};
|
||
|
|
||
|
window.Foundation = Foundation;
|
||
|
$.fn.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 (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 (fn.prototype === undefined) {
|
||
|
return fn.constructor.name;
|
||
|
} else {
|
||
|
return fn.prototype.constructor.name;
|
||
|
}
|
||
|
}
|
||
|
function parseValue(str) {
|
||
|
if (/true/.test(str)) return true;else if (/false/.test(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();
|
||
|
}
|
||
|
}(jQuery);
|