UNPKG

11.8 kBJavaScriptView Raw
1"use strict";
2
3import $ from 'jquery';
4import { GetYoDigits } from './foundation.util.core';
5import { MediaQuery } from './foundation.util.mediaQuery';
6
7var FOUNDATION_VERSION = '6.4.1';
8
9// Global Foundation object
10// This is attached to the window, or used as a module for AMD/Browserify
11var Foundation = {
12 version: FOUNDATION_VERSION,
13
14 /**
15 * Stores initialized plugins.
16 */
17 _plugins: {},
18
19 /**
20 * Stores generated unique ids for plugin instances
21 */
22 _uuids: [],
23
24 /**
25 * Defines a Foundation plugin, adding it to the `Foundation` namespace and the list of plugins to initialize when reflowing.
26 * @param {Object} plugin - The constructor of the plugin.
27 */
28 plugin: function(plugin, name) {
29 // Object key to use when adding to global Foundation object
30 // Examples: Foundation.Reveal, Foundation.OffCanvas
31 var className = (name || functionName(plugin));
32 // Object key to use when storing the plugin, also used to create the identifying data attribute for the plugin
33 // Examples: data-reveal, data-off-canvas
34 var attrName = hyphenate(className);
35
36 // Add to the Foundation object and the plugins list (for reflowing)
37 this._plugins[attrName] = this[className] = plugin;
38 },
39 /**
40 * @function
41 * Populates the _uuids array with pointers to each individual plugin instance.
42 * Adds the `zfPlugin` data-attribute to programmatically created plugins to allow use of $(selector).foundation(method) calls.
43 * Also fires the initialization event for each plugin, consolidating repetitive code.
44 * @param {Object} plugin - an instance of a plugin, usually `this` in context.
45 * @param {String} name - the name of the plugin, passed as a camelCased string.
46 * @fires Plugin#init
47 */
48 registerPlugin: function(plugin, name){
49 var pluginName = name ? hyphenate(name) : functionName(plugin.constructor).toLowerCase();
50 plugin.uuid = GetYoDigits(6, pluginName);
51
52 if(!plugin.$element.attr(`data-${pluginName}`)){ plugin.$element.attr(`data-${pluginName}`, plugin.uuid); }
53 if(!plugin.$element.data('zfPlugin')){ plugin.$element.data('zfPlugin', plugin); }
54 /**
55 * Fires when the plugin has initialized.
56 * @event Plugin#init
57 */
58 plugin.$element.trigger(`init.zf.${pluginName}`);
59
60 this._uuids.push(plugin.uuid);
61
62 return;
63 },
64 /**
65 * @function
66 * Removes the plugins uuid from the _uuids array.
67 * Removes the zfPlugin data attribute, as well as the data-plugin-name attribute.
68 * Also fires the destroyed event for the plugin, consolidating repetitive code.
69 * @param {Object} plugin - an instance of a plugin, usually `this` in context.
70 * @fires Plugin#destroyed
71 */
72 unregisterPlugin: function(plugin){
73 var pluginName = hyphenate(functionName(plugin.$element.data('zfPlugin').constructor));
74
75 this._uuids.splice(this._uuids.indexOf(plugin.uuid), 1);
76 plugin.$element.removeAttr(`data-${pluginName}`).removeData('zfPlugin')
77 /**
78 * Fires when the plugin has been destroyed.
79 * @event Plugin#destroyed
80 */
81 .trigger(`destroyed.zf.${pluginName}`);
82 for(var prop in plugin){
83 plugin[prop] = null;//clean up script to prep for garbage collection.
84 }
85 return;
86 },
87
88 /**
89 * @function
90 * Causes one or more active plugins to re-initialize, resetting event listeners, recalculating positions, etc.
91 * @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'`
92 * @default If no argument is passed, reflow all currently active plugins.
93 */
94 reInit: function(plugins){
95 var isJQ = plugins instanceof $;
96 try{
97 if(isJQ){
98 plugins.each(function(){
99 $(this).data('zfPlugin')._init();
100 });
101 }else{
102 var type = typeof plugins,
103 _this = this,
104 fns = {
105 'object': function(plgs){
106 plgs.forEach(function(p){
107 p = hyphenate(p);
108 $('[data-'+ p +']').foundation('_init');
109 });
110 },
111 'string': function(){
112 plugins = hyphenate(plugins);
113 $('[data-'+ plugins +']').foundation('_init');
114 },
115 'undefined': function(){
116 this['object'](Object.keys(_this._plugins));
117 }
118 };
119 fns[type](plugins);
120 }
121 }catch(err){
122 console.error(err);
123 }finally{
124 return plugins;
125 }
126 },
127
128 /**
129 * Initialize plugins on any elements within `elem` (and `elem` itself) that aren't already initialized.
130 * @param {Object} elem - jQuery object containing the element to check inside. Also checks the element itself, unless it's the `document` object.
131 * @param {String|Array} plugins - A list of plugins to initialize. Leave this out to initialize everything.
132 */
133 reflow: function(elem, plugins) {
134
135 // If plugins is undefined, just grab everything
136 if (typeof plugins === 'undefined') {
137 plugins = Object.keys(this._plugins);
138 }
139 // If plugins is a string, convert it to an array with one item
140 else if (typeof plugins === 'string') {
141 plugins = [plugins];
142 }
143
144 var _this = this;
145
146 // Iterate through each plugin
147 $.each(plugins, function(i, name) {
148 // Get the current plugin
149 var plugin = _this._plugins[name];
150
151 // Localize the search to all elements inside elem, as well as elem itself, unless elem === document
152 var $elem = $(elem).find('[data-'+name+']').addBack('[data-'+name+']');
153
154 // For each plugin found, initialize it
155 $elem.each(function() {
156 var $el = $(this),
157 opts = {};
158 // Don't double-dip on plugins
159 if ($el.data('zfPlugin')) {
160 console.warn("Tried to initialize "+name+" on an element that already has a Foundation plugin.");
161 return;
162 }
163
164 if($el.attr('data-options')){
165 var thing = $el.attr('data-options').split(';').forEach(function(e, i){
166 var opt = e.split(':').map(function(el){ return el.trim(); });
167 if(opt[0]) opts[opt[0]] = parseValue(opt[1]);
168 });
169 }
170 try{
171 $el.data('zfPlugin', new plugin($(this), opts));
172 }catch(er){
173 console.error(er);
174 }finally{
175 return;
176 }
177 });
178 });
179 },
180 getFnName: functionName,
181
182 addToJquery: function($) {
183 // TODO: consider not making this a jQuery function
184 // TODO: need way to reflow vs. re-initialize
185 /**
186 * The Foundation jQuery method.
187 * @param {String|Array} method - An action to perform on the current jQuery object.
188 */
189 var foundation = function(method) {
190 var type = typeof method,
191 $noJS = $('.no-js');
192
193 if($noJS.length){
194 $noJS.removeClass('no-js');
195 }
196
197 if(type === 'undefined'){//needs to initialize the Foundation object, or an individual plugin.
198 MediaQuery._init();
199 Foundation.reflow(this);
200 }else if(type === 'string'){//an individual method to invoke on a plugin or group of plugins
201 var args = Array.prototype.slice.call(arguments, 1);//collect all the arguments, if necessary
202 var plugClass = this.data('zfPlugin');//determine the class of plugin
203
204 if(plugClass !== undefined && plugClass[method] !== undefined){//make sure both the class and method exist
205 if(this.length === 1){//if there's only one, call it directly.
206 plugClass[method].apply(plugClass, args);
207 }else{
208 this.each(function(i, el){//otherwise loop through the jQuery collection and invoke the method on each
209 plugClass[method].apply($(el).data('zfPlugin'), args);
210 });
211 }
212 }else{//error for no class or no method
213 throw new ReferenceError("We're sorry, '" + method + "' is not an available method for " + (plugClass ? functionName(plugClass) : 'this element') + '.');
214 }
215 }else{//error for invalid argument type
216 throw new TypeError(`We're sorry, ${type} is not a valid parameter. You must use a string representing the method you wish to invoke.`);
217 }
218 return this;
219 };
220 $.fn.foundation = foundation;
221 return $;
222 }
223};
224
225Foundation.util = {
226 /**
227 * Function for applying a debounce effect to a function call.
228 * @function
229 * @param {Function} func - Function to be called at end of timeout.
230 * @param {Number} delay - Time in ms to delay the call of `func`.
231 * @returns function
232 */
233 throttle: function (func, delay) {
234 var timer = null;
235
236 return function () {
237 var context = this, args = arguments;
238
239 if (timer === null) {
240 timer = setTimeout(function () {
241 func.apply(context, args);
242 timer = null;
243 }, delay);
244 }
245 };
246 }
247};
248
249window.Foundation = Foundation;
250
251// Polyfill for requestAnimationFrame
252(function() {
253 if (!Date.now || !window.Date.now)
254 window.Date.now = Date.now = function() { return new Date().getTime(); };
255
256 var vendors = ['webkit', 'moz'];
257 for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
258 var vp = vendors[i];
259 window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
260 window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
261 || window[vp+'CancelRequestAnimationFrame']);
262 }
263 if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent)
264 || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
265 var lastTime = 0;
266 window.requestAnimationFrame = function(callback) {
267 var now = Date.now();
268 var nextTime = Math.max(lastTime + 16, now);
269 return setTimeout(function() { callback(lastTime = nextTime); },
270 nextTime - now);
271 };
272 window.cancelAnimationFrame = clearTimeout;
273 }
274 /**
275 * Polyfill for performance.now, required by rAF
276 */
277 if(!window.performance || !window.performance.now){
278 window.performance = {
279 start: Date.now(),
280 now: function(){ return Date.now() - this.start; }
281 };
282 }
283})();
284if (!Function.prototype.bind) {
285 Function.prototype.bind = function(oThis) {
286 if (typeof this !== 'function') {
287 // closest thing possible to the ECMAScript 5
288 // internal IsCallable function
289 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
290 }
291
292 var aArgs = Array.prototype.slice.call(arguments, 1),
293 fToBind = this,
294 fNOP = function() {},
295 fBound = function() {
296 return fToBind.apply(this instanceof fNOP
297 ? this
298 : oThis,
299 aArgs.concat(Array.prototype.slice.call(arguments)));
300 };
301
302 if (this.prototype) {
303 // native functions don't have a prototype
304 fNOP.prototype = this.prototype;
305 }
306 fBound.prototype = new fNOP();
307
308 return fBound;
309 };
310}
311// Polyfill to get the name of a function in IE9
312function functionName(fn) {
313 if (Function.prototype.name === undefined) {
314 var funcNameRegex = /function\s([^(]{1,})\(/;
315 var results = (funcNameRegex).exec((fn).toString());
316 return (results && results.length > 1) ? results[1].trim() : "";
317 }
318 else if (fn.prototype === undefined) {
319 return fn.constructor.name;
320 }
321 else {
322 return fn.prototype.constructor.name;
323 }
324}
325function parseValue(str){
326 if ('true' === str) return true;
327 else if ('false' === str) return false;
328 else if (!isNaN(str * 1)) return parseFloat(str);
329 return str;
330}
331// Convert PascalCase to kebab-case
332// Thank you: http://stackoverflow.com/a/8955580
333function hyphenate(str) {
334 return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
335}
336
337export {Foundation};