PageRenderTime 44ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/OAS.Web/js/foundation.core.js

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