1!function($) { 2"use strict"; 3 4var FOUNDATION_VERSION = '6.1.2'; 5 6// Global Foundation object 7// This is attached to the window, or used as a module for AMD/Browserify 8var Foundation = { 9 version: FOUNDATION_VERSION, 10 11 /** 12 * Stores initialized plugins. 13 */ 14 _plugins: {}, 15 16 /** 17 * Stores generated unique ids for plugin instances 18 */ 19 _uuids: [], 20 21 /** 22 * Returns a boolean for RTL support 23 */ 24 rtl: function(){ 25 return $('html').attr('dir') === 'rtl'; 26 }, 27 /** 28 * Defines a Foundation plugin, adding it to the `Foundation` namespace and the list of plugins to initialize when reflowing. 29 * @param {Object} plugin - The constructor of the plugin. 30 */ 31 plugin: function(plugin, name) { 32 // Object key to use when adding to global Foundation object 33 // Examples: Foundation.Reveal, Foundation.OffCanvas 34 var className = (name || functionName(plugin)); 35 // Object key to use when storing the plugin, also used to create the identifying data attribute for the plugin 36 // Examples: data-reveal, data-off-canvas 37 var attrName = hyphenate(className); 38 39 // Add to the Foundation object and the plugins list (for reflowing) 40 this._plugins[attrName] = this[className] = plugin; 41 }, 42 /** 43 * @function 44 * Populates the _uuids array with pointers to each individual plugin instance. 45 * Adds the `zfPlugin` data-attribute to programmatically created plugins to allow use of $(selector).foundation(method) calls. 46 * Also fires the initialization event for each plugin, consolidating repeditive code. 47 * @param {Object} plugin - an instance of a plugin, usually `this` in context. 48 * @param {String} name - the name of the plugin, passed as a camelCased string. 49 * @fires Plugin#init 50 */ 51 registerPlugin: function(plugin, name){ 52 var pluginName = name ? hyphenate(name) : functionName(plugin.constructor).toLowerCase(); 53 plugin.uuid = this.GetYoDigits(6, pluginName); 54 55 if(!plugin.$element.attr('data-' + pluginName)){ plugin.$element.attr('data-' + pluginName, plugin.uuid); } 56 if(!plugin.$element.data('zfPlugin')){ plugin.$element.data('zfPlugin', plugin); } 57 /** 58 * Fires when the plugin has initialized. 59 * @event Plugin#init 60 */ 61 plugin.$element.trigger('init.zf.' + pluginName); 62 63 this._uuids.push(plugin.uuid); 64 65 return; 66 }, 67 /** 68 * @function 69 * Removes the plugins uuid from the _uuids array. 70 * Removes the zfPlugin data attribute, as well as the data-plugin-name attribute. 71 * Also fires the destroyed event for the plugin, consolidating repeditive code. 72 * @param {Object} plugin - an instance of a plugin, usually `this` in context. 73 * @fires Plugin#destroyed 74 */ 75 unregisterPlugin: function(plugin){ 76 var pluginName = hyphenate(functionName(plugin.$element.data('zfPlugin').constructor)); 77 78 this._uuids.splice(this._uuids.indexOf(plugin.uuid), 1); 79 plugin.$element.removeAttr('data-' + pluginName).removeData('zfPlugin') 80 /** 81 * Fires when the plugin has been destroyed. 82 * @event Plugin#destroyed 83 */ 84 .trigger('destroyed.zf.' + pluginName); 85 for(var prop in plugin){ 86 plugin[prop] = null;//clean up script to prep for garbage collection. 87 } 88 return; 89 }, 90 91 /** 92 * @function 93 * Causes one or more active plugins to re-initialize, resetting event listeners, recalculating positions, etc. 94 * @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'` 95 * @default If no argument is passed, reflow all currently active plugins. 96 */ 97 reInit: function(plugins){ 98 var isJQ = plugins instanceof $; 99 try{ 100 if(isJQ){ 101 plugins.each(function(){ 102 $(this).data('zfPlugin')._init(); 103 }); 104 }else{ 105 var type = typeof plugins, 106 _this = this, 107 fns = { 108 'object': function(plgs){
109 plgs.forEach(function(p){ 110 $('[data-'+ p +']').foundation('_init'); 111 }); 112 }, 113 'string': function(){ 114 $('[data-'+ plugins +']').foundation('_init'); 115 }, 116 'undefined': function(){ 117 this['object'](Object.keys(_this._plugins)); 118 } 119 }; 120 fns[type](plugins); 121 } 122 }catch(err){ 123 console.error(err); 124 }finally{ 125 return plugins; 126 } 127 }, 128 129 /** 130 * returns a random base-36 uid with namespacing 131 * @function 132 * @param {Number} length - number of random base-36 digits desired. Increase for more random strings. 133 * @param {String} namespace - name of plugin to be incorporated in uid, optional. 134 * @default {String} '' - if no plugin name is provided, nothing is appended to the uid. 135 * @returns {String} - unique id 136 */ 137 GetYoDigits: function(length, namespace){ 138 length = length || 6; 139 return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1) + (namespace ? '-' + namespace : ''); 140 }, 141 /** 142 * Initialize plugins on any elements within `elem` (and `elem` itself) that aren't already initialized. 143 * @param {Object} elem - jQuery object containing the element to check inside. Also checks the element itself, unless it's the `document` object. 144 * @param {String|Array} plugins - A list of plugins to initialize. Leave this out to initialize everything. 145 */ 146 reflow: function(elem, plugins) { 147 148 // If plugins is undefined, just grab everything 149 if (typeof plugins === 'undefined') { 150 plugins = Object.keys(this._plugins); 151 } 152 // If plugins is a string, convert it to an array with one item 153 else if (typeof plugins === 'string') { 154 plugins = [plugins]; 155 } 156 157 var _this = this; 158 159 // Iterate through each plugin 160 $.each(plugins, function(i, name) { 161 // Get the current plugin 162 var plugin = _this._plugins[name]; 163 164 // Localize the search to all elements inside elem, as well as elem itself, unless elem === document 165 var $elem = $(elem).find('[data-'+name+']').addBack('[data-'+name+']'); 166 167 // For each plugin found, initialize it 168 $elem.each(function() { 169 var $el = $(this), 170 opts = {}; 171 // Don't double-dip on plugins 172 if ($el.data('zfPlugin')) { 173 console.warn("Tried to initialize "+name+" on an element that already has a Foundation plugin."); 174 return; 175 } 176 177 if($el.attr('data-options')){ 178 var thing = $el.attr('data-options').split(';').forEach(function(e, i){ 179 var opt = e.split(':').map(function(el){ return el.trim(); }); 180 if(opt[0]) opts[opt[0]] = parseValue(opt[1]); 181 }); 182 } 183 try{ 184 $el.data('zfPlugin', new plugin($(this), opts)); 185 }catch(er){ 186 console.error(er); 187 }finally{ 188 return; 189 } 190 }); 191 }); 192 }, 193 getFnName: functionName, 194 transitionend: function($elem){ 195 var transitions = { 196 'transition': 'transitionend', 197 'WebkitTransition': 'webkitTransitionEnd', 198 'MozTransition': 'transitionend', 199 'OTransition': 'otransitionend' 200 }; 201 var elem = document.createElement('div'), 202 end; 203 204 for (var t in transitions){ 205 if (typeof elem.style[t] !== 'undefined'){ 206 end = transitions[t]; 207 } 208 } 209 if(end){ 210 return end; 211 }else{ 212 end = setTimeout(function(){ 213 $elem.triggerHandler('transitionend', [$elem]); 214 }, 1); 215 return 'transitionend'; 216 } 217 } 218}; 219 220 221Foundation.util = { 222 /** 223 * Function for applying a debounce effect to a function call. 224 * @function 225 * @param {Function} func - Function to be called at end of timeout. 226 * @param {Number} delay - Time in ms to delay the call of `func`. 227 * @returns function 228 */ 229 throttle: function (func, delay) { 230 var timer = null; 231 232 return function () { 233 var context = this, args = arguments; 234 235 if (timer === null) { 236 timer = setTimeout(function () {
vendor: 5,227 bytes, lines 237-383
237 func.apply(context, args); 238 timer = null; 239 }, delay); 240 } 241 }; 242 } 243}; 244 245// TODO: consider not making this a jQuery function 246// TODO: need way to reflow vs. re-initialize 247/** 248 * The Foundation jQuery method. 249 * @param {String|Array} method - An action to perform on the current jQuery object. 250 */ 251var foundation = function(method) { 252 var type = typeof method, 253 $meta = $('meta.foundation-mq'), 254 $noJS = $('.no-js'); 255 256 if(!$meta.length){ 257 $('<meta class="foundation-mq">').appendTo(document.head); 258 } 259 if($noJS.length){ 260 $noJS.removeClass('no-js'); 261 } 262 263 if(type === 'undefined'){//needs to initialize the Foundation object, or an individual plugin. 264 Foundation.MediaQuery._init(); 265 Foundation.reflow(this); 266 }else if(type === 'string'){//an individual method to invoke on a plugin or group of plugins 267 var args = Array.prototype.slice.call(arguments, 1);//collect all the arguments, if necessary 268 var plugClass = this.data('zfPlugin');//determine the class of plugin 269 270 if(plugClass !== undefined && plugClass[method] !== undefined){//make sure both the class and method exist 271 if(this.length === 1){//if there's only one, call it directly. 272 plugClass[method].apply(plugClass, args); 273 }else{ 274 this.each(function(i, el){//otherwise loop through the jQuery collection and invoke the method on each 275 plugClass[method].apply($(el).data('zfPlugin'), args); 276 }); 277 } 278 }else{//error for no class or no method 279 throw new ReferenceError("We're sorry, '" + method + "' is not an available method for " + (plugClass ? functionName(plugClass) : 'this element') + '.'); 280 } 281 }else{//error for invalid argument type 282 throw new TypeError("We're sorry, '" + type + "' is not a valid parameter. You must use a string representing the method you wish to invoke."); 283 } 284 return this; 285}; 286 287window.Foundation = Foundation; 288$.fn.foundation = foundation; 289 290// Polyfill for requestAnimationFrame 291(function() { 292 if (!Date.now || !window.Date.now) 293 window.Date.now = Date.now = function() { return new Date().getTime(); }; 294 295 var vendors = ['webkit', 'moz']; 296 for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { 297 var vp = vendors[i]; 298 window.requestAnimationFrame = window[vp+'RequestAnimationFrame']; 299 window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame'] 300 || window[vp+'CancelRequestAnimationFrame']); 301 } 302 if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) 303 || !window.requestAnimationFrame || !window.cancelAnimationFrame) { 304 var lastTime = 0; 305 window.requestAnimationFrame = function(callback) { 306 var now = Date.now(); 307 var nextTime = Math.max(lastTime + 16, now); 308 return setTimeout(function() { callback(lastTime = nextTime); }, 309 nextTime - now); 310 }; 311 window.cancelAnimationFrame = clearTimeout; 312 } 313 /** 314 * Polyfill for performance.now, required by rAF 315 */ 316 if(!window.performance || !window.performance.now){ 317 window.performance = { 318 start: Date.now(), 319 now: function(){ return Date.now() - this.start; } 320 }; 321 } 322})(); 323if (!Function.prototype.bind) { 324 Function.prototype.bind = function(oThis) { 325 if (typeof this !== 'function') { 326 // closest thing possible to the ECMAScript 5 327 // internal IsCallable function 328 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 329 } 330 331 var aArgs = Array.prototype.slice.call(arguments, 1), 332 fToBind = this, 333 fNOP = function() {}, 334 fBound = function() { 335 return fToBind.apply(this instanceof fNOP 336 ? this 337 : oThis, 338 aArgs.concat(Array.prototype.slice.call(arguments))); 339 }; 340 341 if (this.prototype) { 342 // native functions don't have a prototype 343 fNOP.prototype = this.prototype; 344 } 345 fBound.prototype = new fNOP(); 346 347 return fBound; 348 }; 349} 350// Polyfill to get the name of a function in IE9 351function functionName(fn) { 352 if (Function.prototype.name === undefined) { 353 var funcNameRegex = /function\s([^(]{1,})\(/; 354 var results = (funcNameRegex).exec((fn).toString()); 355 return (results && results.length > 1) ? results[1].trim() : ""; 356 } 357 else if (fn.prototype === undefined) { 358 return fn.constructor.name; 359 } 360 else { 361 return fn.prototype.constructor.name; 362 } 363} 364function parseValue(str){ 365 if(/true/.test(str)) return true; 366 else if(/false/.test(str)) return false; 367 else if(!isNaN(str * 1)) return parseFloat(str); 368 return str; 369} 370// Convert PascalCase to kebab-case 371// Thank you: http://stackoverflow.com/a/8955580 372function hyphenate(str) { 373 return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); 374} 375 376}(jQuery); 377 378!function(Foundation, window){ 379 /** 380 * Compares the dimensions of an element to a container and determines collision events with container. 381 * @function 382 * @param {jQuery} element - jQuery object to test for collisions. 383 * @param {jQuery}
383 parent - jQuery object to use as bounding container. 384 * @param {Boolean} lrOnly - set to true to check left and right values only. 385 * @param {Boolean} tbOnly - set to true to check top and bottom values only. 386 * @default if no parent object passed, detects collisions with `window`. 387 * @returns {Boolean} - true if collision free, false if a collision in any direction. 388 */ 389 var ImNotTouchingYou = function(element, parent, lrOnly, tbOnly){ 390 var eleDims = GetDimensions(element), 391 top, bottom, left, right; 392 393 if(parent){ 394 var parDims = GetDimensions(parent); 395 396 bottom = (eleDims.offset.top + eleDims.height <= parDims.height + parDims.offset.top); 397 top = (eleDims.offset.top >= parDims.offset.top); 398 left = (eleDims.offset.left >= parDims.offset.left); 399 right = (eleDims.offset.left + eleDims.width <= parDims.width); 400 }else{ 401 bottom = (eleDims.offset.top + eleDims.height <= eleDims.windowDims.height + eleDims.windowDims.offset.top); 402 top = (eleDims.offset.top >= eleDims.windowDims.offset.top); 403 left = (eleDims.offset.left >= eleDims.windowDims.offset.left); 404 right = (eleDims.offset.left + eleDims.width <= eleDims.windowDims.width); 405 } 406 var allDirs = [bottom, top, left, right]; 407 408 if(lrOnly){ return left === right === true; } 409 if(tbOnly){ return top === bottom === true; } 410 411 return allDirs.indexOf(false) === -1; 412 }; 413 414 /** 415 * Uses native methods to return an object of dimension values. 416 * @function 417 * @param {jQuery || HTML} element - jQuery object or DOM element for which to get the dimensions. Can be any element other that document or window. 418 * @returns {Object} - nested object of integer pixel values 419 * TODO - if element is window, return only those values. 420 */ 421 var GetDimensions = function(elem, test){ 422 elem = elem.length ? elem[0] : elem; 423 424 if(elem === window || elem === document){ throw new Error("I'm sorry, Dave. I'm afraid I can't do that."); } 425 426 var rect = elem.getBoundingClientRect(), 427 parRect = elem.parentNode.getBoundingClientRect(), 428 winRect = document.body.getBoundingClientRect(), 429 winY = window.pageYOffset, 430 winX = window.pageXOffset; 431 432 return { 433 width: rect.width, 434 height: rect.height, 435 offset: { 436 top: rect.top + winY, 437 left: rect.left + winX 438 }, 439 parentDims: { 440 width: parRect.width, 441 height: parRect.height, 442 offset: { 443 top: parRect.top + winY, 444 left: parRect.left + winX 445 } 446 }, 447 windowDims: { 448 width: winRect.width, 449 height: winRect.height, 450 offset: { 451 top: winY, 452 left: winX 453 } 454 } 455 }; 456 }; 457 /** 458 * Returns an object of top and left integer pixel values for dynamically rendered elements, 459 * such as: Tooltip, Reveal, and Dropdown 460 * @function 461 * @param {jQuery} element - jQuery object for the element being positioned. 462 * @param {jQuery} anchor - jQuery object for the element's anchor point. 463 * @param {String} position - a string relating to the desired position of the element, relative to it's anchor 464 * @param {Number} vOffset - integer pixel value of desired vertical separation between anchor and element. 465 * @param {Number} hOffset - integer pixel value of desired horizontal separation between anchor and element. 466 * @param {Boolean} isOverflow - if a collision event is detected, sets to true to default the element to full width - any desired offset. 467 * TODO alter/rewrite to work with `em` values as well/instead of pixels 468 */ 469 var GetOffsets = function(element, anchor, position, vOffset, hOffset, isOverflow){ 470 var $eleDims = GetDimensions(element), 471 // var $eleDims = GetDimensions(element), 472 $anchorDims = anchor ? GetDimensions(anchor) : null; 473 // $anchorDims = anchor ? GetDimensions(anchor) : null; 474 switch(position){ 475 case 'top': 476 return { 477 left: $anchorDims.offset.left,
478 top: $anchorDims.offset.top - ($eleDims.height + vOffset) 479 }; 480 break; 481 case 'left': 482 return { 483 left: $anchorDims.offset.left - ($eleDims.width + hOffset), 484 top: $anchorDims.offset.top 485 }; 486 break; 487 case 'right': 488 return { 489 left: $anchorDims.offset.left + $anchorDims.width + hOffset, 490 top: $anchorDims.offset.top 491 }; 492 break; 493 case 'center top': 494 return { 495 left: ($anchorDims.offset.left + ($anchorDims.width / 2)) - ($eleDims.width / 2), 496 top: $anchorDims.offset.top - ($eleDims.height + vOffset) 497 }; 498 break; 499 case 'center bottom': 500 return { 501 left: isOverflow ? hOffset : (($anchorDims.offset.left + ($anchorDims.width / 2)) - ($eleDims.width / 2)), 502 top: $anchorDims.offset.top + $anchorDims.height + vOffset 503 }; 504 break; 505 case 'center left': 506 return { 507 left: $anchorDims.offset.left - ($eleDims.width + hOffset), 508 top: ($anchorDims.offset.top + ($anchorDims.height / 2)) - ($eleDims.height / 2) 509 }; 510 break; 511 case 'center right': 512 return { 513 left: $anchorDims.offset.left + $anchorDims.width + hOffset + 1, 514 top: ($anchorDims.offset.top + ($anchorDims.height / 2)) - ($eleDims.height / 2) 515 }; 516 break; 517 case 'center': 518 return { 519 left: ($eleDims.windowDims.offset.left + ($eleDims.windowDims.width / 2)) - ($eleDims.width / 2), 520 top: ($eleDims.windowDims.offset.top + ($eleDims.windowDims.height / 2)) - ($eleDims.height / 2) 521 }; 522 break; 523 case 'reveal': 524 return { 525 left: ($eleDims.windowDims.width - $eleDims.width) / 2, 526 top: $eleDims.windowDims.offset.top + vOffset 527 }; 528 case 'reveal full': 529 return { 530 left: $eleDims.windowDims.offset.left, 531 top: $eleDims.windowDims.offset.top 532 }; 533 break; 534 default: 535 return { 536 left: $anchorDims.offset.left, 537 top: $anchorDims.offset.top + $anchorDims.height + vOffset 538 }; 539 } 540 }; 541 Foundation.Box = { 542 ImNotTouchingYou: ImNotTouchingYou, 543 GetDimensions: GetDimensions, 544 GetOffsets: GetOffsets 545 }; 546}(window.Foundation, window); 547 548/******************************************* 549 * * 550 * This util was created by Marius Olbertz * 551 * Please thank Marius on GitHub /owlbertz * 552 * or the web http://www.mariusolbertz.de/ * 553 * * 554 ******************************************/ 555!function($, Foundation){ 556 'use strict'; 557 Foundation.Keyboard = {}; 558 559 var keyCodes = { 560 9: 'TAB', 561 13: 'ENTER', 562 27: 'ESCAPE', 563 32: 'SPACE', 564 37: 'ARROW_LEFT', 565 38: 'ARROW_UP', 566 39: 'ARROW_RIGHT', 567 40: 'ARROW_DOWN' 568 }; 569 570 /* 571 * Constants for easier comparing. 572 * Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE 573 */ 574 var keys = (function(kcs) { 575 var k = {}; 576 for (var kc in kcs) k[kcs[kc]] = kcs[kc]; 577 return k; 578 })(keyCodes); 579 580 Foundation.Keyboard.keys = keys; 581 582 /** 583 * Parses the (keyboard) event and returns a String that represents its key 584 * Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE 585 * @param {Event} event - the event generated by the event handler 586 * @return String key - String that represents the key pressed 587 */ 588 var parseKey = function(event) { 589 var key = keyCodes[event.which || event.keyCode] || String.fromCharCode(event.which).toUpperCase(); 590 if (event.shiftKey) key = 'SHIFT_' + key; 591 if (event.ctrlKey) key = 'CTRL_' + key; 592 if (event.altKey) key = 'ALT_' + key; 593 return key; 594 }; 595 Foundation.Keyboard.parseKey = parseKey; 596 597 598 // plain commands per component go here, ltr and rtl are merged based on orientation 599 var commands = {}; 600 601 /** 602 * Handles the given (keyboard) event 603 * @param {Event} event - the event generated by the event handler 604 * @param {String} component - Foundation component's name, e.g. Slider or Reveal 605 * @param {Objects} functions - collection of functions that are to be executed 606 */ 607 var handleKey = function(event, component, functions) { 608 var commandList = commands[component], 609 keyCode = parseKey(event), 610 cmds, 611 command, 612 fn; 613 if (!commandList) return console.warn('Component not defined!'); 614 615 if (typeof commandList.ltr === 'undefined') { // this component does not differentiate between ltr and rtl 616 cmds = commandList; // use plain list 617 }
617 else { // merge ltr and rtl: if document is rtl, rtl overwrites ltr and vice versa 618 if (Foundation.rtl()) cmds = $.extend({}, commandList.ltr, commandList.rtl); 619 620 else cmds = $.extend({}, commandList.rtl, commandList.ltr); 621 } 622 command = cmds[keyCode]; 623 624 625 fn = functions[command]; 626 if (fn && typeof fn === 'function') { // execute function if exists 627 fn.apply(); 628 if (functions.handled || typeof functions.handled === 'function') { // execute function when event was handled 629 functions.handled.apply(); 630 } 631 } else { 632 if (functions.unhandled || typeof functions.unhandled === 'function') { // execute function when event was not handled 633 functions.unhandled.apply(); 634 } 635 } 636 }; 637 Foundation.Keyboard.handleKey = handleKey; 638 639 /** 640 * Finds all focusable elements within the given `$element` 641 * @param {jQuery} $element - jQuery object to search within 642 * @return {jQuery} $focusable - all focusable elements within `$element` 643 */ 644 var findFocusable = function($element) { 645 return $element.find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]').filter(function() { 646 if (!$(this).is(':visible') || $(this).attr('tabindex') < 0) { return false; } //only have visible elements and those that have a tabindex greater or equal 0 647 return true; 648 }); 649 }; 650 Foundation.Keyboard.findFocusable = findFocusable; 651 652 /** 653 * Returns the component name name 654 * @param {Object} component - Foundation component, e.g. Slider or Reveal 655 * @return String componentName 656 */ 657 658 var register = function(componentName, cmds) { 659 commands[componentName] = cmds; 660 }; 661 Foundation.Keyboard.register = register; 662}(jQuery, window.Foundation); 663 664!function($, Foundation) { 665 666// Default set of media queries 667var defaultQueries = { 668 'default' : 'only screen', 669 landscape : 'only screen and (orientation: landscape)', 670 portrait : 'only screen and (orientation: portrait)', 671 retina : 'only screen and (-webkit-min-device-pixel-ratio: 2),' + 672 'only screen and (min--moz-device-pixel-ratio: 2),' + 673 'only screen and (-o-min-device-pixel-ratio: 2/1),' + 674 'only screen and (min-device-pixel-ratio: 2),' + 675 'only screen and (min-resolution: 192dpi),' + 676 'only screen and (min-resolution: 2dppx)' 677}; 678 679var MediaQuery = { 680 queries: [], 681 current: '', 682 683 /** 684 * Checks if the screen is at least as wide as a breakpoint. 685 * @function 686 * @param {String} size - Name of the breakpoint to check. 687 * @returns {Boolean} `true` if the breakpoint matches, `false` if it's smaller. 688 */ 689 atLeast: function(size) { 690 var query = this.get(size); 691 692 if (query) { 693 return window.matchMedia(query).matches; 694 } 695 696 return false; 697 }, 698 699 /** 700 * Gets the media query of a breakpoint. 701 * @function 702 * @param {String} size - Name of the breakpoint to get. 703 * @returns {String|null} - The media query of the breakpoint, or `null` if the breakpoint doesn't exist. 704 */ 705 get: function(size) { 706 for (var i in this.queries) { 707 var query = this.queries[i]; 708 if (size === query.name) return query.value; 709 } 710 711 return null; 712 }, 713 714 /** 715 * Initializes the media query helper, by extracting the breakpoint list from the CSS and activating the breakpoint watcher. 716 * @function 717 * @private 718 */ 719 _init: function() { 720 var self = this; 721 var extractedStyles = $('.foundation-mq').css('font-family'); 722 var namedQueries; 723 724 namedQueries = parseStyleToObject(extractedStyles); 725 726 for (var key in namedQueries) { 727 self.queries.push({ 728 name: key, 729 value: 'only screen and (min-width: ' + namedQueries[key] + ')' 730 }); 731 } 732 733 this.current = this._getCurrentSize(); 734 735 this._watcher(); 736 737 // Extend default queries 738 // namedQueries = $.extend(defaultQueries, namedQueries); 739 }, 740 741 /** 742 * Gets the current breakpoint name by testing every breakpoint and returning the last one to match (the biggest one). 743 * @function 744 * @private 745 * @returns {String} Name of the current breakpoint. 746 */ 747 _getCurrentSize: function() { 748 var matched; 749 750 for (var i in this.queries) { 751 var query = this.queries[i];
752 753 if (window.matchMedia(query.value).matches) { 754 matched = query; 755 } 756 } 757 758 if(typeof matched === 'object') { 759 return matched.name; 760 } else { 761 return matched; 762 } 763 }, 764 765 /** 766 * Activates the breakpoint watcher, which fires an event on the window whenever the breakpoint changes. 767 * @function 768 * @private 769 */ 770 _watcher: function() { 771 var _this = this; 772 773 $(window).on('resize.zf.mediaquery', function() { 774 var newSize = _this._getCurrentSize(); 775 776 if (newSize !== _this.current) { 777 // Broadcast the media query change on the window 778 $(window).trigger('changed.zf.mediaquery', [newSize, _this.current]); 779 780 // Change the current media query 781 _this.current = newSize; 782 } 783 }); 784 } 785}; 786 787Foundation.MediaQuery = MediaQuery; 788 789// matchMedia() polyfill - Test a CSS media type/query in JS. 790// Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license 791window.matchMedia || (window.matchMedia = function() { 792 'use strict'; 793 794 // For browsers that support matchMedium api such as IE 9 and webkit 795 var styleMedia = (window.styleMedia || window.media); 796 797 // For those that don't support matchMedium 798 if (!styleMedia) { 799 var style = document.createElement('style'), 800 script = document.getElementsByTagName('script')[0], 801 info = null; 802 803 style.type = 'text/css'; 804 style.id = 'matchmediajs-test'; 805 806 script.parentNode.insertBefore(style, script); 807 808 // 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers 809 info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle; 810 811 styleMedia = { 812 matchMedium: function(media) { 813 var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }'; 814 815 // 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers 816 if (style.styleSheet) { 817 style.styleSheet.cssText = text; 818 } else { 819 style.textContent = text; 820 } 821 822 // Test if media query is true or false 823 return info.width === '1px'; 824 } 825 }; 826 } 827 828 return function(media) { 829 return { 830 matches: styleMedia.matchMedium(media || 'all'), 831 media: media || 'all' 832 }; 833 }; 834}()); 835 836// Thank you: https://github.com/sindresorhus/query-string 837function parseStyleToObject(str) { 838 var styleObject = {}; 839 840 if (typeof str !== 'string') { 841 return styleObject; 842 } 843 844 str = str.trim().slice(1, -1); // browsers re-quote string style values 845 846 if (!str) { 847 return styleObject; 848 } 849 850 styleObject = str.split('&').reduce(function(ret, param) { 851 var parts = param.replace(/\+/g, ' ').split('='); 852 var key = parts[0]; 853 var val = parts[1]; 854 key = decodeURIComponent(key); 855 856 // missing `=` should be `null`: 857 // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters 858 val = val === undefined ? null : decodeURIComponent(val); 859 860 if (!ret.hasOwnProperty(key)) { 861 ret[key] = val; 862 } else if (Array.isArray(ret[key])) { 863 ret[key].push(val); 864 } else { 865 ret[key] = [ret[key], val]; 866 } 867 return ret; 868 }, {}); 869 870 return styleObject; 871} 872 873}(jQuery, Foundation); 874 875/** 876 * Motion module. 877 * @module foundation.motion 878 */ 879!function($, Foundation) { 880 881var initClasses = ['mui-enter', 'mui-leave']; 882var activeClasses = ['mui-enter-active', 'mui-leave-active']; 883 884function animate(isIn, element, animation, cb) { 885 element = $(element).eq(0); 886 887 if (!element.length) return; 888 889 var initClass = isIn ? initClasses[0] : initClasses[1]; 890 var activeClass = isIn ? activeClasses[0] : activeClasses[1]; 891 892 // Set up the animation 893 reset(); 894 element.addClass(animation) 895 .css('transition', 'none'); 896 // .addClass(initClass); 897 // if(isIn) element.show(); 898 requestAnimationFrame(function() { 899 element.addClass(initClass); 900 if (isIn) element.show(); 901 }); 902 // Start the animation 903 requestAnimationFrame(function() { 904 element[0].offsetWidth; 905 element.css('transition', ''); 906 element.addClass(activeClass); 907 }); 908 // Move(500, element, function(){ 909 // // element[0].offsetWidth; 910 // element.css('transition', ''); 911 // element.addClass(activeClass); 912 // }); 913 914 // Clean up the animation when it finishes 915 element.one(Foundation.transitionend(element), finish);//.one('finished.zf.animate', finish); 916 917 // Hides the element (for out animations), resets the element, and runs a callback 918 function finish() { 919 if (!isIn) element.hide(); 920 reset(); 921 if (cb) cb.apply(element); 922 } 923 924 // Resets transitions and removes motion-specific classes 925 function reset() { 926 element[0].style.transitionDuration = 0; 927 element.removeClass(initClass + ' ' + activeClass + ' ' + animation); 928 } 929} 930 931var Motion = { 932 animateIn: function(element, animation, /*duration,*/ cb) { 933 animate(true, element, animation, cb); 934 }, 935 936 animateOut: function(element, animation, /*duration,*/ cb) { 937 animate(false, element, animation, cb); 938 } 939}; 940 941var Move = function(duration, elem, fn){ 942 var anim, prog, start = null; 943 // console.log('called'); 944 945 function move(ts){ 946 if(!start) start = window.performance.now(); 947 // console.log(start, ts); 948 prog = ts - start; 949 fn.apply(elem); 950 951 if(prog < duration){ anim = window.requestAnimationFrame(move, elem); } 952 else{ 953 window.cancelAnimationFrame(anim); 954 elem.trigger('finished.zf.animate', [elem]).triggerHandler('finished.zf.animate', [elem]); 955 } 956 } 957 anim = window.requestAnimationFrame(move); 958}; 959 960Foundation.Move = Move; 961Foundation.Motion = Motion; 962 963}(jQuery, Foundation); 964 965!function($, Foundation){ 966 'use strict'; 967 Foundation.Nest = { 968 Feather: function(menu, type){ 969 menu.attr('role', 'menubar'); 970 type = type || 'zf'; 971 var items = menu.find('li').attr({'role': 'menuitem'}), 972 subMenuClass = 'is-' + type + '-submenu', 973 subItemClass = subMenuClass + '-item', 974 hasSubClass = 'is-' + type + '-submenu-parent'; 975 menu.find('a:first').attr('tabindex', 0); 976 items.each(function(){ 977 var $item = $(this), 978 $sub = $item.children('ul'); 979 if($sub.length){ 980 $item.addClass(hasSubClass) 981 .attr({ 982 'aria-haspopup': true, 983 'aria-expanded': false, 984 'aria-label': $item.children('a:first').text() 985 }); 986 $sub.addClass('submenu ' + subMenuClass) 987 .attr({ 988 'data-submenu': '', 989 'aria-hidden': true, 990 'role': 'menu' 991 }); 992 } 993 if($item.parent('[data-submenu]').length){ 994 $item.addClass('is-submenu-item ' + subItemClass); 995 } 996 }); 997 return; 998 }, 999 Burn: function(menu, type){ 1000 var items = menu.find('li').removeAttr('tabindex'), 1001 subMenuClass = 'is-' + type + '-submenu', 1002 subItemClass = subMenuClass + '-item', 1003 hasSubClass = 'is-' + type + '-submenu-parent'; 1004 1005 // menu.find('.is-active').removeClass('is-active'); 1006 menu.find('*') 1007 // menu.find('.' + subMenuClass + ', .' + subItemClass + ', .is-active, .has-submenu, .is-submenu-item, .submenu, [data-submenu]') 1008 .removeClass(subMenuClass + ' ' + subItemClass + ' ' + hasSubClass + ' is-submenu-item submenu is-active') 1009 .removeAttr('data-submenu').css('display', ''); 1010 1011 // console.log( menu.find('.' + subMenuClass + ', .' + subItemClass + ', .has-submenu, .is-submenu-item, .submenu, [data-submenu]') 1012 // .removeClass(subMenuClass + ' ' + subItemClass + ' has-submenu is-submenu-item submenu') 1013 // .removeAttr('data-submenu')); 1014 // items.each(function(){ 1015 // var $item = $(this), 1016 // $sub = $item.children('ul'); 1017 // if($item.parent('[data-submenu]').length){ 1018 // $item.removeClass('is-submenu-item ' + subItemClass); 1019 // } 1020 // if($sub.length){ 1021 // $item.removeClass('has-submenu'); 1022 // $sub.removeClass('submenu ' + subMenuClass).removeAttr('data-submenu'); 1023 // } 1024 // }); 1025 } 1026 }; 1027}(jQuery, window.Foundation); 1028 1029!function($, Foundation){ 1030 'use strict'; 1031 var Timer = function(elem, options, cb){ 1032 var _this = this, 1033 duration = options.duration,//options is an object for easily adding features later. 1034 nameSpace = Object.keys(elem.data())[0] || 'timer', 1035 remain = -1, 1036 start, 1037 timer; 1038 1039 this.isPaused = false; 1040 1041 this.restart = function(){ 1042 remain = -1; 1043 clearTimeout(timer); 1044 this.start(); 1045 }; 1046 1047 this.start = function(){ 1048 this.isPaused = false 1049 // if(!elem.data('paused')){ return false; }//maybe implement this sanity check if used for other things. 1050 clearTimeout(timer); 1051 remain = remain <= 0 ? duration : remain; 1052 elem.data('paused', false); 1053 start = Date.now(); 1054 timer = setTimeout(function(){ 1055 if(options.infinite){ 1056 _this.restart();//rerun the timer. 1057 } 1058 cb(); 1059 }, remain); 1060 elem.trigger('timerstart.zf.' + nameSpace); 1061 }; 1062 1063 this.pause = function(){ 1064 this.isPaused = true; 1065 //if(elem.data('paused')){ return false; }//maybe implement this sanity check if used for other things. 1066 clearTimeout(timer); 1067 elem.data('paused', true); 1068 var end = Date.now(); 1069 remain = remain - (end - start); 1070 elem.trigger('timerpaused.zf.' + nameSpace); 1071 }; 1072 }; 1073 /** 1074 * Runs a callback function when images are fully loaded. 1075 * @param {Object} images - Image(s) to check if loaded. 1076 * @param {Func} callback - Function to execute when image is fully loaded. 1077 */ 1078 var onImagesLoaded = function(images, callback){ 1079 var self = this, 1080 unloaded = images.length; 1081 1082 if (unloaded === 0) { 1083 callback(); 1084 } 1085 1086 var singleImageLoaded = function() { 1087 unloaded--; 1088 if (unloaded === 0) { 1089 callback(); 1090 } 1091 }; 1092 1093 images.each(function() { 1094 if (this.complete) { 1095 singleImageLoaded(); 1096 }
1097 else if (typeof this.naturalWidth !== 'undefined' && this.naturalWidth > 0) { 1098 singleImageLoaded(); 1099 } 1100 else { 1101 $(this).one('load', function() { 1102 singleImageLoaded(); 1103 }); 1104 } 1105 }); 1106 }; 1107 1108 Foundation.Timer = Timer; 1109 Foundation.onImagesLoaded = onImagesLoaded; 1110}(jQuery, window.Foundation); 1111 1112//************************************************** 1113//**Work inspired by multiple jquery swipe plugins** 1114//**Done by Yohai Ararat *************************** 1115//************************************************** 1116(function($) { 1117 1118 $.spotSwipe = { 1119 version: '1.0.0', 1120 enabled: 'ontouchstart' in document.documentElement, 1121 preventDefault: false, 1122 moveThreshold: 75, 1123 timeThreshold: 200 1124 }; 1125 1126 var startPosX, 1127 startPosY, 1128 startTime, 1129 elapsedTime, 1130 isMoving = false; 1131 1132 function onTouchEnd() { 1133 // alert(this); 1134 this.removeEventListener('touchmove', onTouchMove); 1135 this.removeEventListener('touchend', onTouchEnd); 1136 isMoving = false; 1137 } 1138 1139 function onTouchMove(e) { 1140 if ($.spotSwipe.preventDefault) { e.preventDefault(); } 1141 if(isMoving) { 1142 var x = e.touches[0].pageX; 1143 var y = e.touches[0].pageY; 1144 var dx = startPosX - x; 1145 var dy = startPosY - y; 1146 var dir; 1147 elapsedTime = new Date().getTime() - startTime; 1148 if(Math.abs(dx) >= $.spotSwipe.moveThreshold && elapsedTime <= $.spotSwipe.timeThreshold) { 1149 dir = dx > 0 ? 'left' : 'right'; 1150 } 1151 // else if(Math.abs(dy) >= $.spotSwipe.moveThreshold && elapsedTime <= $.spotSwipe.timeThreshold) { 1152 // dir = dy > 0 ? 'down' : 'up'; 1153 // } 1154 if(dir) { 1155 e.preventDefault(); 1156 onTouchEnd.call(this); 1157 $(this).trigger('swipe', dir).trigger('swipe' + dir); 1158 } 1159 } 1160 } 1161 1162 function onTouchStart(e) { 1163 if (e.touches.length == 1) { 1164 startPosX = e.touches[0].pageX; 1165 startPosY = e.touches[0].pageY; 1166 isMoving = true; 1167 startTime = new Date().getTime(); 1168 this.addEventListener('touchmove', onTouchMove, false); 1169 this.addEventListener('touchend', onTouchEnd, false); 1170 } 1171 } 1172 1173 function init() { 1174 this.addEventListener && this.addEventListener('touchstart', onTouchStart, false); 1175 } 1176 1177 function teardown() { 1178 this.removeEventListener('touchstart', onTouchStart); 1179 } 1180 1181 $.event.special.swipe = { setup: init }; 1182 1183 $.each(['left', 'up', 'down', 'right'], function () { 1184 $.event.special['swipe' + this] = { setup: function(){ 1185 $(this).on('swipe', $.noop); 1186 } }; 1187 }); 1188})(jQuery); 1189/**************************************************** 1190 * Method for adding psuedo drag events to elements * 1191 ***************************************************/ 1192!function($){ 1193 $.fn.addTouch = function(){ 1194 this.each(function(i,el){ 1195 $(el).bind('touchstart touchmove touchend touchcancel',function(){ 1196 //we pass the original event object because the jQuery event 1197 //object is normalized to w3c specs and does not provide the TouchList 1198 handleTouch(event); 1199 }); 1200 }); 1201 1202 var handleTouch = function(event){ 1203 var touches = event.changedTouches, 1204 first = touches[0], 1205 eventTypes = { 1206 touchstart: 'mousedown', 1207 touchmove: 'mousemove', 1208 touchend: 'mouseup' 1209 }, 1210 type = eventTypes[event.type], 1211 simulatedEvent 1212 ; 1213 1214 if('MouseEvent' in window && typeof window.MouseEvent === 'function') { 1215 simulatedEvent = window.MouseEvent(type, { 1216 'bubbles': true, 1217 'cancelable': true, 1218 'screenX': first.screenX, 1219 'screenY': first.screenY, 1220 'clientX': first.clientX, 1221 'clientY': first.clientY 1222 }); 1223 } else { 1224 simulatedEvent = document.createEvent('MouseEvent'); 1225 simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0/*left*/, null); 1226 } 1227 first.target.dispatchEvent(simulatedEvent); 1228 }; 1229 }; 1230}(jQuery); 1231 1232 1233//********************************** 1234//**From the jQuery Mobile Library** 1235//**need to recreate functionality** 1236//**and try to improve if possible** 1237//********************************** 1238 1239/* Removing the jQuery function ****
1240************************************ 1241 1242(function( $, window, undefined ) { 1243 1244 var $document = $( document ), 1245 // supportTouch = $.mobile.support.touch, 1246 touchStartEvent = 'touchstart'//supportTouch ? "touchstart" : "mousedown", 1247 touchStopEvent = 'touchend'//supportTouch ? "touchend" : "mouseup", 1248 touchMoveEvent = 'touchmove'//supportTouch ? "touchmove" : "mousemove"; 1249 1250 // setup new event shortcuts 1251 $.each( ( "touchstart touchmove touchend " + 1252 "swipe swipeleft swiperight" ).split( " " ), function( i, name ) { 1253 1254 $.fn[ name ] = function( fn ) { 1255 return fn ? this.bind( name, fn ) : this.trigger( name ); 1256 }; 1257 1258 // jQuery < 1.8 1259 if ( $.attrFn ) { 1260 $.attrFn[ name ] = true; 1261 } 1262 }); 1263 1264 function triggerCustomEvent( obj, eventType, event, bubble ) { 1265 var originalType = event.type; 1266 event.type = eventType; 1267 if ( bubble ) { 1268 $.event.trigger( event, undefined, obj ); 1269 } else { 1270 $.event.dispatch.call( obj, event ); 1271 } 1272 event.type = originalType; 1273 } 1274 1275 // also handles taphold 1276 1277 // Also handles swipeleft, swiperight 1278 $.event.special.swipe = { 1279 1280 // More than this horizontal displacement, and we will suppress scrolling. 1281 scrollSupressionThreshold: 30, 1282 1283 // More time than this, and it isn't a swipe. 1284 durationThreshold: 1000, 1285 1286 // Swipe horizontal displacement must be more than this. 1287 horizontalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30, 1288 1289 // Swipe vertical displacement must be less than this. 1290 verticalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30, 1291 1292 getLocation: function ( event ) { 1293 var winPageX = window.pageXOffset, 1294 winPageY = window.pageYOffset, 1295 x = event.clientX, 1296 y = event.clientY; 1297 1298 if ( event.pageY === 0 && Math.floor( y ) > Math.floor( event.pageY ) || 1299 event.pageX === 0 && Math.floor( x ) > Math.floor( event.pageX ) ) { 1300 1301 // iOS4 clientX/clientY have the value that should have been 1302 // in pageX/pageY. While pageX/page/ have the value 0 1303 x = x - winPageX; 1304 y = y - winPageY; 1305 } else if ( y < ( event.pageY - winPageY) || x < ( event.pageX - winPageX ) ) { 1306 1307 // Some Android browsers have totally bogus values for clientX/Y 1308 // when scrolling/zooming a page. Detectable since clientX/clientY 1309 // should never be smaller than pageX/pageY minus page scroll 1310 x = event.pageX - winPageX; 1311 y = event.pageY - winPageY; 1312 } 1313 1314 return { 1315 x: x, 1316 y: y 1317 }; 1318 }, 1319 1320 start: function( event ) { 1321 var data = event.originalEvent.touches ? 1322 event.originalEvent.touches[ 0 ] : event, 1323 location = $.event.special.swipe.getLocation( data ); 1324 return { 1325 time: ( new Date() ).getTime(), 1326 coords: [ location.x, location.y ], 1327 origin: $( event.target ) 1328 }; 1329 }, 1330 1331 stop: function( event ) { 1332 var data = event.originalEvent.touches ? 1333 event.originalEvent.touches[ 0 ] : event, 1334 location = $.event.special.swipe.getLocation( data ); 1335 return { 1336 time: ( new Date() ).getTime(), 1337 coords: [ location.x, location.y ] 1338 }; 1339 }, 1340 1341 handleSwipe: function( start, stop, thisObject, origTarget ) { 1342 if ( stop.time - start.time < $.event.special.swipe.durationThreshold && 1343 Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && 1344 Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { 1345 var direction = start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight"; 1346 1347 triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop }), true ); 1348 triggerCustomEvent( thisObject, direction,$.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ), true ); 1349 return true; 1350 } 1351 return false; 1352 1353 }, 1354 1355 // This serves as a flag to ensure that at most one swipe event event is 1356 // in work at any given time 1357 eventInProgress: false, 1358 1359 setup: function() { 1360 var events, 1361 thisObject = this, 1362 $this = $( thisObject ), 1363 context = {}; 1364 1365 // Retrieve the events data for this element and add the swipe context 1366 events = $.data( this, "mobile-events" ); 1367 if ( !events ) { 1368 events = { length: 0 }; 1369 $.data( this, "mobile-events", events ); 1370 } 1371 events.length++; 1372 events.swipe = context; 1373 1374 context.start = function( event ) { 1375 1376 // Bail if we're already working on a swipe event 1377 if ( $.event.special.swipe.eventInProgress ) { 1378 return; 1379 } 1380 $.event.special.swipe.eventInProgress = true; 1381 1382 var stop, 1383 start = $.event.special.swipe.start( event ), 1384 origTarget = event.target, 1385 emitted = false; 1386 1387 context.move = function( event ) { 1388 if ( !start || event.isDefaultPrevented() ) { 1389 return; 1390 } 1391 1392 stop = $.event.special.swipe.stop( event ); 1393 if ( !emitted ) { 1394 emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget ); 1395 if ( emitted ) { 1396 1397 // Reset the context to make way for the next swipe event 1398 $.event.special.swipe.eventInProgress = false; 1399 } 1400 } 1401 // prevent scrolling
1402 if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { 1403 event.preventDefault(); 1404 } 1405 }; 1406 1407 context.stop = function() { 1408 emitted = true; 1409 1410 // Reset the context to make way for the next swipe event 1411 $.event.special.swipe.eventInProgress = false; 1412 $document.off( touchMoveEvent, context.move ); 1413 context.move = null; 1414 }; 1415 1416 $document.on( touchMoveEvent, context.move ) 1417 .one( touchStopEvent, context.stop ); 1418 }; 1419 $this.on( touchStartEvent, context.start ); 1420 }, 1421 1422 teardown: function() { 1423 var events, context; 1424 1425 events = $.data( this, "mobile-events" ); 1426 if ( events ) { 1427 context = events.swipe; 1428 delete events.swipe; 1429 events.length--; 1430 if ( events.length === 0 ) { 1431 $.removeData( this, "mobile-events" ); 1432 } 1433 } 1434 1435 if ( context ) { 1436 if ( context.start ) { 1437 $( this ).off( touchStartEvent, context.start ); 1438 } 1439 if ( context.move ) { 1440 $document.off( touchMoveEvent, context.move ); 1441 } 1442 if ( context.stop ) { 1443 $document.off( touchStopEvent, context.stop ); 1444 } 1445 } 1446 } 1447 }; 1448 $.each({ 1449 swipeleft: "swipe.left", 1450 swiperight: "swipe.right" 1451 }, function( event, sourceEvent ) { 1452 1453 $.event.special[ event ] = { 1454 setup: function() { 1455 $( this ).bind( sourceEvent, $.noop ); 1456 }, 1457 teardown: function() { 1458 $( this ).unbind( sourceEvent ); 1459 } 1460 }; 1461 }); 1462})( jQuery, this ); 1463*/ 1464 1465!function(Foundation, $) { 1466 'use strict'; 1467 // Elements with [data-open] will reveal a plugin that supports it when clicked. 1468 $(document).on('click.zf.trigger', '[data-open]', function() { 1469 var id = $(this).data('open'); 1470 $('#' + id).triggerHandler('open.zf.trigger', [$(this)]); 1471 }); 1472 1473 // Elements with [data-close] will close a plugin that supports it when clicked. 1474 // If used without a value on [data-close], the event will bubble, allowing it to close a parent component. 1475 $(document).on('click.zf.trigger', '[data-close]', function() { 1476 var id = $(this).data('close'); 1477 if (id) { 1478 $('#' + id).triggerHandler('close.zf.trigger', [$(this)]); 1479 } 1480 else { 1481 $(this).trigger('close.zf.trigger'); 1482 } 1483 }); 1484 1485 // Elements with [data-toggle] will toggle a plugin that supports it when clicked. 1486 $(document).on('click.zf.trigger', '[data-toggle]', function() { 1487 var id = $(this).data('toggle'); 1488 $('#' + id).triggerHandler('toggle.zf.trigger', [$(this)]); 1489 }); 1490 1491 // Elements with [data-closable] will respond to close.zf.trigger events. 1492 $(document).on('close.zf.trigger', '[data-closable]', function(e){ 1493 e.stopPropagation(); 1494 var animation = $(this).data('closable'); 1495 1496 if(animation !== ''){ 1497 Foundation.Motion.animateOut($(this), animation, function() { 1498 $(this).trigger('closed.zf'); 1499 }); 1500 }else{ 1501 $(this).fadeOut().trigger('closed.zf'); 1502 } 1503 }); 1504 1505 var MutationObserver = (function () { 1506 var prefixes = ['WebKit', 'Moz', 'O', 'Ms', '']; 1507 for (var i=0; i < prefixes.length; i++) { 1508 if (prefixes[i] + 'MutationObserver' in window) { 1509 return window[prefixes[i] + 'MutationObserver']; 1510 } 1511 } 1512 return false; 1513 }()); 1514 1515 1516 var checkListeners = function(){ 1517 eventsListener(); 1518 resizeListener(); 1519 scrollListener(); 1520 closemeListener(); 1521 }; 1522 /** 1523 * Fires once after all other scripts have loaded 1524 * @function 1525 * @private 1526 */ 1527 $(window).load(function(){ 1528 checkListeners(); 1529 }); 1530 1531 //******** only fires this function once on load, if there's something to watch ******** 1532 var closemeListener = function(pluginName){ 1533 var yetiBoxes = $('[data-yeti-box]'), 1534 plugNames = ['dropdown', 'tooltip', 'reveal']; 1535 1536 if(pluginName){ 1537 if(typeof pluginName === 'string'){ 1538 plugNames.push(pluginName); 1539 }else if(typeof pluginName === 'object' && typeof pluginName[0] === 'string'){ 1540 plugNames.concat(pluginName); 1541 }else{ 1542 console.error('Plugin names must be strings'); 1543 } 1544 }
1545 if(yetiBoxes.length){ 1546 var listeners = plugNames.map(function(name){ 1547 return 'closeme.zf.' + name; 1548 }).join(' '); 1549 1550 $(window).off(listeners).on(listeners, function(e, pluginId){ 1551 var plugin = e.namespace.split('.')[0]; 1552 var plugins = $('[data-' + plugin + ']').not('[data-yeti-box="' + pluginId + '"]'); 1553 1554 plugins.each(function(){ 1555 var _this = $(this); 1556 1557 _this.triggerHandler('close.zf.trigger', [_this]); 1558 }); 1559 }); 1560 } 1561 }; 1562 var resizeListener = function(debounce){ 1563 var timer, 1564 $nodes = $('[data-resize]'); 1565 if($nodes.length){ 1566 $(window).off('resize.zf.trigger') 1567 .on('resize.zf.trigger', function(e) { 1568 if (timer) { clearTimeout(timer); } 1569 1570 timer = setTimeout(function(){ 1571 1572 if(!MutationObserver){//fallback for IE 9 1573 $nodes.each(function(){ 1574 $(this).triggerHandler('resizeme.zf.trigger'); 1575 }); 1576 } 1577 //trigger all listening elements and signal a resize event 1578 $nodes.attr('data-events', "resize"); 1579 }, debounce || 10);//default time to emit resize event 1580 }); 1581 } 1582 }; 1583 var scrollListener = function(debounce){ 1584 var timer, 1585 $nodes = $('[data-scroll]'); 1586 if($nodes.length){ 1587 $(window).off('scroll.zf.trigger') 1588 .on('scroll.zf.trigger', function(e){ 1589 if(timer){ clearTimeout(timer); } 1590 1591 timer = setTimeout(function(){ 1592 1593 if(!MutationObserver){//fallback for IE 9 1594 $nodes.each(function(){ 1595 $(this).triggerHandler('scrollme.zf.trigger'); 1596 }); 1597 } 1598 //trigger all listening elements and signal a scroll event 1599 $nodes.attr('data-events', "scroll"); 1600 }, debounce || 10);//default time to emit scroll event 1601 }); 1602 } 1603 }; 1604 // function domMutationObserver(debounce) { 1605 // // !!! This is coming soon and needs more work; not active !!! // 1606 // var timer, 1607 // nodes = document.querySelectorAll('[data-mutate]'); 1608 // // 1609 // if (nodes.length) { 1610 // // var MutationObserver = (function () { 1611 // // var prefixes = ['WebKit', 'Moz', 'O', 'Ms', '']; 1612 // // for (var i=0; i < prefixes.length; i++) { 1613 // // if (prefixes[i] + 'MutationObserver' in window) { 1614 // // return window[prefixes[i] + 'MutationObserver']; 1615 // // } 1616 // // } 1617 // // return false; 1618 // // }()); 1619 // 1620 // 1621 // //for the body, we need to listen for all changes effecting the style and class attributes 1622 // var bodyObserver = new MutationObserver(bodyMutation); 1623 // bodyObserver.observe(document.body, { attributes: true, childList: true, characterData: false, subtree:true, attributeFilter:["style", "class"]}); 1624 // 1625 // 1626 // //body callback 1627 // function bodyMutation(mutate) { 1628 // //trigger all listening elements and signal a mutation event 1629 // if (timer) { clearTimeout(timer); } 1630 // 1631 // timer = setTimeout(function() { 1632 // bodyObserver.disconnect(); 1633 // $('[data-mutate]').attr('data-events',"mutate"); 1634 // }, debounce || 150); 1635 // } 1636 // } 1637 // } 1638 var eventsListener = function() { 1639 if(!MutationObserver){ return false; } 1640 var nodes = document.querySelectorAll('[data-resize], [data-scroll], [data-mutate]'); 1641 1642 //element callback 1643 var listeningElementsMutation = function(mutationRecordsList) { 1644 var $target = $(mutationRecordsList[0].target); 1645 //trigger the event handler for the element depending on type 1646 switch ($target.attr("data-events")) { 1647 1648 case "resize" : 1649 $target.triggerHandler('resizeme.zf.trigger', [$target]); 1650 break; 1651 1652 case "scroll" : 1653 $target.triggerHandler('scrollme.zf.trigger', [$target, window.pageYOffset]); 1654 break; 1655 1656 // case "mutate" : 1657 // console.log('mutate', $target); 1658 // $target.triggerHandler('mutate.zf.trigger'); 1659 // 1660 // //make sure we don't get stuck in an infinite loop from sloppy codeing 1661 // if ($target.index('[data-mutate]') == $("[data-mutate]").length-1) { 1662 // domMutationObserver(); 1663 // } 1664 // break; 1665 1666 default : 1667 return false; 1668 //nothing 1669 } 1670 } 1671 1672 if(nodes.length){ 1673 //for each element that needs to listen for resizing, scrolling, (or coming soon mutation) add a single observer 1674 for (var i = 0; i <= nodes.length-1; i++) { 1675 var elementObserver = new MutationObserver(listeningElementsMutation); 1676 elementObserver.observe(nodes[i], { attributes: true, childList: false, characterData: false, subtree:false, attributeFilter:["data-events"]}); 1677 } 1678 } 1679 }; 1680 // ------------------------------------ 1681 1682 // [PH] 1683 // Foundation.CheckWatchers = checkWatchers; 1684 Foundation.IHearYou = checkListeners; 1685 // Foundation.ISeeYou = scrollListener; 1686 // Foundation.IFeelYou = closemeListener; 1687 1688}(window.Foundation, window.jQuery); 1689 1690!function(Foundation, $) { 1691 'use strict'; 1692 1693 /** 1694 * Creates a new instance of Abide. 1695 * @class 1696 * @fires Abide#init 1697 * @param {Object} element - jQuery object to add the trigger to. 1698 * @param {Object} options - Overrides to the default plugin settings. 1699 */ 1700 function Abide(element, options) { 1701 this.$element = element; 1702 this.options = $.extend({}, Abide.defaults, this.$element.data(), options); 1703 1704 this._init(); 1705 1706 Foundation.registerPlugin(this, 'Abide'); 1707 } 1708 1709 /** 1710 * Default settings for plugin 1711 */ 1712 Abide.defaults = { 1713 /** 1714 * The default event to validate inputs. Checkboxes and radios validate immediately. 1715 * Remove or change this value for manual validation. 1716 * @option 1717 * @example 'fieldChange' 1718 */ 1719 validateOn: 'fieldChange', 1720 /** 1721 * Class to be applied to input labels on failed validation. 1722 * @option 1723 * @example 'is-invalid-label' 1724 */ 1725 labelErrorClass: 'is-invalid-label', 1726 /** 1727 * Class to be applied to inputs on failed validation. 1728 * @option 1729 * @example 'is-invalid-input' 1730 */ 1731 inputErrorClass: 'is-invalid-input', 1732 /** 1733 * Class selector to use to target Form Errors for show/hide. 1734 * @option 1735 * @example '.form-error' 1736 */ 1737 formErrorSelector: '.form-error', 1738 /** 1739 * Class added to Form Errors on failed validation. 1740 * @option 1741 * @example 'is-visible' 1742 */ 1743 formErrorClass: 'is-visible', 1744 /** 1745 * Set to true to validate text inputs on any value change. 1746 * @option 1747 * @example false 1748 */ 1749 liveValidate: false, 1750 1751 patterns: { 1752 alpha : /^[a-zA-Z]+$/,
1753 alpha_numeric : /^[a-zA-Z0-9]+$/, 1754 integer : /^[-+]?\d+$/, 1755 number : /^[-+]?\d*(?:[\.\,]\d+)?$/, 1756 1757 // amex, visa, diners 1758 card : /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/, 1759 cvv : /^([0-9]){3,4}$/, 1760 1761 // http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address 1762 email : /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/, 1763 1764 url : /^(https?|ftp|file|ssh):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/, 1765 // abc.de 1766 domain : /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,8}$/, 1767 1768 datetime : /^([0-2][0-9]{3})\-([0-1][0-9])\-([0-3][0-9])T([0-5][0-9])\:([0-5][0-9])\:([0-5][0-9])(Z|([\-\+]([0-1][0-9])\:00))$/, 1769 // YYYY-MM-DD 1770 date : /(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))$/, 1771 // HH:MM:SS 1772 time : /^(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}$/, 1773 dateISO : /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/, 1774 // MM/DD/YYYY 1775 month_day_year : /^(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.]\d{4}$/, 1776 // DD/MM/YYYY 1777 day_month_year : /^(0[1-9]|[12][0-9]|3[01])[- \/.](0[1-9]|1[012])[- \/.]\d{4}$/, 1778 1779 // #FFF or #FFFFFF 1780 color : /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/ 1781 }, 1782 /** 1783 * Optional validation functions to be used. `equalTo` being the only default included function. 1784 * Functions should return only a boolean if the input is valid or not. Functions are given the following arguments: 1785 * el : The jQuery element to validate. 1786 * required : Boolean value of the required attribute be present or not. 1787 * parent : The direct parent of the input. 1788 * @option 1789 */ 1790 validators: { 1791 equalTo: function (el, required, parent) { 1792 return $('#' + el.attr('data-equalto')).val() === el.val(); 1793 } 1794 } 1795 }; 1796 1797 1798 /** 1799 * Initializes the Abide plugin and calls functions to get Abide functioning on load. 1800 * @private 1801 */ 1802 Abide.prototype._init = function(){ 1803 this.$inputs = this.$element.find('input, textarea, select').not('[data-abide-ignore]'); 1804 1805 this._events(); 1806 }; 1807 1808 /** 1809 * Initializes events for Abide. 1810 * @private 1811 */ 1812 Abide.prototype._events = function() { 1813 var _this = this; 1814 1815 this.$element.off('.abide') 1816 .on('reset.zf.abide', function(e){ 1817 _this.resetForm(); 1818 }) 1819 .on('submit.zf.abide', function(e){ 1820 return _this.validateForm(); 1821 }); 1822 1823 if(this.options.validateOn === 'fieldChange'){ 1824 this.$inputs.off('change.zf.abide') 1825 .on('change.zf.abide', function(e){ 1826 _this.validateInput($(this)); 1827 }); 1828 } 1829 1830 if(this.options.liveValidate){ 1831 this.$inputs.off('input.zf.abide') 1832 .on('input.zf.abide', function(e){
1833 _this.validateInput($(this)); 1834 }); 1835 } 1836 }, 1837 /** 1838 * Calls necessary functions to update Abide upon DOM change 1839 * @private 1840 */ 1841 Abide.prototype._reflow = function() { 1842 this._init(); 1843 }; 1844 /** 1845 * Checks whether or not a form element has the required attribute and if it's checked or not 1846 * @param {Object} element - jQuery object to check for required attribute 1847 * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty 1848 */ 1849 Abide.prototype.requiredCheck = function($el) { 1850 if(!$el.attr('required')) return true; 1851 var isGood = true; 1852 switch ($el[0].type) { 1853 1854 case 'checkbox': 1855 case 'radio': 1856 isGood = $el[0].checked; 1857 break; 1858 1859 case 'select': 1860 case 'select-one': 1861 case 'select-multiple': 1862 var opt = $el.find('option:selected'); 1863 if(!opt.length || !opt.val()) isGood = false; 1864 break; 1865 1866 default: 1867 if(!$el.val() || !$el.val().length) isGood = false; 1868 } 1869 return isGood; 1870 }; 1871 /** 1872 * Based on $el, get the first element with selector in this order: 1873 * 1. The element's direct sibling('s). 1874 * 3. The element's parent's children. 1875 * 1876 * This allows for multiple form errors per input, though if none are found, no form errors will be shown. 1877 * 1878 * @param {Object} $el - jQuery object to use as reference to find the form error selector. 1879 * @returns {Object} jQuery object with the selector. 1880 */ 1881 Abide.prototype.findFormError = function($el){ 1882 var $error = $el.siblings(this.options.formErrorSelector); 1883 if(!$error.length){ 1884 $error = $el.parent().find(this.options.formErrorSelector); 1885 } 1886 return $error; 1887 }; 1888 /** 1889 * Get the first element in this order: 1890 * 2. The <label> with the attribute `[for="someInputId"]` 1891 * 3. The `.closest()` <label> 1892 * 1893 * @param {Object} $el - jQuery object to check for required attribute 1894 * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty 1895 */ 1896 Abide.prototype.findLabel = function($el) { 1897 var $label = this.$element.find('label[for="' + $el[0].id + '"]'); 1898 if(!$label.length){ 1899 return $el.closest('label'); 1900 } 1901 return $label; 1902 }; 1903 /** 1904 * Adds the CSS error class as specified by the Abide settings to the label, input, and the form 1905 * @param {Object} $el - jQuery object to add the class to 1906 */ 1907 Abide.prototype.addErrorClasses = function($el){ 1908 var $label = this.findLabel($el), 1909 $formError = this.findFormError($el); 1910 1911 if($label.length){ 1912 $label.addClass(this.options.labelErrorClass); 1913 } 1914 if($formError.length){ 1915 $formError.addClass(this.options.formErrorClass); 1916 } 1917 $el.addClass(this.options.inputErrorClass).attr('data-invalid', ''); 1918 }; 1919 /** 1920 * Removes CSS error class as specified by the Abide settings from the label, input, and the form 1921 * @param {Object} $el - jQuery object to remove the class from 1922 */ 1923 Abide.prototype.removeErrorClasses = function($el){ 1924 var $label = this.findLabel($el), 1925 $formError = this.findFormError($el); 1926 1927 if($label.length){ 1928 $label.removeClass(this.options.labelErrorClass); 1929 } 1930 if($formError.length){ 1931 $formError.removeClass(this.options.formErrorClass); 1932 } 1933 $el.removeClass(this.options.inputErrorClass).removeAttr('data-invalid'); 1934 }; 1935 /** 1936 * Goes through a form to find inputs and proceeds to validate them in ways specific to their type 1937 * @fires Abide#invalid 1938 * @fires Abide#valid 1939 * @param {Object} element - jQuery object to validate, should be an HTML input 1940 * @returns {Boolean} goodToGo - If the input is valid or not. 1941 */ 1942 Abide.prototype.validateInput = function($el){ 1943 var clearRequire = this.requiredCheck($el), 1944 validated = false, 1945 customValidator = true, 1946 validator = $el.attr('data-validator'), 1947 equalTo = true; 1948 1949 switch ($el[0].type) { 1950 1951 case 'radio': 1952 validated = this.validateRadio($el.attr('name')); 1953 break; 1954 1955 case 'checkbox': 1956 validated = clearRequire; 1957 break; 1958 1959 case 'select': 1960 case 'select-one': 1961 case 'select-multiple': 1962 validated = clearRequire; 1963 break; 1964 1965 default: 1966 validated = this.validateText($el); 1967 } 1968 1969 if(validator){ customValidator = this.matchValidation($el, validator, $el.attr('required')); } 1970 if($el.attr('data-equalto')){ equalTo = this.options.validators.equalTo($el); } 1971 1972 var goodToGo = [clearRequire, validated, customValidator, equalTo].indexOf(false) === -1,
1973 message = (goodToGo ? 'valid' : 'invalid') + '.zf.abide'; 1974 1975 this[goodToGo ? 'removeErrorClasses' : 'addErrorClasses']($el); 1976 1977 /** 1978 * Fires when the input is done checking for validation. Event trigger is either `valid.zf.abide` or `invalid.zf.abide` 1979 * Trigger includes the DOM element of the input. 1980 * @event Abide#valid 1981 * @event Abide#invalid 1982 */ 1983 $el.trigger(message, [$el]); 1984 1985 return goodToGo; 1986 }; 1987 /** 1988 * Goes through a form and if there are any invalid inputs, it will display the form error element 1989 * @returns {Boolean} noError - true if no errors were detected... 1990 * @fires Abide#formvalid 1991 * @fires Abide#forminvalid 1992 */ 1993 Abide.prototype.validateForm = function(){ 1994 var acc = [], 1995 _this = this; 1996 1997 this.$inputs.each(function(){ 1998 acc.push(_this.validateInput($(this))); 1999 }); 2000 2001 var noError = acc.indexOf(false) === -1; 2002 2003 this.$element.find('[data-abide-error]').css('display', (noError ? 'none' : 'block')); 2004 /** 2005 * Fires when the form is finished validating. Event trigger is either `formvalid.zf.abide` or `forminvalid.zf.abide`. 2006 * Trigger includes the element of the form. 2007 * @event Abide#formvalid 2008 * @event Abide#forminvalid 2009 */ 2010 this.$element.trigger((noError ? 'formvalid' : 'forminvalid') + '.zf.abide', [this.$element]); 2011 2012 return noError; 2013 }; 2014 /** 2015 * Determines whether or a not a text input is valid based on the pattern specified in the attribute. If no matching pattern is found, returns true. 2016 * @param {Object} $el - jQuery object to validate, should be a text input HTML element 2017 * @param {String} pattern - string value of one of the RegEx patterns in Abide.options.patterns 2018 * @returns {Boolean} Boolean value depends on whether or not the input value matches the pattern specified 2019 */ 2020 Abide.prototype.validateText = function($el, pattern){ 2021 // pattern = pattern ? pattern : $el.attr('pattern') ? $el.attr('pattern') : $el.attr('type'); 2022 pattern = (pattern || $el.attr('pattern') || $el.attr('type')); 2023 var inputText = $el.val(); 2024 2025 return inputText.length ?//if text, check if the pattern exists, if so, test it, if no text or no pattern, return true. 2026 this.options.patterns.hasOwnProperty(pattern) ? this.options.patterns[pattern].test(inputText) : 2027 pattern && pattern !== $el.attr('type') ? new RegExp(pattern).test(inputText) : true : true; 2028 }; /** 2029 * Determines whether or a not a radio input is valid based on whether or not it is required and selected 2030 * @param {String} groupName - A string that specifies the name of a radio button group 2031 * @returns {Boolean} Boolean value depends on whether or not at least one radio input has been selected (if it's required) 2032 */ 2033 Abide.prototype.validateRadio = function(groupName){ 2034 var $group = this.$element.find(':radio[name="' + groupName + '"]'), 2035 counter = [], 2036 _this = this; 2037 2038 $group.each(function(){ 2039 var rdio = $(this), 2040 clear = _this.requiredCheck(rdio); 2041 counter.push(clear); 2042 if(clear) _this.removeErrorClasses(rdio); 2043 }); 2044 2045 return counter.indexOf(false) === -1; 2046 }; 2047 /** 2048 * Determines if a selected input passes a custom validation function. Multiple validations can be used, if passed to the element with `data-validator="foo bar baz"` in a space separated listed. 2049 * @param {Object} $el - jQuery input element. 2050 * @param {String} validators - a string of function names matching functions in the Abide.options.validators object. 2051 * @param {Boolean} required - self explanatory? 2052 * @returns {Boolean} - true if validations passed. 2053 */ 2054 Abide.prototype.matchValidation = function($el, validators, required){ 2055 var _this = this; 2056 required = required ? true : false; 2057 var clear = validators.split(' ').map(function(v){ 2058 return _this.options.validators[v]($el, required, $el.parent()); 2059 }); 2060 return clear.indexOf(false) === -1; 2061 }; 2062 /** 2063 * Resets form inputs and styles 2064 * @fires Abide#formreset 2065 */ 2066 Abide.prototype.resetForm = function() { 2067 var $form = this.$element, 2068 opts = this.options; 2069 2070 $('.' + opts.labelErrorClass, $form).not('small').removeClass(opts.labelErrorClass); 2071 $('.' + opts.inputErrorClass, $form).not('small').removeClass(opts.inputErrorClass); 2072 $(opts.formErrorSelector + '.' + opts.formErrorClass).removeClass(opts.formErrorClass); 2073 $form.find('[data-abide-error]').css('display', 'none'); 2074 $(':input', $form).not(':button, :submit, :reset, :hidden, [data-abide-ignore]').val('').removeAttr('data-invalid'); 2075 /** 2076 * Fires when the form has been reset. 2077 * @event Abide#formreset 2078 */ 2079 $form.trigger('formreset.zf.abide', [$form]); 2080 }; 2081 /** 2082 * Destroys an instance of Abide. 2083 * Removes error styles and classes from elements, without resetting their values. 2084 */ 2085 Abide.prototype.destroy = function(){ 2086 var _this = this; 2087 this.$element.off('.abide') 2088 .find('[data-abide-error]').css('display', 'none'); 2089 this.$inputs.off('.abide') 2090 .each(function(){ 2091 _this.removeErrorClasses($(this)); 2092 }); 2093 2094 Foundation.unregisterPlugin(this); 2095 }; 2096 2097 Foundation.plugin(Abide, 'Abide'); 2098 2099 // Exports for AMD/Browserify 2100 if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') 2101 module.exports = Abide; 2102 if (typeof define === 'function') 2103 define(['foundation'], function() { 2104 return Abide; 2105 });
2106 2107}(Foundation, jQuery); 2108 2109/** 2110 * Accordion module. 2111 * @module foundation.accordion 2112 * @requires foundation.util.keyboard 2113 * @requires foundation.util.motion 2114 */ 2115!function($, Foundation) { 2116 'use strict'; 2117 2118 /** 2119 * Creates a new instance of an accordion. 2120 * @class 2121 * @fires Accordion#init 2122 * @param {jQuery} element - jQuery object to make into an accordion. 2123 * @param {Object} options - a plain object with settings to override the default options. 2124 */ 2125 function Accordion(element, options){ 2126 this.$element = element; 2127 this.options = $.extend({}, Accordion.defaults, this.$element.data(), options); 2128 2129 this._init(); 2130 2131 Foundation.registerPlugin(this, 'Accordion'); 2132 Foundation.Keyboard.register('Accordion', { 2133 'ENTER': 'toggle', 2134 'SPACE': 'toggle', 2135 'ARROW_DOWN': 'next', 2136 'ARROW_UP': 'previous' 2137 }); 2138 } 2139 2140 Accordion.defaults = { 2141 /** 2142 * Amount of time to animate the opening of an accordion pane. 2143 * @option 2144 * @example 250 2145 */ 2146 slideSpeed: 250, 2147 /** 2148 * Allow the accordion to have multiple open panes. 2149 * @option 2150 * @example false 2151 */ 2152 multiExpand: false, 2153 /** 2154 * Allow the accordion to close all panes. 2155 * @option 2156 * @example false 2157 */ 2158 allowAllClosed: false 2159 }; 2160 2161 /** 2162 * Initializes the accordion by animating the preset active pane(s). 2163 * @private 2164 */ 2165 Accordion.prototype._init = function() { 2166 this.$element.attr('role', 'tablist'); 2167 this.$tabs = this.$element.children('li'); 2168 if (this.$tabs.length === 0) { 2169 this.$tabs = this.$element.children('[data-accordion-item]'); 2170 } 2171 this.$tabs.each(function(idx, el){ 2172 2173 var $el = $(el), 2174 $content = $el.find('[data-tab-content]'), 2175 id = $content[0].id || Foundation.GetYoDigits(6, 'accordion'), 2176 linkId = el.id || id + '-label'; 2177 2178 $el.find('a:first').attr({ 2179 'aria-controls': id, 2180 'role': 'tab', 2181 'id': linkId, 2182 'aria-expanded': false, 2183 'aria-selected': false 2184 }); 2185 $content.attr({'role': 'tabpanel', 'aria-labelledby': linkId, 'aria-hidden': true, 'id': id}); 2186 }); 2187 var $initActive = this.$element.find('.is-active').children('[data-tab-content]'); 2188 if($initActive.length){ 2189 this.down($initActive, true); 2190 } 2191 this._events(); 2192 }; 2193 2194 /** 2195 * Adds event handlers for items within the accordion. 2196 * @private 2197 */ 2198 Accordion.prototype._events = function() { 2199 var _this = this; 2200 2201 this.$tabs.each(function(){ 2202 var $elem = $(this); 2203 var $tabContent = $elem.children('[data-tab-content]'); 2204 if ($tabContent.length) { 2205 $elem.children('a').off('click.zf.accordion keydown.zf.accordion') 2206 .on('click.zf.accordion', function(e){ 2207 // $(this).children('a').on('click.zf.accordion', function(e) { 2208 e.preventDefault(); 2209 if ($elem.hasClass('is-active')) { 2210 if(_this.options.allowAllClosed || $elem.siblings().hasClass('is-active')){ 2211 _this.up($tabContent); 2212 } 2213 } 2214 else { 2215 _this.down($tabContent); 2216 } 2217 }).on('keydown.zf.accordion', function(e){ 2218 Foundation.Keyboard.handleKey(e, 'Accordion', { 2219 toggle: function() { 2220 _this.toggle($tabContent); 2221 }, 2222 next: function() { 2223 $elem.next().find('a').focus().trigger('click.zf.accordion'); 2224 }, 2225 previous: function() { 2226 $elem.prev().find('a').focus().trigger('click.zf.accordion'); 2227 }, 2228 handled: function() { 2229 e.preventDefault(); 2230 e.stopPropagation(); 2231 } 2232 }); 2233 }); 2234 } 2235 }); 2236 }; 2237 /** 2238 * Toggles the selected content pane's open/close state. 2239 * @param {jQuery} $target - jQuery object of the pane to toggle. 2240 * @function 2241 */ 2242 Accordion.prototype.toggle = function($target){ 2243 if($target.parent().hasClass('is-active')){ 2244 if(this.options.allowAllClosed || $target.parent().siblings().hasClass('is-active')){ 2245 this.up($target); 2246 }else{ return; } 2247 }else{ 2248 this.down($target); 2249 } 2250 }; 2251 /** 2252 * Opens the accordion tab defined by `$target`. 2253 * @param {jQuery} $target - Accordion pane to open. 2254 * @param {Boolean} firstTime - flag to determine if reflow should happen. 2255 * @fires Accordion#down 2256 * @function 2257 */ 2258 Accordion.prototype.down = function($target, firstTime) { 2259 var _this = this; 2260 if(!this.options.multiExpand && !firstTime){ 2261 var $currentActive = this.$element.find('.is-active').children('[data-tab-content]'); 2262 if($currentActive.length){ 2263 this.up($currentActive); 2264 } 2265 } 2266 2267 $target 2268 .attr('aria-hidden', false) 2269 .parent('[data-tab-content]') 2270 .addBack() 2271 .parent().addClass('is-active'); 2272 2273 // Foundation.Move(_this.options.slideSpeed, $target, function(){ 2274 $target.slideDown(_this.options.slideSpeed, function () { 2275 /** 2276 * Fires when the tab is done opening. 2277 * @event Accordion#down 2278 */ 2279 _this.$element.trigger('down.zf.accordion', [$target]); 2280 }); 2281 // }); 2282 2283 // if(!firstTime){ 2284 // Foundation._reflow(this.$element.attr('data-accordion')); 2285 // } 2286 $('#' + $target.attr('aria-labelledby')).attr({ 2287 'aria-expanded': true, 2288 'aria-selected': true 2289 });
2290 }; 2291 2292 /** 2293 * Closes the tab defined by `$target`. 2294 * @param {jQuery} $target - Accordion tab to close. 2295 * @fires Accordion#up 2296 * @function 2297 */ 2298 Accordion.prototype.up = function($target) { 2299 var $aunts = $target.parent().siblings(), 2300 _this = this; 2301 var canClose = this.options.multiExpand ? $aunts.hasClass('is-active') : $target.parent().hasClass('is-active'); 2302 2303 if(!this.options.allowAllClosed && !canClose){ 2304 return; 2305 } 2306 2307 // Foundation.Move(this.options.slideSpeed, $target, function(){ 2308 $target.slideUp(_this.options.slideSpeed, function () { 2309 /** 2310 * Fires when the tab is done collapsing up. 2311 * @event Accordion#up 2312 */ 2313 _this.$element.trigger('up.zf.accordion', [$target]); 2314 }); 2315 // }); 2316 2317 $target.attr('aria-hidden', true) 2318 .parent().removeClass('is-active'); 2319 2320 $('#' + $target.attr('aria-labelledby')).attr({ 2321 'aria-expanded': false, 2322 'aria-selected': false 2323 }); 2324 }; 2325 2326 /** 2327 * Destroys an instance of an accordion. 2328 * @fires Accordion#destroyed 2329 * @function 2330 */ 2331 Accordion.prototype.destroy = function() { 2332 this.$element.find('[data-tab-content]').slideUp(0).css('display', ''); 2333 this.$element.find('a').off('.zf.accordion'); 2334 2335 Foundation.unregisterPlugin(this); 2336 }; 2337 2338 Foundation.plugin(Accordion, 'Accordion'); 2339}(jQuery, window.Foundation); 2340 2341/** 2342 * AccordionMenu module. 2343 * @module foundation.accordionMenu 2344 * @requires foundation.util.keyboard 2345 * @requires foundation.util.motion 2346 * @requires foundation.util.nest 2347 */ 2348!function($) { 2349 'use strict'; 2350 2351 /** 2352 * Creates a new instance of an accordion menu. 2353 * @class 2354 * @fires AccordionMenu#init 2355 * @param {jQuery} element - jQuery object to make into an accordion menu. 2356 * @param {Object} options - Overrides to the default plugin settings. 2357 */ 2358 function AccordionMenu(element, options) { 2359 this.$element = element; 2360 this.options = $.extend({}, AccordionMenu.defaults, this.$element.data(), options); 2361 2362 Foundation.Nest.Feather(this.$element, 'accordion'); 2363 2364 this._init(); 2365 2366 Foundation.registerPlugin(this, 'AccordionMenu'); 2367 Foundation.Keyboard.register('AccordionMenu', { 2368 'ENTER': 'toggle', 2369 'SPACE': 'toggle', 2370 'ARROW_RIGHT': 'open', 2371 'ARROW_UP': 'up', 2372 'ARROW_DOWN': 'down', 2373 'ARROW_LEFT': 'close', 2374 'ESCAPE': 'closeAll', 2375 'TAB': 'down', 2376 'SHIFT_TAB': 'up' 2377 }); 2378 } 2379 2380 AccordionMenu.defaults = { 2381 /** 2382 * Amount of time to animate the opening of a submenu in ms. 2383 * @option 2384 * @example 250 2385 */ 2386 slideSpeed: 250, 2387 /** 2388 * Allow the menu to have multiple open panes. 2389 * @option 2390 * @example true 2391 */ 2392 multiOpen: true 2393 }; 2394 2395 /** 2396 * Initializes the accordion menu by hiding all nested menus. 2397 * @private 2398 */ 2399 AccordionMenu.prototype._init = function() { 2400 this.$element.find('[data-submenu]').not('.is-active').slideUp(0);//.find('a').css('padding-left', '1rem'); 2401 this.$element.attr({ 2402 'role': 'tablist', 2403 'aria-multiselectable': this.options.multiOpen 2404 }); 2405 2406 this.$menuLinks = this.$element.find('.is-accordion-submenu-parent'); 2407 this.$menuLinks.each(function(){ 2408 var linkId = this.id || Foundation.GetYoDigits(6, 'acc-menu-link'), 2409 $elem = $(this), 2410 $sub = $elem.children('[data-submenu]'), 2411 subId = $sub[0].id || Foundation.GetYoDigits(6, 'acc-menu'), 2412 isActive = $sub.hasClass('is-active'); 2413 $elem.attr({ 2414 'aria-controls': subId, 2415 'aria-expanded': isActive, 2416 'role': 'tab', 2417 'id': linkId 2418 }); 2419 $sub.attr({ 2420 'aria-labelledby': linkId, 2421 'aria-hidden': !isActive, 2422 'role': 'tabpanel', 2423 'id': subId 2424 }); 2425 }); 2426 var initPanes = this.$element.find('.is-active'); 2427 if(initPanes.length){ 2428 var _this = this; 2429 initPanes.each(function(){ 2430 _this.down($(this)); 2431 }); 2432 } 2433 this._events(); 2434 }; 2435 2436 /** 2437 * Adds event handlers for items within the menu. 2438 * @private 2439 */ 2440 AccordionMenu.prototype._events = function() { 2441 var _this = this; 2442 2443 this.$element.find('li').each(function() { 2444 var $submenu = $(this).children('[data-submenu]'); 2445 2446 if ($submenu.length) { 2447 $(this).children('a').off('click.zf.accordionMenu').on('click.zf.accordionMenu', function(e) { 2448 e.preventDefault(); 2449 2450 _this.toggle($submenu); 2451 }); 2452 } 2453 }).on('keydown.zf.accordionmenu', function(e){
2454 var $element = $(this), 2455 $elements = $element.parent('ul').children('li'), 2456 $prevElement, 2457 $nextElement, 2458 $target = $element.children('[data-submenu]'); 2459 2460 $elements.each(function(i) { 2461 if ($(this).is($element)) { 2462 $prevElement = $elements.eq(Math.max(0, i-1)); 2463 $nextElement = $elements.eq(Math.min(i+1, $elements.length-1)); 2464 2465 if ($(this).children('[data-submenu]:visible').length) { // has open sub menu 2466 $nextElement = $element.find('li:first-child'); 2467 } 2468 if ($(this).is(':first-child')) { // is first element of sub menu 2469 $prevElement = $element.parents('li').first(); 2470 } else if ($prevElement.children('[data-submenu]:visible').length) { // if previous element has open sub menu 2471 $prevElement = $prevElement.find('li:last-child'); 2472 } 2473 if ($(this).is(':last-child')) { // is last element of sub menu 2474 $nextElement = $element.parents('li').first().next('li'); 2475 } 2476 2477 return; 2478 } 2479 }); 2480 Foundation.Keyboard.handleKey(e, 'AccordionMenu', { 2481 open: function() { 2482 if ($target.is(':hidden')) { 2483 _this.down($target); 2484 $target.find('li').first().focus(); 2485 } 2486 }, 2487 close: function() { 2488 if ($target.length && !$target.is(':hidden')) { // close active sub of this item 2489 _this.up($target); 2490 } else if ($element.parent('[data-submenu]').length) { // close currently open sub 2491 _this.up($element.parent('[data-submenu]')); 2492 $element.parents('li').first().focus(); 2493 } 2494 }, 2495 up: function() { 2496 $prevElement.focus(); 2497 }, 2498 down: function() { 2499 $nextElement.focus(); 2500 }, 2501 toggle: function() { 2502 if ($element.children('[data-submenu]').length) { 2503 _this.toggle($element.children('[data-submenu]')); 2504 } 2505 }, 2506 closeAll: function() { 2507 _this.hideAll(); 2508 }, 2509 handled: function() { 2510 e.preventDefault(); 2511 e.stopImmediatePropagation(); 2512 } 2513 }); 2514 });//.attr('tabindex', 0); 2515 }; 2516 /** 2517 * Closes all panes of the menu. 2518 * @function 2519 */ 2520 AccordionMenu.prototype.hideAll = function(){ 2521 this.$element.find('[data-submenu]').slideUp(this.options.slideSpeed); 2522 }; 2523 /** 2524 * Toggles the open/close state of a submenu. 2525 * @function 2526 * @param {jQuery} $target - the submenu to toggle 2527 */ 2528 AccordionMenu.prototype.toggle = function($target){ 2529 if(!$target.is(':animated')) { 2530 if (!$target.is(':hidden')) { 2531 this.up($target); 2532 } 2533 else { 2534 this.down($target); 2535 } 2536 } 2537 }; 2538 /** 2539 * Opens the sub-menu defined by `$target`. 2540 * @param {jQuery} $target - Sub-menu to open. 2541 * @fires AccordionMenu#down 2542 */ 2543 AccordionMenu.prototype.down = function($target) { 2544 var _this = this; 2545 2546 if(!this.options.multiOpen){ 2547 this.up(this.$element.find('.is-active').not($target.parentsUntil(this.$element).add($target))); 2548 } 2549 2550 $target.addClass('is-active').attr({'aria-hidden': false}) 2551 .parent('.is-accordion-submenu-parent').attr({'aria-expanded': true}); 2552 2553 Foundation.Move(this.options.slideSpeed, $target, function(){ 2554 $target.slideDown(_this.options.slideSpeed, function () { 2555 /** 2556 * Fires when the menu is done opening. 2557 * @event AccordionMenu#down 2558 */ 2559 _this.$element.trigger('down.zf.accordionMenu', [$target]); 2560 }); 2561 }); 2562 }; 2563 2564 /** 2565 * Closes the sub-menu defined by `$target`. All sub-menus inside the target will be closed as well. 2566 * @param {jQuery} $target - Sub-menu to close.
2567 * @fires AccordionMenu#up 2568 */ 2569 AccordionMenu.prototype.up = function($target) { 2570 var _this = this; 2571 Foundation.Move(this.options.slideSpeed, $target, function(){ 2572 $target.slideUp(_this.options.slideSpeed, function () { 2573 /** 2574 * Fires when the menu is done collapsing up. 2575 * @event AccordionMenu#up 2576 */ 2577 _this.$element.trigger('up.zf.accordionMenu', [$target]); 2578 }); 2579 }); 2580 2581 var $menus = $target.find('[data-submenu]').slideUp(0).addBack().attr('aria-hidden', true); 2582 2583 $menus.parent('.is-accordion-submenu-parent').attr('aria-expanded', false); 2584 }; 2585 2586 /** 2587 * Destroys an instance of accordion menu. 2588 * @fires AccordionMenu#destroyed 2589 */ 2590 AccordionMenu.prototype.destroy = function(){ 2591 this.$element.find('[data-submenu]').slideDown(0).css('display', ''); 2592 this.$element.find('a').off('click.zf.accordionMenu'); 2593 2594 Foundation.Nest.Burn(this.$element, 'accordion'); 2595 Foundation.unregisterPlugin(this); 2596 }; 2597 2598 Foundation.plugin(AccordionMenu, 'AccordionMenu'); 2599}(jQuery, window.Foundation); 2600 2601/** 2602 * Drilldown module. 2603 * @module foundation.drilldown 2604 * @requires foundation.util.keyboard 2605 * @requires foundation.util.motion 2606 * @requires foundation.util.nest 2607 */ 2608!function($, Foundation){ 2609 'use strict'; 2610 2611 /** 2612 * Creates a new instance of a drilldown menu. 2613 * @class 2614 * @param {jQuery} element - jQuery object to make into an accordion menu. 2615 * @param {Object} options - Overrides to the default plugin settings. 2616 */ 2617 function Drilldown(element, options){ 2618 this.$element = element; 2619 this.options = $.extend({}, Drilldown.defaults, this.$element.data(), options); 2620 2621 Foundation.Nest.Feather(this.$element, 'drilldown'); 2622 2623 this._init(); 2624 2625 Foundation.registerPlugin(this, 'Drilldown'); 2626 Foundation.Keyboard.register('Drilldown', { 2627 'ENTER': 'open', 2628 'SPACE': 'open', 2629 'ARROW_RIGHT': 'next', 2630 'ARROW_UP': 'up', 2631 'ARROW_DOWN': 'down', 2632 'ARROW_LEFT': 'previous', 2633 'ESCAPE': 'close', 2634 'TAB': 'down', 2635 'SHIFT_TAB': 'up' 2636 }); 2637 } 2638 Drilldown.defaults = { 2639 /** 2640 * Markup used for JS generated back button. Prepended to submenu lists and deleted on `destroy` method, 'js-drilldown-back' class required. Remove the backslash (`\`) if copy and pasting. 2641 * @option 2642 * @example '<\li><\a>Back<\/a><\/li>' 2643 */ 2644 backButton: '<li class="js-drilldown-back"><a>Back</a></li>', 2645 /** 2646 * Markup used to wrap drilldown menu. Use a class name for independent styling; the JS applied class: `is-drilldown` is required. Remove the backslash (`\`) if copy and pasting. 2647 * @option 2648 * @example '<\div class="is-drilldown"><\/div>' 2649 */ 2650 wrapper: '<div></div>', 2651 /** 2652 * Allow the menu to return to root list on body click. 2653 * @option 2654 * @example false 2655 */ 2656 closeOnClick: false 2657 // holdOpen: false 2658 }; 2659 /** 2660 * Initializes the drilldown by creating jQuery collections of elements 2661 * @private 2662 */ 2663 Drilldown.prototype._init = function(){ 2664 this.$submenuAnchors = this.$element.find('li.is-drilldown-submenu-parent'); 2665 this.$submenus = this.$submenuAnchors.children('[data-submenu]'); 2666 this.$menuItems = this.$element.find('li').not('.js-drilldown-back').attr('role', 'menuitem'); 2667 2668 this._prepareMenu(); 2669 2670 this._keyboardEvents(); 2671 }; 2672 /** 2673 * prepares drilldown menu by setting attributes to links and elements 2674 * sets a min height to prevent content jumping 2675 * wraps the element if not already wrapped 2676 * @private 2677 * @function 2678 */ 2679 Drilldown.prototype._prepareMenu = function(){ 2680 var _this = this; 2681 // if(!this.options.holdOpen){ 2682 // this._menuLinkEvents(); 2683 // } 2684 this.$submenuAnchors.each(function(){ 2685 var $sub = $(this); 2686 var $link = $sub.find('a:first'); 2687 $link.data('savedHref', $link.attr('href')).removeAttr('href'); 2688 $sub.children('[data-submenu]') 2689 .attr({ 2690 'aria-hidden': true, 2691 'tabindex': 0, 2692 'role': 'menu' 2693 }); 2694 _this._events($sub); 2695 });
2696 this.$submenus.each(function(){ 2697 var $menu = $(this), 2698 $back = $menu.find('.js-drilldown-back'); 2699 if(!$back.length){ 2700 $menu.prepend(_this.options.backButton); 2701 } 2702 _this._back($menu); 2703 }); 2704 if(!this.$element.parent().hasClass('is-drilldown')){ 2705 this.$wrapper = $(this.options.wrapper).addClass('is-drilldown').css(this._getMaxDims()); 2706 this.$element.wrap(this.$wrapper); 2707 } 2708 2709 }; 2710 /** 2711 * Adds event handlers to elements in the menu. 2712 * @function 2713 * @private 2714 * @param {jQuery} $elem - the current menu item to add handlers to. 2715 */ 2716 Drilldown.prototype._events = function($elem){ 2717 var _this = this; 2718 2719 $elem.off('click.zf.drilldown') 2720 .on('click.zf.drilldown', function(e){ 2721 if($(e.target).parentsUntil('ul', 'li').hasClass('is-drilldown-submenu-parent')){ 2722 e.stopImmediatePropagation(); 2723 e.preventDefault(); 2724 } 2725 2726 // if(e.target !== e.currentTarget.firstElementChild){ 2727 // return false; 2728 // } 2729 _this._show($elem); 2730 2731 if(_this.options.closeOnClick){ 2732 var $body = $('body').not(_this.$wrapper); 2733 $body.off('.zf.drilldown').on('click.zf.drilldown', function(e){ 2734 e.preventDefault(); 2735 _this._hideAll(); 2736 $body.off('.zf.drilldown'); 2737 }); 2738 } 2739 }); 2740 }; 2741 /** 2742 * Adds keydown event listener to `li`'s in the menu. 2743 * @private 2744 */ 2745 Drilldown.prototype._keyboardEvents = function() { 2746 var _this = this; 2747 this.$menuItems.add(this.$element.find('.js-drilldown-back')).on('keydown.zf.drilldown', function(e){ 2748 var $element = $(this), 2749 $elements = $element.parent('ul').children('li'), 2750 $prevElement, 2751 $nextElement; 2752 2753 $elements.each(function(i) { 2754 if ($(this).is($element)) { 2755 $prevElement = $elements.eq(Math.max(0, i-1)); 2756 $nextElement = $elements.eq(Math.min(i+1, $elements.length-1)); 2757 return; 2758 } 2759 }); 2760 Foundation.Keyboard.handleKey(e, 'Drilldown', { 2761 next: function() { 2762 if ($element.is(_this.$submenuAnchors)) { 2763 _this._show($element); 2764 $element.on(Foundation.transitionend($element), function(){ 2765 $element.find('ul li').filter(_this.$menuItems).first().focus(); 2766 }); 2767 } 2768 }, 2769 previous: function() { 2770 _this._hide($element.parent('ul')); 2771 $element.parent('ul').on(Foundation.transitionend($element), function(){ 2772 setTimeout(function() { 2773 $element.parent('ul').parent('li').focus(); 2774 }, 1); 2775 }); 2776 }, 2777 up: function() { 2778 $prevElement.focus(); 2779 }, 2780 down: function() { 2781 $nextElement.focus(); 2782 }, 2783 close: function() { 2784 _this._back(); 2785 //_this.$menuItems.first().focus(); // focus to first element 2786 }, 2787 open: function() { 2788 if (!$element.is(_this.$menuItems)) { // not menu item means back button 2789 _this._hide($element.parent('ul')); 2790 setTimeout(function(){$element.parent('ul').parent('li').focus();}, 1); 2791 } else if ($element.is(_this.$submenuAnchors)) { 2792 _this._show($element); 2793 setTimeout(function(){$element.find('ul li').filter(_this.$menuItems).first().focus();}, 1); 2794 } 2795 }, 2796 handled: function() { 2797 e.preventDefault(); 2798 e.stopImmediatePropagation(); 2799 } 2800 }); 2801 }); // end keyboardAccess 2802 }; 2803 2804 /** 2805 * Closes all open elements, and returns to root menu. 2806 * @function 2807 * @fires Drilldown#closed 2808 */ 2809 Drilldown.prototype._hideAll = function(){ 2810 var $elem = this.$element.find('.is-drilldown-submenu.is-active').addClass('is-closing'); 2811 $elem.one(Foundation.transitionend($elem), function(e){ 2812 $elem.removeClass('is-active is-closing'); 2813 }); 2814 /** 2815 * Fires when the menu is fully closed. 2816 * @event Drilldown#closed 2817 */ 2818 this.$element.trigger('closed.zf.drilldown'); 2819 }; 2820 /** 2821 * Adds event listener for each `back` button, and closes open menus. 2822 * @function 2823 * @fires Drilldown#back 2824 * @param {jQuery} $elem - the current sub-menu to add `back` event. 2825 */ 2826 Drilldown.prototype._back = function($elem){ 2827 var _this = this; 2828 $elem.off('click.zf.drilldown'); 2829 $elem.children('.js-drilldown-back') 2830 .on('click.zf.drilldown', function(e){
2831 e.stopImmediatePropagation(); 2832 // console.log('mouseup on back'); 2833 _this._hide($elem); 2834 }); 2835 }; 2836 /** 2837 * Adds event listener to menu items w/o submenus to close open menus on click. 2838 * @function 2839 * @private 2840 */ 2841 Drilldown.prototype._menuLinkEvents = function(){ 2842 var _this = this; 2843 this.$menuItems.not('.is-drilldown-submenu-parent') 2844 .off('click.zf.drilldown') 2845 .on('click.zf.drilldown', function(e){ 2846 // e.stopImmediatePropagation(); 2847 setTimeout(function(){ 2848 _this._hideAll(); 2849 }, 0); 2850 }); 2851 }; 2852 /** 2853 * Opens a submenu. 2854 * @function 2855 * @fires Drilldown#open 2856 * @param {jQuery} $elem - the current element with a submenu to open. 2857 */ 2858 Drilldown.prototype._show = function($elem){ 2859 $elem.children('[data-submenu]').addClass('is-active'); 2860 2861 this.$element.trigger('open.zf.drilldown', [$elem]); 2862 }; 2863 /** 2864 * Hides a submenu 2865 * @function 2866 * @fires Drilldown#hide 2867 * @param {jQuery} $elem - the current sub-menu to hide. 2868 */ 2869 Drilldown.prototype._hide = function($elem){ 2870 var _this = this; 2871 $elem.addClass('is-closing') 2872 .one(Foundation.transitionend($elem), function(){ 2873 $elem.removeClass('is-active is-closing'); 2874 }); 2875 /** 2876 * Fires when the submenu is has closed. 2877 * @event Drilldown#hide 2878 */ 2879 $elem.trigger('hide.zf.drilldown', [$elem]); 2880 2881 }; 2882 /** 2883 * Iterates through the nested menus to calculate the min-height, and max-width for the menu. 2884 * Prevents content jumping. 2885 * @function 2886 * @private 2887 */ 2888 Drilldown.prototype._getMaxDims = function(){ 2889 var max = 0, result = {}; 2890 this.$submenus.add(this.$element).each(function(){ 2891 var numOfElems = $(this).children('li').length; 2892 max = numOfElems > max ? numOfElems : max; 2893 }); 2894 2895 result.height = max * this.$menuItems[0].getBoundingClientRect().height + 'px'; 2896 result.width = this.$element[0].getBoundingClientRect().width + 'px'; 2897 2898 return result; 2899 }; 2900 /** 2901 * Destroys the Drilldown Menu 2902 * @function 2903 */ 2904 Drilldown.prototype.destroy = function(){ 2905 this._hideAll(); 2906 Foundation.Nest.Burn(this.$element, 'drilldown'); 2907 this.$element.unwrap() 2908 .find('.js-drilldown-back').remove() 2909 .end().find('.is-active, .is-closing, .is-drilldown-submenu').removeClass('is-active is-closing is-drilldown-submenu') 2910 .end().find('[data-submenu]').removeAttr('aria-hidden tabindex role') 2911 .off('.zf.drilldown').end().off('zf.drilldown'); 2912 this.$element.find('a').each(function(){ 2913 var $link = $(this); 2914 if($link.data('savedHref')){ 2915 $link.attr('href', $link.data('savedHref')).removeData('savedHref'); 2916 }else{ return; } 2917 }); 2918 Foundation.unregisterPlugin(this); 2919 }; 2920 Foundation.plugin(Drilldown, 'Drilldown'); 2921}(jQuery, window.Foundation); 2922 2923/** 2924 * Dropdown module. 2925 * @module foundation.dropdown 2926 * @requires foundation.util.keyboard 2927 * @requires foundation.util.box 2928 * @requires foundation.util.triggers 2929 */ 2930!function($, Foundation){ 2931 'use strict'; 2932 /** 2933 * Creates a new instance of a dropdown. 2934 * @class 2935 * @param {jQuery} element - jQuery object to make into a dropdown. 2936 * Object should be of the dropdown panel, rather than its anchor. 2937 * @param {Object} options - Overrides to the default plugin settings. 2938 */ 2939 function Dropdown(element, options){ 2940 this.$element = element; 2941 this.options = $.extend({}, Dropdown.defaults, this.$element.data(), options); 2942 this._init(); 2943 2944 Foundation.registerPlugin(this, 'Dropdown'); 2945 Foundation.Keyboard.register('Dropdown', { 2946 'ENTER': 'open', 2947 'SPACE': 'open', 2948 'ESCAPE': 'close', 2949 'TAB': 'tab_forward', 2950 'SHIFT_TAB': 'tab_backward' 2951 }); 2952 } 2953 2954 Dropdown.defaults = { 2955 /** 2956 * Amount of time to delay opening a submenu on hover event. 2957 * @option 2958 * @example 250 2959 */ 2960 hoverDelay: 250, 2961 /** 2962 * Allow submenus to open on hover events 2963 * @option 2964 * @example false 2965 */ 2966 hover: false, 2967 /** 2968 * Don't close dropdown when hovering over dropdown pane 2969 * @option 2970 * @example true 2971 */ 2972 hoverPane: false, 2973 /** 2974 * Number of pixels between the dropdown pane and the triggering element on open. 2975 * @option 2976 * @example 1 2977 */ 2978 vOffset: 1, 2979 /** 2980 * Number of pixels between the dropdown pane and the triggering element on open. 2981 * @option 2982 * @example 1 2983 */ 2984 hOffset: 1, 2985 /** 2986 * Class applied to adjust open position. JS will test and fill this in. 2987 * @option 2988 * @example 'top' 2989 */ 2990 positionClass: '', 2991 /** 2992 * Allow the plugin to trap focus to the dropdown pane if opened with keyboard commands. 2993 * @option 2994 * @example false 2995 */ 2996 trapFocus: false, 2997 /** 2998 * Allow the plugin to set focus to the first focusable element within the pane, regardless of method of opening. 2999 * @option 3000 * @example true 3001 */ 3002 autoFocus: false, 3003 /** 3004 * Allows a click on the body to close the dropdown. 3005 * @option 3006 * @example false 3007 */ 3008 closeOnClick: false 3009 }; 3010 /** 3011 * Initializes the plugin by setting/checking options and attributes, adding helper variables, and saving the anch
3011or. 3012 * @function 3013 * @private 3014 */ 3015 Dropdown.prototype._init = function(){ 3016 var $id = this.$element.attr('id'); 3017 3018 this.$anchor = $('[data-toggle="' + $id + '"]') || $('[data-open="' + $id + '"]'); 3019 this.$anchor.attr({ 3020 'aria-controls': $id, 3021 'data-is-focus': false, 3022 'data-yeti-box': $id, 3023 'aria-haspopup': true, 3024 'aria-expanded': false 3025 // 'data-resize': $id 3026 }); 3027 3028 this.options.positionClass = this.getPositionClass(); 3029 this.counter = 4; 3030 this.usedPositions = []; 3031 this.$element.attr({ 3032 'aria-hidden': 'true', 3033 'data-yeti-box': $id, 3034 'data-resize': $id, 3035 'aria-labelledby': this.$anchor[0].id || Foundation.GetYoDigits(6, 'dd-anchor') 3036 }); 3037 this._events(); 3038 }; 3039 /** 3040 * Helper function to determine current orientation of dropdown pane. 3041 * @function 3042 * @returns {String} position - string value of a position class. 3043 */ 3044 Dropdown.prototype.getPositionClass = function(){ 3045 var position = this.$element[0].className.match(/\b(top|left|right)\b/g); 3046 position = position ? position[0] : ''; 3047 return position; 3048 }; 3049 /** 3050 * Adjusts the dropdown panes orientation by adding/removing positioning classes. 3051 * @function 3052 * @private 3053 * @param {String} position - position class to remove. 3054 */ 3055 Dropdown.prototype._reposition = function(position){ 3056 this.usedPositions.push(position ? position : 'bottom'); 3057 //default, try switching to opposite side 3058 if(!position && (this.usedPositions.indexOf('top') < 0)){ 3059 this.$element.addClass('top'); 3060 }else if(position === 'top' && (this.usedPositions.indexOf('bottom') < 0)){ 3061 this.$element.removeClass(position); 3062 }else if(position === 'left' && (this.usedPositions.indexOf('right') < 0)){ 3063 this.$element.removeClass(position) 3064 .addClass('right'); 3065 }else if(position === 'right' && (this.usedPositions.indexOf('left') < 0)){ 3066 this.$element.removeClass(position) 3067 .addClass('left'); 3068 } 3069 3070 //if default change didn't work, try bottom or left first 3071 else if(!position && (this.usedPositions.indexOf('top') > -1) && (this.usedPositions.indexOf('left') < 0)){ 3072 this.$element.addClass('left'); 3073 }else if(position === 'top' && (this.usedPositions.indexOf('bottom') > -1) && (this.usedPositions.indexOf('left') < 0)){ 3074 this.$element.removeClass(position) 3075 .addClass('left'); 3076 }else if(position === 'left' && (this.usedPositions.indexOf('right') > -1) && (this.usedPositions.indexOf('bottom') < 0)){ 3077 this.$element.removeClass(position); 3078 }else if(position === 'right' && (this.usedPositions.indexOf('left') > -1) && (this.usedPositions.indexOf('bottom') < 0)){ 3079 this.$element.removeClass(position); 3080 } 3081 //if nothing cleared, set to bottom 3082 else{ 3083 this.$element.removeClass(position); 3084 } 3085 this.classChanged = true; 3086 this.counter--; 3087 }; 3088 /** 3089 * Sets the position and orientation of the dropdown pane, checks for collisions. 3090 * Recursively calls itself if a collision is detected, with a new position class. 3091 * @function 3092 * @private 3093 */ 3094 Dropdown.prototype._setPosition = function(){ 3095 if(this.$anchor.attr('aria-expanded') === 'false'){ return false; } 3096 var position = this.getPositionClass(), 3097 $eleDims = Foundation.Box.GetDimensions(this.$element), 3098 $anchorDims = Foundation.Box.GetDimensions(this.$anchor), 3099 _this = this, 3100 direction = (position === 'left' ? 'left' : ((position === 'right') ? 'left' : 'top')), 3101 param = (direction === 'top') ? 'height' : 'width', 3102 offset = (param === 'height') ? this.options.vOffset : this.options.hOffset; 3103 3104 if(($eleDims.width >= $eleDims.windowDims.width) || (!this.counter && !Foundation.Box.ImNotTouchingYou(this.$element))){ 3105 this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({ 3106 'width': $eleDims.windowDims.width - (this.options.hOffset * 2), 3107 'height': 'auto' 3108 }); 3109 this.classChanged = true; 3110 return false; 3111 } 3112 3113 this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, position, this.options.vOffset, this.options.hOffset)); 3114 3115 while(!Foundation.Box.ImNotTouchingYou(this.$element) && this.counter){ 3116 this._reposition(position); 3117 this._setPosition(); 3118 } 3119 }; 3120 /** 3121 * Adds event listeners to the element utilizing the triggers utility library. 3122 * @function 3123 * @private 3124 */ 3125 Dropdown.prototype._events = function(){ 3126 var _this = this; 3127 this.$element.on({ 3128 'open.zf.trigger': this.open.bind(this), 3129 'close.zf.trigger': this.close.bind(this), 3130 'toggle.zf.trigger': this.toggle.bind(this), 3131 'resizeme.zf.trigger': this._setPosition.bind(this) 3132 });
3133 3134 if(this.options.hover){ 3135 this.$anchor.off('mouseenter.zf.dropdown mouseleave.zf.dropdown') 3136 .on('mouseenter.zf.dropdown', function(){ 3137 clearTimeout(_this.timeout); 3138 _this.timeout = setTimeout(function(){ 3139 _this.open(); 3140 _this.$anchor.data('hover', true); 3141 }, _this.options.hoverDelay); 3142 }).on('mouseleave.zf.dropdown', function(){ 3143 clearTimeout(_this.timeout); 3144 _this.timeout = setTimeout(function(){ 3145 _this.close(); 3146 _this.$anchor.data('hover', false); 3147 }, _this.options.hoverDelay); 3148 }); 3149 if(this.options.hoverPane){ 3150 this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown') 3151 .on('mouseenter.zf.dropdown', function(){ 3152 clearTimeout(_this.timeout); 3153 }).on('mouseleave.zf.dropdown', function(){ 3154 clearTimeout(_this.timeout); 3155 _this.timeout = setTimeout(function(){ 3156 _this.close(); 3157 _this.$anchor.data('hover', false); 3158 }, _this.options.hoverDelay); 3159 }); 3160 } 3161 } 3162 this.$anchor.add(this.$element).on('keydown.zf.dropdown', function(e) { 3163 3164 var $target = $(this), 3165 visibleFocusableElements = Foundation.Keyboard.findFocusable(_this.$element); 3166 3167 Foundation.Keyboard.handleKey(e, 'Dropdown', { 3168 tab_forward: function() { 3169 if (_this.$element.find(':focus').is(visibleFocusableElements.eq(-1))) { // left modal downwards, setting focus to first element 3170 if (_this.options.trapFocus) { // if focus shall be trapped 3171 visibleFocusableElements.eq(0).focus(); 3172 e.preventDefault(); 3173 } else { // if focus is not trapped, close dropdown on focus out 3174 _this.close(); 3175 } 3176 } 3177 }, 3178 tab_backward: function() { 3179 if (_this.$element.find(':focus').is(visibleFocusableElements.eq(0)) || _this.$element.is(':focus')) { // left modal upwards, setting focus to last element 3180 if (_this.options.trapFocus) { // if focus shall be trapped 3181 visibleFocusableElements.eq(-1).focus(); 3182 e.preventDefault(); 3183 } else { // if focus is not trapped, close dropdown on focus out 3184 _this.close(); 3185 } 3186 } 3187 }, 3188 open: function() { 3189 if ($target.is(_this.$anchor)) { 3190 _this.open(); 3191 _this.$element.attr('tabindex', -1).focus(); 3192 e.preventDefault(); 3193 } 3194 }, 3195 close: function() { 3196 _this.close(); 3197 _this.$anchor.focus(); 3198 } 3199 }); 3200 }); 3201 }; 3202 /** 3203 * Adds an event handler to the body to close any dropdowns on a click. 3204 * @function 3205 * @private 3206 */ 3207 Dropdown.prototype._addBodyHandler = function(){ 3208 var $body = $(document.body).not(this.$element), 3209 _this = this; 3210 $body.off('click.zf.dropdown') 3211 .on('click.zf.dropdown', function(e){ 3212 if(_this.$anchor.is(e.target) || _this.$anchor.find(e.target).length) { 3213 return; 3214 } 3215 if(_this.$element.find(e.target).length) { 3216 return; 3217 } 3218 _this.close(); 3219 $body.off('click.zf.dropdown'); 3220 }); 3221 }; 3222 /** 3223 * Opens the dropdown pane, and fires a bubbling event to close other dropdowns. 3224 * @function 3225 * @fires Dropdown#closeme 3226 * @fires Dropdown#show 3227 */ 3228 Dropdown.prototype.open = function(){ 3229 // var _this = this; 3230 /** 3231 * Fires to close other open dropdowns 3232 * @event Dropdown#closeme 3233 */ 3234 this.$element.trigger('closeme.zf.dropdown', this.$element.attr('id')); 3235 this.$anchor.addClass('hover') 3236 .attr({'aria-expanded': true}); 3237 // this.$element/*.show()*/; 3238 this._setPosition(); 3239 this.$element.addClass('is-open') 3240 .attr({'aria-hidden': false}); 3241 3242 if(this.options.autoFocus){
3243 var $focusable = Foundation.Keyboard.findFocusable(this.$element); 3244 if($focusable.length){ 3245 $focusable.eq(0).focus(); 3246 } 3247 } 3248 3249 if(this.options.closeOnClick){ this._addBodyHandler(); } 3250 3251 /** 3252 * Fires once the dropdown is visible. 3253 * @event Dropdown#show 3254 */ 3255 this.$element.trigger('show.zf.dropdown', [this.$element]); 3256 //why does this not work correctly for this plugin? 3257 // Foundation.reflow(this.$element, 'dropdown'); 3258 // Foundation._reflow(this.$element.attr('data-dropdown')); 3259 }; 3260 3261 /** 3262 * Closes the open dropdown pane. 3263 * @function 3264 * @fires Dropdown#hide 3265 */ 3266 Dropdown.prototype.close = function(){ 3267 if(!this.$element.hasClass('is-open')){ 3268 return false; 3269 } 3270 this.$element.removeClass('is-open') 3271 .attr({'aria-hidden': true}); 3272 3273 this.$anchor.removeClass('hover') 3274 .attr('aria-expanded', false); 3275 3276 if(this.classChanged){ 3277 var curPositionClass = this.getPositionClass(); 3278 if(curPositionClass){ 3279 this.$element.removeClass(curPositionClass); 3280 } 3281 this.$element.addClass(this.options.positionClass) 3282 /*.hide()*/.css({height: '', width: ''}); 3283 this.classChanged = false; 3284 this.counter = 4; 3285 this.usedPositions.length = 0; 3286 } 3287 this.$element.trigger('hide.zf.dropdown', [this.$element]); 3288 // Foundation.reflow(this.$element, 'dropdown'); 3289 }; 3290 /** 3291 * Toggles the dropdown pane's visibility. 3292 * @function 3293 */ 3294 Dropdown.prototype.toggle = function(){ 3295 if(this.$element.hasClass('is-open')){ 3296 if(this.$anchor.data('hover')) return; 3297 this.close(); 3298 }else{ 3299 this.open(); 3300 } 3301 }; 3302 /** 3303 * Destroys the dropdown. 3304 * @function 3305 */ 3306 Dropdown.prototype.destroy = function(){ 3307 this.$element.off('.zf.trigger').hide(); 3308 this.$anchor.off('.zf.dropdown'); 3309 3310 Foundation.unregisterPlugin(this); 3311 }; 3312 3313 Foundation.plugin(Dropdown, 'Dropdown'); 3314}(jQuery, window.Foundation); 3315 3316/** 3317 * DropdownMenu module. 3318 * @module foundation.dropdown-menu 3319 * @requires foundation.util.keyboard 3320 * @requires foundation.util.box 3321 * @requires foundation.util.nest 3322 */ 3323!function($, Foundation){ 3324 'use strict'; 3325 3326 /** 3327 * Creates a new instance of DropdownMenu. 3328 * @class 3329 * @fires DropdownMenu#init 3330 * @param {jQuery} element - jQuery object to make into a dropdown menu. 3331 * @param {Object} options - Overrides to the default plugin settings. 3332 */ 3333 function DropdownMenu(element, options){ 3334 this.$element = element; 3335 this.options = $.extend({}, DropdownMenu.defaults, this.$element.data(), options); 3336 3337 Foundation.Nest.Feather(this.$element, 'dropdown'); 3338 this._init(); 3339 3340 Foundation.registerPlugin(this, 'DropdownMenu'); 3341 Foundation.Keyboard.register('DropdownMenu', { 3342 'ENTER': 'open', 3343 'SPACE': 'open', 3344 'ARROW_RIGHT': 'next', 3345 'ARROW_UP': 'up', 3346 'ARROW_DOWN': 'down', 3347 'ARROW_LEFT': 'previous', 3348 'ESCAPE': 'close' 3349 }); 3350 } 3351 3352 /** 3353 * Default settings for plugin 3354 */ 3355 DropdownMenu.defaults = { 3356 /** 3357 * Disallows hover events from opening submenus 3358 * @option 3359 * @example false 3360 */ 3361 disableHover: false, 3362 /** 3363 * Allow a submenu to automatically close on a mouseleave event, if not clicked open. 3364 * @option 3365 * @example true 3366 */ 3367 autoclose: true, 3368 /** 3369 * Amount of time to delay opening a submenu on hover event. 3370 * @option 3371 * @example 50 3372 */ 3373 hoverDelay: 50, 3374 /** 3375 * Allow a submenu to open/remain open on parent click event. Allows cursor to move away from menu. 3376 * @option 3377 * @example true 3378 */ 3379 clickOpen: false, 3380 /** 3381 * Amount of time to delay closing a submenu on a mouseleave event. 3382 * @option 3383 * @example 500 3384 */ 3385 3386 closingTime: 500, 3387 /** 3388 * Position of the menu relative to what direction the submenus should open. Handled by JS. 3389 * @option 3390 * @example 'left' 3391 */ 3392 alignment: 'left', 3393 /** 3394 * Allow clicks on the body to close any open submenus. 3395 * @option 3396 * @example true 3397 */ 3398 closeOnClick: true, 3399 /** 3400 * Class applied to vertical oriented menus, Foundation default is `vertical`. Update this if using your own class. 3401 * @option 3402 * @example 'vertical' 3403 */ 3404 verticalClass: 'vertical', 3405 /** 3406 * Class applied to right-side oriented menus, Foundation default is `align-right`. Update this if using your own class. 3407 * @option 3408 * @example 'align-right' 3409 */ 3410 rightClass: 'align-right', 3411 /** 3412 * Boolean to force overide the clicking of links to perform default action, on second touch event for mobile. 3413 * @option 3414 * @example false 3415 */ 3416 forceFollow: true 3417 }; 3418 /** 3419 * Initializes the plugin, and calls _prepareMenu 3420 * @private 3421 * @function 3422 */ 3423 DropdownMenu.prototype._init = function(){ 3424 var subs = this.$element.find('li.is-dropdown-submenu-parent'); 3425 this.$element.children('.is-dropdown-submenu-parent').children('.is-dropdown-submenu').addClass('first-sub'); 3426 3427 this.$menuItems = this.$element.find('[role="menuitem"]'); 3428 this.$tabs = this.$element.children('[role="menuitem"]'); 3429 this.isVert = this.$element.hasClass(this.options.verticalClass); 3430 this.$tabs.find('ul.is-dropdown-submenu').addClass(this.options.verticalClass); 3431 3432 if(this.$element.hasClass(this.options.rightClass) || this.options.alignment === 'right' || Foundation.rtl()){ 3433 this.options.alignment = 'right'; 3434 subs.addClass('is-left-arrow opens-left'); 3435 }else{ 3436 subs.addClass('is-right-arrow opens-right'); 3437 } 3438 if(!this.isVert){ 3439 this.$tabs.filter('.is-dropdown-submenu-parent').removeClass('is-right-arrow is-left-arrow opens-right opens-left') 3440 .addClass('is-down-arrow'); 3441 } 3442 this.changed = false; 3443 this._events(); 3444 }; 3445 /** 3446 * Adds event listeners to elements within the menu 3447 * @private 3448 * @function 3449 */ 3450 DropdownMenu.prototype._events = function(){ 3451 var _this = this, 3452 hasTouch = 'ontouchstart' in window || (typeof window.ontouchstart !== 'undefined'), 3453 parClass = 'is-dropdown-submenu-parent'; 3454 3455 if(this.options.clickOpen || hasTouch){ 3456 this.$menuItems.on('click.zf.dropdownmenu touchstart.zf.dropdownmenu', function(e){
3457 var $elem = $(e.target).parentsUntil('ul', '.' + parClass), 3458 hasSub = $elem.hasClass(parClass), 3459 hasClicked = $elem.attr('data-is-click') === 'true', 3460 $sub = $elem.children('.is-dropdown-submenu'); 3461 3462 if(hasSub){ 3463 if(hasClicked){ 3464 if(!_this.options.closeOnClick || (!_this.options.clickOpen && !hasTouch) || (_this.options.forceFollow && hasTouch)){ return; } 3465 else{ 3466 e.stopImmediatePropagation(); 3467 e.preventDefault(); 3468 _this._hide($elem); 3469 } 3470 }else{ 3471 e.preventDefault(); 3472 e.stopImmediatePropagation(); 3473 _this._show($elem.children('.is-dropdown-submenu')); 3474 $elem.add($elem.parentsUntil(_this.$element, '.' + parClass)).attr('data-is-click', true); 3475 } 3476 }else{ return; } 3477 }); 3478 } 3479 3480 if(!this.options.disableHover){ 3481 this.$menuItems.on('mouseenter.zf.dropdownmenu', function(e){ 3482 e.stopImmediatePropagation(); 3483 var $elem = $(this), 3484 hasSub = $elem.hasClass(parClass); 3485 3486 if(hasSub){ 3487 clearTimeout(_this.delay); 3488 _this.delay = setTimeout(function(){ 3489 _this._show($elem.children('.is-dropdown-submenu')); 3490 }, _this.options.hoverDelay); 3491 } 3492 }).on('mouseleave.zf.dropdownmenu', function(e){ 3493 var $elem = $(this), 3494 hasSub = $elem.hasClass(parClass); 3495 if(hasSub && _this.options.autoclose){ 3496 if($elem.attr('data-is-click') === 'true' && _this.options.clickOpen){ return false; } 3497 3498 clearTimeout(_this.delay); 3499 _this.delay = setTimeout(function(){ 3500 _this._hide($elem); 3501 }, _this.options.closingTime); 3502 } 3503 }); 3504 } 3505 this.$menuItems.on('keydown.zf.dropdownmenu', function(e){ 3506 var $element = $(e.target).parentsUntil('ul', '[role="menuitem"]'), 3507 isTab = _this.$tabs.index($element) > -1, 3508 $elements = isTab ? _this.$tabs : $element.siblings('li').add($element), 3509 $prevElement, 3510 $nextElement; 3511 3512 $elements.each(function(i) { 3513 if ($(this).is($element)) { 3514 $prevElement = $elements.eq(i-1); 3515 $nextElement = $elements.eq(i+1); 3516 return; 3517 } 3518 }); 3519 3520 var nextSibling = function() { 3521 if (!$element.is(':last-child')) $nextElement.children('a:first').focus(); 3522 }, prevSibling = function() { 3523 $prevElement.children('a:first').focus(); 3524 }, openSub = function() { 3525 var $sub = $element.children('ul.is-dropdown-submenu'); 3526 if($sub.length){ 3527 _this._show($sub); 3528 $element.find('li > a:first').focus(); 3529 }else{ return; } 3530 }, closeSub = function() { 3531 //if ($element.is(':first-child')) { 3532 var close = $element.parent('ul').parent('li'); 3533 close.children('a:first').focus(); 3534 _this._hide(close); 3535 //} 3536 }; 3537 var functions = { 3538 open: openSub, 3539 close: function() { 3540 _this._hide(_this.$element); 3541 _this.$menuItems.find('a:first').focus(); // focus to first element 3542 }, 3543 handled: function() { 3544 e.preventDefault(); 3545 e.stopImmediatePropagation(); 3546 } 3547 }; 3548 3549 if (isTab) { 3550 if (_this.vertical) { // vertical menu 3551 if (_this.options.alignment === 'left') { // left aligned 3552 $.extend(functions, { 3553 down: nextSibling, 3554 up: prevSibling, 3555 next: openSub, 3556 previous: closeSub 3557 }); 3558 } else { // right aligned 3559 $.extend(functions, { 3560 down: nextSibling, 3561 up: prevSibling, 3562 next: closeSub, 3563 previous: openSub 3564 }); 3565 } 3566 } else { // horizontal menu 3567 $.extend(functions, { 3568 next: nextSibling, 3569 previous: prevSibling, 3570 down: openSub, 3571 up: closeSub 3572 }); 3573 } 3574 } else { // not tabs -> one sub 3575 if (_this.options.alignment === 'left') { // left aligned 3576 $.extend(functions, { 3577 next: openSub, 3578 previous: closeSub, 3579 down: nextSibling, 3580 up: prevSibling 3581 }); 3582 } else { // right aligned 3583 $.extend(functions, { 3584 next: closeSub, 3585 previous: openSub, 3586 down: nextSibling, 3587 up: prevSibling 3588 }); 3589 } 3590 } 3591 Foundation.Keyboard.handleKey(e, 'DropdownMenu', functions); 3592 3593 });
3594 }; 3595 /** 3596 * Adds an event handler to the body to close any dropdowns on a click. 3597 * @function 3598 * @private 3599 */ 3600 DropdownMenu.prototype._addBodyHandler = function(){ 3601 var $body = $(document.body), 3602 _this = this; 3603 $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu') 3604 .on('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu', function(e){ 3605 var $link = _this.$element.find(e.target); 3606 if($link.length){ return; } 3607 3608 _this._hide(); 3609 $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu'); 3610 }); 3611 }; 3612 /** 3613 * Opens a dropdown pane, and checks for collisions first. 3614 * @param {jQuery} $sub - ul element that is a submenu to show 3615 * @function 3616 * @private 3617 * @fires DropdownMenu#show 3618 */ 3619 DropdownMenu.prototype._show = function($sub){ 3620 var idx = this.$tabs.index(this.$tabs.filter(function(i, el){ 3621 return $(el).find($sub).length > 0; 3622 })); 3623 var $sibs = $sub.parent('li.is-dropdown-submenu-parent').siblings('li.is-dropdown-submenu-parent'); 3624 this._hide($sibs, idx); 3625 $sub.css('visibility', 'hidden').addClass('js-dropdown-active').attr({'aria-hidden': false}) 3626 .parent('li.is-dropdown-submenu-parent').addClass('is-active') 3627 .attr({'aria-expanded': true}); 3628 var clear = Foundation.Box.ImNotTouchingYou($sub, null, true); 3629 if(!clear){ 3630 var oldClass = this.options.alignment === 'left' ? '-right' : '-left', 3631 $parentLi = $sub.parent('.is-dropdown-submenu-parent'); 3632 $parentLi.removeClass('opens' + oldClass).addClass('opens-' + this.options.alignment); 3633 clear = Foundation.Box.ImNotTouchingYou($sub, null, true); 3634 if(!clear){ 3635 $parentLi.removeClass('opens-' + this.options.alignment).addClass('opens-inner'); 3636 } 3637 this.changed = true; 3638 } 3639 $sub.css('visibility', ''); 3640 if(this.options.closeOnClick){ this._addBodyHandler(); } 3641 /** 3642 * Fires when the new dropdown pane is visible. 3643 * @event DropdownMenu#show 3644 */ 3645 this.$element.trigger('show.zf.dropdownmenu', [$sub]); 3646 }; 3647 /** 3648 * Hides a single, currently open dropdown pane, if passed a parameter, otherwise, hides everything. 3649 * @function 3650 * @param {jQuery} $elem - element with a submenu to hide 3651 * @param {Number} idx - index of the $tabs collection to hide 3652 * @private 3653 */ 3654 DropdownMenu.prototype._hide = function($elem, idx){ 3655 var $toClose; 3656 if($elem && $elem.length){ 3657 $toClose = $elem; 3658 }else if(idx !== undefined){ 3659 $toClose = this.$tabs.not(function(i, el){ 3660 return i === idx; 3661 }); 3662 } 3663 else{ 3664 $toClose = this.$element; 3665 } 3666 var somethingToClose = $toClose.hasClass('is-active') || $toClose.find('.is-active').length > 0; 3667 3668 if(somethingToClose){ 3669 $toClose.find('li.is-active').add($toClose).attr({ 3670 'aria-expanded': false, 3671 'data-is-click': false 3672 }).removeClass('is-active'); 3673 3674 $toClose.find('ul.js-dropdown-active').attr({ 3675 'aria-hidden': true 3676 }).removeClass('js-dropdown-active'); 3677 3678 if(this.changed || $toClose.find('opens-inner').length){ 3679 var oldClass = this.options.alignment === 'left' ? 'right' : 'left'; 3680 $toClose.find('li.is-dropdown-submenu-parent').add($toClose) 3681 .removeClass('opens-inner opens-' + this.options.alignment) 3682 .addClass('opens-' + oldClass); 3683 this.changed = false; 3684 } 3685 /** 3686 * Fires when the open menus are closed. 3687 * @event DropdownMenu#hide 3688 */ 3689 this.$element.trigger('hide.zf.dropdownmenu', [$toClose]); 3690 } 3691 }; 3692 /** 3693 * Destroys the plugin. 3694 * @function 3695 */ 3696 DropdownMenu.prototype.destroy = function(){ 3697 this.$menuItems.off('.zf.dropdownmenu').removeAttr('data-is-click') 3698 .removeClass('is-right-arrow is-left-arrow is-down-arrow opens-right opens-left opens-inner'); 3699 $(document.body).off('.zf.dropdownmenu'); 3700 Foundation.Nest.Burn(this.$element, 'dropdown'); 3701 Foundation.unregisterPlugin(this); 3702 }; 3703 3704 Foundation.plugin(DropdownMenu, 'DropdownMenu'); 3705}(jQuery, window.Foundation); 3706 3707!function(Foundation, $) { 3708 'use strict'; 3709 3710 /** 3711 * Creates a new instance of Equalizer. 3712 * @class 3713 * @fires Equalizer#init 3714 * @param {Object} element - jQuery object to add the trigger to. 3715 * @param {Object} options - Overrides to the default plugin settings. 3716 */ 3717 function Equalizer(element, options){ 3718 this.$element = element; 3719 this.options = $.extend({}, Equalizer.defaults, this.$element.data(), options); 3720 3721 this._init(); 3722 3723 Foundation.registerPlugin(this, 'Equalizer'); 3724 } 3725 3726 /** 3727 * Default settings for plugin 3728 */ 3729 Equalizer.defaults = { 3730 /** 3731 * Enable height equalization when stacked on smaller screens. 3732 * @option 3733 * @example true 3734 */ 3735 equalizeOnStack: true, 3736 /** 3737 * Enable height equalization row by row. 3738 * @option 3739 * @example false 3740 */ 3741 equalizeByRow: false, 3742 /** 3743 * String representing the minimum breakpoint size the plugin should equalize heights on. 3744 * @option 3745 * @example 'medium' 3746 */ 3747 equalizeOn: '' 3748 }; 3749 3750 /** 3751 * Initializes the Equalizer plugin and calls functions to get equalizer functioning on load. 3752 * @private 3753 */ 3754 Equalizer.prototype._init = function(){ 3755 var eqId = this.$element.attr('data-equalizer') || ''; 3756 var $watched = this.$element.find('[data-equalizer-watch="' + eqId + '"]'); 3757 3758 this.$watched = $watched.length ? $watched : this.$element.find('[data-equalizer-watch]'); 3759 this.$element.attr('data-resize', (eqId || Foundation.GetYoDigits(6, 'eq'))); 3760 3761 this.hasNested = this.$element.find('[data-equalizer]').length > 0;
3762 this.isNested = this.$element.parentsUntil(document.body, '[data-equalizer]').length > 0; 3763 this.isOn = false; 3764 3765 var imgs = this.$element.find('img'); 3766 var tooSmall; 3767 if(this.options.equalizeOn){ 3768 tooSmall = this._checkMQ(); 3769 $(window).on('changed.zf.mediaquery', this._checkMQ.bind(this)); 3770 }else{ 3771 this._events(); 3772 } 3773 if((tooSmall !== undefined && tooSmall === false) || tooSmall === undefined){ 3774 if(imgs.length){ 3775 Foundation.onImagesLoaded(imgs, this._reflow.bind(this)); 3776 }else{ 3777 this._reflow(); 3778 } 3779 } 3780 3781 }; 3782 /** 3783 * Removes event listeners if the breakpoint is too small. 3784 * @private 3785 */ 3786 Equalizer.prototype._pauseEvents = function(){ 3787 this.isOn = false; 3788 this.$element.off('.zf.equalizer resizeme.zf.trigger'); 3789 }; 3790 /** 3791 * Initializes events for Equalizer. 3792 * @private 3793 */ 3794 Equalizer.prototype._events = function(){ 3795 var _this = this; 3796 this._pauseEvents(); 3797 if(this.hasNested){ 3798 this.$element.on('postequalized.zf.equalizer', function(e){ 3799 if(e.target !== _this.$element[0]){ _this._reflow(); } 3800 }); 3801 }else{ 3802 this.$element.on('resizeme.zf.trigger', this._reflow.bind(this)); 3803 } 3804 this.isOn = true; 3805 }; 3806 /** 3807 * Checks the current breakpoint to the minimum required size. 3808 * @private 3809 */ 3810 Equalizer.prototype._checkMQ = function(){ 3811 var tooSmall = !Foundation.MediaQuery.atLeast(this.options.equalizeOn); 3812 if(tooSmall){ 3813 if(this.isOn){ 3814 this._pauseEvents(); 3815 this.$watched.css('height', 'auto'); 3816 } 3817 }else{ 3818 if(!this.isOn){ 3819 this._events(); 3820 } 3821 } 3822 return tooSmall; 3823 }; 3824 /** 3825 * A noop version for the plugin 3826 * @private 3827 */ 3828 Equalizer.prototype._killswitch = function(){ 3829 return; 3830 }; 3831 /** 3832 * Calls necessary functions to update Equalizer upon DOM change 3833 * @private 3834 */ 3835 Equalizer.prototype._reflow = function(){ 3836 if(!this.options.equalizeOnStack){ 3837 if(this._isStacked()){ 3838 this.$watched.css('height', 'auto'); 3839 return false; 3840 } 3841 } 3842 if (this.options.equalizeByRow) { 3843 this.getHeightsByRow(this.applyHeightByRow.bind(this)); 3844 }else{ 3845 this.getHeights(this.applyHeight.bind(this)); 3846 } 3847 }; 3848 /** 3849 * Manually determines if the first 2 elements are *NOT* stacked. 3850 * @private 3851 */ 3852 Equalizer.prototype._isStacked = function(){ 3853 return this.$watched[0].offsetTop !== this.$watched[1].offsetTop; 3854 }; 3855 /** 3856 * Finds the outer heights of children contained within an Equalizer parent and returns them in an array 3857 * @param {Function} cb - A non-optional callback to return the heights array to. 3858 * @returns {Array} heights - An array of heights of children within Equalizer container 3859 */ 3860 Equalizer.prototype.getHeights = function(cb){ 3861 var heights = []; 3862 for(var i = 0, len = this.$watched.length; i < len; i++){ 3863 this.$watched[i].style.height = 'auto'; 3864 heights.push(this.$watched[i].offsetHeight); 3865 } 3866 cb(heights); 3867 }; 3868 /** 3869 * Finds the outer heights of children contained within an Equalizer parent and returns them in an array 3870 * @param {Function} cb - A non-optional callback to return the heights array to. 3871 * @returns {Array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child 3872 */ 3873 Equalizer.prototype.getHeightsByRow = function(cb) { 3874 var lastElTopOffset = this.$watched.first().offset().top, 3875 groups = [], 3876 group = 0; 3877 //group by Row 3878 groups[group] = []; 3879 for(var i = 0, len = this.$watched.length; i < len; i++){ 3880 this.$watched[i].style.height = 'auto'; 3881 //maybe could use this.$watched[i].offsetTop 3882 var elOffsetTop = $(this.$watched[i]).offset().top; 3883 if (elOffsetTop!=lastElTopOffset) { 3884 group++; 3885 groups[group] = []; 3886 lastElTopOffset=elOffsetTop; 3887 } 3888 groups[group].push([this.$watched[i],this.$watched[i].offsetHeight]); 3889 } 3890 3891 for (var j = 0, ln = groups.length; j < ln; j++) { 3892 var heights = $(groups[j]).map(function(){ return this[1]; }).get(); 3893 var max = Math.max.apply(null, heights); 3894 groups[j].push(max); 3895 } 3896 cb(groups); 3897 }; 3898 /** 3899 * Changes the CSS height property of each child in an Equalizer parent to match the tallest 3900 * @param {array} heights - An array of heights of children within Equalizer container 3901 * @fires Equalizer#preequalized 3902 * @fires Equalizer#postequalized 3903 */ 3904 Equalizer.prototype.applyHeight = function(heights){ 3905 var max = Math.max.apply(null, heights); 3906 /** 3907 * Fires before the heights are applied 3908 * @event Equalizer#preequalized 3909 */
3910 this.$element.trigger('preequalized.zf.equalizer'); 3911 3912 this.$watched.css('height', max); 3913 3914 /** 3915 * Fires when the heights have been applied 3916 * @event Equalizer#postequalized 3917 */ 3918 this.$element.trigger('postequalized.zf.equalizer'); 3919 }; 3920 /** 3921 * Changes the CSS height property of each child in an Equalizer parent to match the tallest by row 3922 * @param {array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child 3923 * @fires Equalizer#preequalized 3924 * @fires Equalizer#preequalizedRow 3925 * @fires Equalizer#postequalizedRow 3926 * @fires Equalizer#postequalized 3927 */ 3928 Equalizer.prototype.applyHeightByRow = function(groups){ 3929 /** 3930 * Fires before the heights are applied 3931 */ 3932 this.$element.trigger('preequalized.zf.equalizer'); 3933 for (var i = 0, len = groups.length; i < len ; i++) { 3934 var groupsILength = groups[i].length, 3935 max = groups[i][groupsILength - 1]; 3936 if (groupsILength<=2) { 3937 $(groups[i][0][0]).css({'height':'auto'}); 3938 continue; 3939 } 3940 /** 3941 * Fires before the heights per row are applied 3942 * @event Equalizer#preequalizedRow 3943 */ 3944 this.$element.trigger('preequalizedrow.zf.equalizer'); 3945 for (var j = 0, lenJ = (groupsILength-1); j < lenJ ; j++) { 3946 $(groups[i][j][0]).css({'height':max}); 3947 } 3948 /** 3949 * Fires when the heights per row have been applied 3950 * @event Equalizer#postequalizedRow 3951 */ 3952 this.$element.trigger('postequalizedrow.zf.equalizer'); 3953 } 3954 /** 3955 * Fires when the heights have been applied 3956 */ 3957 this.$element.trigger('postequalized.zf.equalizer'); 3958 }; 3959 /** 3960 * Destroys an instance of Equalizer. 3961 * @function 3962 */ 3963 Equalizer.prototype.destroy = function(){ 3964 this._pauseEvents(); 3965 this.$watched.css('height', 'auto'); 3966 3967 Foundation.unregisterPlugin(this); 3968 }; 3969 3970 Foundation.plugin(Equalizer, 'Equalizer'); 3971 3972 // Exports for AMD/Browserify 3973 if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') 3974 module.exports = Equalizer; 3975 if (typeof define === 'function') 3976 define(['foundation'], function() { 3977 return Equalizer; 3978 }); 3979 3980}(Foundation, jQuery); 3981 3982/** 3983 * Interchange module. 3984 * @module foundation.interchange 3985 * @requires foundation.util.mediaQuery 3986 * @requires foundation.util.timerAndImageLoader 3987 */ 3988!function(Foundation, $) { 3989 'use strict'; 3990 3991 /** 3992 * Creates a new instance of Interchange. 3993 * @class 3994 * @fires Interchange#init 3995 * @param {Object} element - jQuery object to add the trigger to. 3996 * @param {Object} options - Overrides to the default plugin settings. 3997 */ 3998 function Interchange(element, options) { 3999 this.$element = element; 4000 this.options = $.extend({}, Interchange.defaults, options); 4001 this.rules = []; 4002 this.currentPath = ''; 4003 4004 this._init(); 4005 this._events(); 4006 4007 Foundation.registerPlugin(this, 'Interchange'); 4008 } 4009 4010 /** 4011 * Default settings for plugin 4012 */ 4013 Interchange.defaults = { 4014 /** 4015 * Rules to be applied to Interchange elements. Set with the `data-interchange` array notation. 4016 * @option 4017 */ 4018 rules: null 4019 }; 4020 4021 Interchange.SPECIAL_QUERIES = { 4022 'landscape': 'screen and (orientation: landscape)', 4023 'portrait': 'screen and (orientation: portrait)', 4024 'retina': 'only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx)' 4025 }; 4026 4027 /** 4028 * Initializes the Interchange plugin and calls functions to get interchange functioning on load. 4029 * @function 4030 * @private 4031 */ 4032 Interchange.prototype._init = function() { 4033 this._addBreakpoints(); 4034 this._generateRules(); 4035 this._reflow(); 4036 }; 4037 4038 /** 4039 * Initializes events for Interchange. 4040 * @function 4041 * @private 4042 */ 4043 Interchange.prototype._events = function() { 4044 $(window).on('resize.zf.interchange', Foundation.util.throttle(this._reflow.bind(this), 50)); 4045 }; 4046 4047 /** 4048 * Calls necessary functions to update Interchange upon DOM change 4049 * @function 4050 * @private 4051 */ 4052 Interchange.prototype._reflow = function() { 4053 var match; 4054 4055 // Iterate through each rule, but only save the last match 4056 for (var i in this.rules) { 4057 var rule = this.rules[i]; 4058 4059 if (window.matchMedia(rule.query).matches) { 4060 match = rule; 4061 } 4062 } 4063 4064 if (match) { 4065 this.replace(match.path); 4066 } 4067 }; 4068 4069 /** 4070 * Gets the Foundation breakpoints and adds them to the Interchange.SPECIAL_QUERIES object. 4071 * @function 4072 * @private 4073 */ 4074 Interchange.prototype._addBreakpoints = function() { 4075 for (var i in Foundation.MediaQuery.queries) { 4076 var query = Foundation.MediaQuery.queries[i];
4077 Interchange.SPECIAL_QUERIES[query.name] = query.value; 4078 } 4079 }; 4080 4081 /** 4082 * Checks the Interchange element for the provided media query + content pairings 4083 * @function 4084 * @private 4085 * @param {Object} element - jQuery object that is an Interchange instance 4086 * @returns {Array} scenarios - Array of objects that have 'mq' and 'path' keys with corresponding keys 4087 */ 4088 Interchange.prototype._generateRules = function() { 4089 var rulesList = []; 4090 var rules; 4091 4092 if (this.options.rules) { 4093 rules = this.options.rules; 4094 } 4095 else { 4096 rules = this.$element.data('interchange').match(/\[.*?\]/g); 4097 } 4098 4099 for (var i in rules) { 4100 var rule = rules[i].slice(1, -1).split(', '); 4101 var path = rule.slice(0, -1).join(''); 4102 var query = rule[rule.length - 1]; 4103 4104 if (Interchange.SPECIAL_QUERIES[query]) { 4105 query = Interchange.SPECIAL_QUERIES[query]; 4106 } 4107 4108 rulesList.push({ 4109 path: path, 4110 query: query 4111 }); 4112 } 4113 4114 this.rules = rulesList; 4115 }; 4116 4117 /** 4118 * Update the `src` property of an image, or change the HTML of a container, to the specified path. 4119 * @function 4120 * @param {String} path - Path to the image or HTML partial. 4121 * @fires Interchange#replaced 4122 */ 4123 Interchange.prototype.replace = function(path) { 4124 if (this.currentPath === path) return; 4125 4126 var _this = this, 4127 trigger = 'replaced.zf.interchange'; 4128 4129 // Replacing images 4130 if (this.$element[0].nodeName === 'IMG') { 4131 this.$element.attr('src', path).load(function() { 4132 _this.currentPath = path; 4133 }) 4134 .trigger(trigger); 4135 } 4136 // Replacing background images 4137 else if (path.match(/\.(gif|jpg|jpeg|tiff|png)([?#].*)?/i)) { 4138 this.$element.css({ 'background-image': 'url('+path+')' }) 4139 .trigger(trigger); 4140 } 4141 // Replacing HTML 4142 else { 4143 $.get(path, function(response) { 4144 _this.$element.html(response) 4145 .trigger(trigger); 4146 $(response).foundation(); 4147 _this.currentPath = path; 4148 }); 4149 } 4150 4151 /** 4152 * Fires when content in an Interchange element is done being loaded. 4153 * @event Interchange#replaced 4154 */ 4155 // this.$element.trigger('replaced.zf.interchange'); 4156 }; 4157 /** 4158 * Destroys an instance of interchange. 4159 * @function 4160 */ 4161 Interchange.prototype.destroy = function(){ 4162 //TODO this. 4163 }; 4164 Foundation.plugin(Interchange, 'Interchange'); 4165 4166 // Exports for AMD/Browserify 4167 if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') 4168 module.exports = Interchange; 4169 if (typeof define === 'function') 4170 define(['foundation'], function() { 4171 return Interchange; 4172 }); 4173 4174}(Foundation, jQuery); 4175 4176/** 4177 * Magellan module. 4178 * @module foundation.magellan 4179 */ 4180!function(Foundation, $) { 4181 'use strict'; 4182 4183 /** 4184 * Creates a new instance of Magellan. 4185 * @class 4186 * @fires Magellan#init 4187 * @param {Object} element - jQuery object to add the trigger to. 4188 * @param {Object} options - Overrides to the default plugin settings. 4189 */ 4190 function Magellan(element, options) { 4191 this.$element = element; 4192 this.options = $.extend({}, Magellan.defaults, this.$element.data(), options); 4193 4194 this._init(); 4195 4196 Foundation.registerPlugin(this, 'Magellan'); 4197 } 4198 4199 /** 4200 * Default settings for plugin 4201 */ 4202 Magellan.defaults = { 4203 /** 4204 * Amount of time, in ms, the animated scrolling should take between locations. 4205 * @option 4206 * @example 500 4207 */ 4208 animationDuration: 500, 4209 /** 4210 * Animation style to use when scrolling between locations. 4211 * @option 4212 * @example 'ease-in-out' 4213 */ 4214 animationEasing: 'linear', 4215 /** 4216 * Number of pixels to use as a marker for location changes. 4217 * @option 4218 * @example 50 4219 */ 4220 threshold: 50, 4221 /** 4222 * Class applied to the active locations link on the magellan container. 4223 * @option 4224 * @example 'active' 4225 */ 4226 activeClass: 'active', 4227 /** 4228 * Allows the script to manipulate the url of the current page, and if supported, alter the history. 4229 * @option 4230 * @example true 4231 */ 4232 deepLinking: false, 4233 /** 4234 * Number of pixels to offset the scroll of the page on item click if using a sticky nav bar. 4235 * @option 4236 * @example 25 4237 */ 4238 barOffset: 0 4239 }; 4240 4241 /** 4242 * Initializes the Magellan plugin and calls functions to get equalizer functioning on load. 4243 * @private 4244 */ 4245 Magellan.prototype._init = function() { 4246 var id = this.$element[0].id || Foundation.GetYoDigits(6, 'magellan'), 4247 _this = this; 4248 this.$targets = $('[data-magellan-target]'); 4249 this.$links = this.$element.find('a'); 4250 this.$element.attr({ 4251 'data-resize': id, 4252 'data-scroll': id, 4253 'id': id 4254 });
4255 this.$active = $(); 4256 this.scrollPos = parseInt(window.pageYOffset, 10); 4257 4258 this._events(); 4259 }; 4260 /** 4261 * Calculates an array of pixel values that are the demarcation lines between locations on the page. 4262 * Can be invoked if new elements are added or the size of a location changes. 4263 * @function 4264 */ 4265 Magellan.prototype.calcPoints = function(){ 4266 var _this = this, 4267 body = document.body, 4268 html = document.documentElement; 4269 4270 this.points = []; 4271 this.winHeight = Math.round(Math.max(window.innerHeight, html.clientHeight)); 4272 this.docHeight = Math.round(Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight)); 4273 4274 this.$targets.each(function(){ 4275 var $tar = $(this), 4276 pt = Math.round($tar.offset().top - _this.options.threshold); 4277 $tar.targetPoint = pt; 4278 _this.points.push(pt); 4279 }); 4280 }; 4281 /** 4282 * Initializes events for Magellan. 4283 * @private 4284 */ 4285 Magellan.prototype._events = function() { 4286 var _this = this, 4287 $body = $('html, body'), 4288 opts = { 4289 duration: _this.options.animationDuration, 4290 easing: _this.options.animationEasing 4291 }; 4292 $(window).one('load', function(){ 4293 if(_this.options.deepLinking){ 4294 if(location.hash){ 4295 _this.scrollToLoc(location.hash); 4296 } 4297 } 4298 _this.calcPoints();
4299 _this._updateActive(); 4300 }); 4301 4302 this.$element.on({ 4303 'resizeme.zf.trigger': this.reflow.bind(this), 4304 'scrollme.zf.trigger': this._updateActive.bind(this) 4305 }).on('click.zf.magellan', 'a[href^="#"]', function(e) { 4306 e.preventDefault(); 4307 var arrival = this.getAttribute('href'); 4308 _this.scrollToLoc(arrival); 4309 }); 4310 }; 4311 /** 4312 * Function to scroll to a given location on the page. 4313 * @param {String} loc - a properly formatted jQuery id selector. 4314 * @example '#foo' 4315 * @function 4316 */ 4317 Magellan.prototype.scrollToLoc = function(loc){ 4318 var scrollPos = $(loc).offset().top - this.options.threshold / 2 - this.options.barOffset; 4319 4320 $(document.body).stop(true).animate({ 4321 scrollTop: scrollPos 4322 }, 4323 { 4324 duration: this.options.animationDuration, 4325 easiing: this.options.animationEasing 4326 }); 4327 }; 4328 /** 4329 * Calls necessary functions to update Magellan upon DOM change 4330 * @function 4331 */ 4332 Magellan.prototype.reflow = function(){ 4333 this.calcPoints();
4334 this._updateActive(); 4335 }; 4336 /** 4337 * Updates the visibility of an active location link, and updates the url hash for the page, if deepLinking enabled. 4338 * @private 4339 * @function 4340 * @fires Magellan#update 4341 */ 4342 Magellan.prototype._updateActive = function(/*evt, elem, scrollPos*/){ 4343 var winPos = /*scrollPos ||*/ parseInt(window.pageYOffset, 10), 4344 curIdx; 4345 4346 if(winPos + this.winHeight === this.docHeight){ curIdx = this.points.length - 1; } 4347 else if(winPos < this.points[0]){ curIdx = 0; } 4348 else{ 4349 var isDown = this.scrollPos < winPos, 4350 _this = this, 4351 curVisible = this.points.filter(function(p, i){ 4352 return isDown ? p <= winPos : p - _this.options.threshold <= winPos;//&& winPos >= _this.points[i -1] - _this.options.threshold; 4353 }); 4354 curIdx = curVisible.length ? curVisible.length - 1 : 0; 4355 } 4356 4357 this.$active.removeClass(this.options.activeClass); 4358 this.$active = this.$links.eq(curIdx).addClass(this.options.activeClass); 4359 4360 if(this.options.deepLinking){ 4361 var hash = this.$active[0].getAttribute('href'); 4362 if(window.history.pushState){ 4363 window.history.pushState(null, null, hash); 4364 }else{ 4365 window.location.hash = hash; 4366 } 4367 } 4368 4369 this.scrollPos = winPos; 4370 /** 4371 * Fires when magellan is finished updating to the new active element. 4372 * @event Magellan#update 4373 */ 4374 this.$element.trigger('update.zf.magellan', [this.$active]); 4375 }; 4376 /** 4377 * Destroys an instance of Magellan and resets the url of the window. 4378 * @function 4379 */ 4380 Magellan.prototype.destroy = function(){ 4381 this.$element.off('.zf.trigger .zf.magellan') 4382 .find('.' + this.options.activeClass).removeClass(this.options.activeClass); 4383 4384 if(this.options.deepLinking){ 4385 var hash = this.$active[0].getAttribute('href'); 4386 window.location.hash.replace(hash, ''); 4387 } 4388 4389 Foundation.unregisterPlugin(this); 4390 }; 4391 Foundation.plugin(Magellan, 'Magellan'); 4392 4393 // Exports for AMD/Browserify 4394 if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') 4395 module.exports = Magellan; 4396 if (typeof define === 'function') 4397 define(['foundation'], function() { 4398 return Magellan; 4399 }); 4400 4401}(Foundation, jQuery); 4402 4403/** 4404 * OffCanvas module. 4405 * @module foundation.offcanvas 4406 * @requires foundation.util.mediaQuery 4407 * @requires foundation.util.triggers 4408 * @requires foundation.util.motion 4409 */ 4410!function($, Foundation) { 4411 4412'use strict'; 4413 4414/** 4415 * Creates a new instance of an off-canvas wrapper. 4416 * @class 4417 * @fires OffCanvas#init 4418 * @param {Object} element - jQuery object to initialize. 4419 * @param {Object} options - Overrides to the default plugin settings. 4420 */ 4421function OffCanvas(element, options) { 4422 this.$element = element; 4423 this.options = $.extend({}, OffCanvas.defaults, this.$element.data(), options); 4424 this.$lastTrigger = $(); 4425 4426 this._init(); 4427 this._events(); 4428 4429 Foundation.registerPlugin(this, 'OffCanvas'); 4430} 4431 4432OffCanvas.defaults = { 4433 /** 4434 * Allow the user to click outside of the menu to close it. 4435 * @option 4436 * @example true 4437 */ 4438 closeOnClick: true, 4439 /** 4440 * Amount of time in ms the open and close transition requires. If none selected, pulls from body style. 4441 * @option 4442 * @example 500 4443 */ 4444 transitionTime: 0, 4445 /** 4446 * Direction the offcanvas opens from. Determines class applied to body. 4447 * @option 4448 * @example left 4449 */ 4450 position: 'left', 4451 /** 4452 * Force the page to scroll to top on open. 4453 */ 4454 forceTop: true, 4455 /** 4456 * Allow the offcanvas to be sticky while open. Does nothing if Sass option `$maincontent-prevent-scroll === true`. 4457 * Performance in Safari OSX/iOS is not great. 4458 */ 4459 // isSticky: false, 4460 /** 4461 * Allow the offcanvas to remain open for certain breakpoints. 4462 * @option 4463 * @example false 4464 */ 4465 isRevealed: false, 4466 /** 4467 * Breakpoint at which to reveal. JS will use a RegExp to target standard classes, if changing classnames, pass your class @`revealClass`. 4468 * @option 4469 * @example reveal-for-large 4470 */ 4471 revealOn: null, 4472 /** 4473 * Force focus to the offcanvas on open. If true, will focus the opening trigger on close.
4474 * @option 4475 * @example true 4476 */ 4477 autoFocus: true, 4478 /** 4479 * Class used to force an offcanvas to remain open. Foundation defaults for this are `reveal-for-large` & `reveal-for-medium`. 4480 * @option 4481 * TODO improve the regex testing for this. 4482 * @example reveal-for-large 4483 */ 4484 revealClass: 'reveal-for-', 4485 /** 4486 * Triggers optional focus trapping when opening an offcanvas. Sets tabindex of [data-off-canvas-content] to -1 for accessibility purposes. 4487 * @option 4488 * @example true 4489 */ 4490 trapFocus: false 4491}; 4492 4493/** 4494 * Initializes the off-canvas wrapper by adding the exit overlay (if needed). 4495 * @function 4496 * @private 4497 */ 4498OffCanvas.prototype._init = function() { 4499 var id = this.$element.attr('id'); 4500 4501 this.$element.attr('aria-hidden', 'true'); 4502 4503 // Find triggers that affect this element and add aria-expanded to them 4504 $(document) 4505 .find('[data-open="'+id+'"], [data-close="'+id+'"], [data-toggle="'+id+'"]') 4506 .attr('aria-expanded', 'false') 4507 .attr('aria-controls', id); 4508 4509 // Add a close trigger over the body if necessary 4510 if (this.options.closeOnClick){ 4511 if($('.js-off-canvas-exit').length){ 4512 this.$exiter = $('.js-off-canvas-exit'); 4513 }else{ 4514 var exiter = document.createElement('div'); 4515 exiter.setAttribute('class', 'js-off-canvas-exit'); 4516 $('[data-off-canvas-content]').append(exiter); 4517 4518 this.$exiter = $(exiter); 4519 } 4520 } 4521 4522 this.options.isRevealed = this.options.isRevealed || new RegExp(this.options.revealClass, 'g').test(this.$element[0].className); 4523 4524 if(this.options.isRevealed){ 4525 this.options.revealOn = this.options.revealOn || this.$element[0].className.match(/(reveal-for-medium|reveal-for-large)/g)[0].split('-')[2]; 4526 this._setMQChecker(); 4527 } 4528 if(!this.options.transitionTime){ 4529 this.options.transitionTime = parseFloat(window.getComputedStyle($('[data-off-canvas-wrapper]')[0]).transitionDuration) * 1000; 4530 } 4531}; 4532 4533/** 4534 * Adds event handlers to the off-canvas wrapper and the exit overlay. 4535 * @function 4536 * @private 4537 */ 4538OffCanvas.prototype._events = function() { 4539 this.$element.off('.zf.trigger .zf.offcanvas').on({ 4540 'open.zf.trigger': this.open.bind(this), 4541 'close.zf.trigger': this.close.bind(this), 4542 'toggle.zf.trigger': this.toggle.bind(this), 4543 'keydown.zf.offcanvas': this._handleKeyboard.bind(this) 4544 }); 4545 4546 if (this.options.closeOnClick && this.$exiter.length) { 4547 this.$exiter.on({'click.zf.offcanvas': this.close.bind(this)}); 4548 } 4549}; 4550/** 4551 * Applies event listener for elements that will reveal at certain breakpoints. 4552 * @private 4553 */ 4554OffCanvas.prototype._setMQChecker = function(){ 4555 var _this = this; 4556 4557 $(window).on('changed.zf.mediaquery', function(){ 4558 if(Foundation.MediaQuery.atLeast(_this.options.revealOn)){ 4559 _this.reveal(true); 4560 }else{ 4561 _this.reveal(false); 4562 } 4563 }).one('load.zf.offcanvas', function(){ 4564 if(Foundation.MediaQuery.atLeast(_this.options.revealOn)){ 4565 _this.reveal(true); 4566 } 4567 }); 4568}; 4569/** 4570 * Handles the revealing/hiding the off-canvas at breakpoints, not the same as open. 4571 * @param {Boolean} isRevealed - true if element should be revealed. 4572 * @function 4573 */ 4574OffCanvas.prototype.reveal = function(isRevealed){ 4575 var $closer = this.$element.find('[data-close]'); 4576 if(isRevealed){ 4577 this.close(); 4578 this.isRevealed = true; 4579 // if(!this.options.forceTop){ 4580 // var scrollPos = parseInt(window.pageYOffset); 4581 // this.$element[0].style.transform = 'translate(0,' + scrollPos + 'px)'; 4582 // } 4583 // if(this.options.isSticky){ this._stick(); } 4584 this.$element.off('open.zf.trigger toggle.zf.trigger'); 4585 if($closer.length){ $closer.hide(); } 4586 }else{ 4587 this.isRevealed = false; 4588 // if(this.options.isSticky || !this.options.forceTop){ 4589 // this.$element[0].style.transform = ''; 4590 // $(window).off('scroll.zf.offcanvas'); 4591 // } 4592 this.$element.on({ 4593 'open.zf.trigger': this.open.bind(this), 4594 'toggle.zf.trigger': this.toggle.bind(this) 4595 }); 4596 if($closer.length){ 4597 $closer.show(); 4598 } 4599 } 4600}; 4601 4602/** 4603 * Opens the off-canvas menu. 4604 * @function 4605 * @param {Object} event - Event object passed from listener. 4606 * @param {jQuery} trigger - element that triggered the off-canvas to open. 4607 * @fires OffCanvas#opened 4608 */ 4609OffCanvas.prototype.open = function(event, trigger) { 4610 if (this.$element.hasClass('is-open') || this.isRevealed){ return; } 4611 var _this = this, 4612 $body = $(document.body); 4613 $('body').scrollTop(0); 4614 // window.pageYOffset = 0; 4615 4616 // if(!this.options.forceTop){ 4617 // var scrollPos = parseInt(window.pageYOffset); 4618 // this.$element[0].style.transform = 'translate(0,' + scrollPos + 'px)'; 4619 // if(this.$exiter.length){ 4620 // this.$exiter[0].style.transform = 'translate(0,' + scrollPos + 'px)'; 4621 // } 4622 // } 4623 /** 4624 * Fires when the off-canvas menu opens. 4625 * @event OffCanvas#opened 4626 */ 4627 Foundation.Move(this.options.transitionTime, this.$element, function(){ 4628 $('[data-off-canvas-wrapper]').addClass('is-off-canvas-open is-open-'+ _this.options.position); 4629 4630 _this.$element 4631 .addClass('is-open') 4632 4633 // if(_this.options.isSticky){ 4634 // _this._stick(); 4635 // } 4636 }); 4637 this.$element.attr('aria-hidden', 'false') 4638 .trigger('opened.zf.offcanvas'); 4639 4640 if(this.options.closeOnClick){ 4641 this.$exiter.addClass('is-visible'); 4642 } 4643 if(trigger){
4644 this.$lastTrigger = trigger.attr('aria-expanded', 'true'); 4645 } 4646 if(this.options.autoFocus){ 4647 this.$element.one('finished.zf.animate', function(){ 4648 _this.$element.find('a, button').eq(0).focus(); 4649 }); 4650 } 4651 if(this.options.trapFocus){ 4652 $('[data-off-canvas-content]').attr('tabindex', '-1'); 4653 this._trapFocus(); 4654 } 4655}; 4656/** 4657 * Traps focus within the offcanvas on open. 4658 * @private 4659 */ 4660OffCanvas.prototype._trapFocus = function(){ 4661 var focusable = Foundation.Keyboard.findFocusable(this.$element), 4662 first = focusable.eq(0), 4663 last = focusable.eq(-1); 4664 4665 focusable.off('.zf.offcanvas').on('keydown.zf.offcanvas', function(e){ 4666 if(e.which === 9 || e.keycode === 9){ 4667 if(e.target === last[0] && !e.shiftKey){ 4668 e.preventDefault(); 4669 first.focus(); 4670 } 4671 if(e.target === first[0] && e.shiftKey){ 4672 e.preventDefault(); 4673 last.focus(); 4674 } 4675 } 4676 }); 4677}; 4678/** 4679 * Allows the offcanvas to appear sticky utilizing translate properties. 4680 * @private 4681 */ 4682// OffCanvas.prototype._stick = function(){ 4683// var elStyle = this.$element[0].style; 4684// 4685// if(this.options.closeOnClick){ 4686// var exitStyle = this.$exiter[0].style; 4687// } 4688// 4689// $(window).on('scroll.zf.offcanvas', function(e){ 4690// console.log(e); 4691// var pageY = window.pageYOffset; 4692// elStyle.transform = 'translate(0,' + pageY + 'px)'; 4693// if(exitStyle !== undefined){ exitStyle.transform = 'translate(0,' + pageY + 'px)'; } 4694// }); 4695// // this.$element.trigger('stuck.zf.offcanvas'); 4696// }; 4697/** 4698 * Closes the off-canvas menu. 4699 * @function 4700 * @param {Function} cb - optional cb to fire after closure. 4701 * @fires OffCanvas#closed 4702 */ 4703OffCanvas.prototype.close = function(cb) { 4704 if(!this.$element.hasClass('is-open') || this.isRevealed){ return; } 4705 4706 var _this = this; 4707 4708 // Foundation.Move(this.options.transitionTime, this.$element, function(){ 4709 $('[data-off-canvas-wrapper]').removeClass('is-off-canvas-open is-open-' + _this.options.position); 4710 _this.$element.removeClass('is-open'); 4711 // Foundation._reflow(); 4712 // }); 4713 this.$element.attr('aria-hidden', 'true') 4714 /** 4715 * Fires when the off-canvas menu opens. 4716 * @event OffCanvas#closed 4717 */ 4718 .trigger('closed.zf.offcanvas'); 4719 // if(_this.options.isSticky || !_this.options.forceTop){ 4720 // setTimeout(function(){ 4721 // _this.$element[0].style.transform = ''; 4722 // $(window).off('scroll.zf.offcanvas'); 4723 // }, this.options.transitionTime); 4724 // } 4725 if(this.options.closeOnClick){ 4726 this.$exiter.removeClass('is-visible'); 4727 } 4728 4729 this.$lastTrigger.attr('aria-expanded', 'false'); 4730 if(this.options.trapFocus){ 4731 $('[data-off-canvas-content]').removeAttr('tabindex'); 4732 } 4733 4734}; 4735 4736/** 4737 * Toggles the off-canvas menu open or closed. 4738 * @function 4739 * @param {Object} event - Event object passed from listener. 4740 * @param {jQuery} trigger - element that triggered the off-canvas to open. 4741 */ 4742OffCanvas.prototype.toggle = function(event, trigger) { 4743 if (this.$element.hasClass('is-open')) { 4744 this.close(event, trigger); 4745 } 4746 else { 4747 this.open(event, trigger); 4748 } 4749}; 4750 4751/** 4752 * Handles keyboard input when detected. When the escape key is pressed, the off-canvas menu closes, and focus is restored to the element that opened the menu. 4753 * @function 4754 * @private 4755 */ 4756OffCanvas.prototype._handleKeyboard = function(event) { 4757 if (event.which !== 27) return; 4758 4759 event.stopPropagation(); 4760 event.preventDefault(); 4761 this.close(); 4762 this.$lastTrigger.focus(); 4763}; 4764/** 4765 * Destroys the offcanvas plugin. 4766 * @function 4767 */ 4768OffCanvas.prototype.destroy = function(){ 4769 this.close(); 4770 this.$element.off('.zf.trigger .zf.offcanvas'); 4771 this.$exiter.off('.zf.offcanvas'); 4772 4773 Foundation.unregisterPlugin(this); 4774}; 4775 4776Foundation.plugin(OffCanvas, 'OffCanvas'); 4777 4778}(jQuery, Foundation); 4779 4780/** 4781* Orbit module. 4782* @module foundation.orbit 4783* @requires foundation.util.keyboard 4784* @requires foundation.util.motion 4785* @requires foundation.util.timerAndImageLoader 4786* @requires foundation.util.touch 4787*/ 4788!function($, Foundation){ 4789 'use strict'; 4790 /** 4791 * Creates a new instance of an orbit carousel. 4792 * @class 4793 * @param {jQuery} element - jQuery object to make into an Orbit Carousel. 4794 * @param {Object} options - Overrides to the default plugin settings. 4795 */ 4796 function Orbit(element, options){ 4797 this.$element = element; 4798 this.options = $.extend({}, Orbit.defaults, this.$element.data(), options); 4799 4800 this._init(); 4801
4802 Foundation.registerPlugin(this, 'Orbit'); 4803 Foundation.Keyboard.register('Orbit', { 4804 'ltr': { 4805 'ARROW_RIGHT': 'next', 4806 'ARROW_LEFT': 'previous' 4807 }, 4808 'rtl': { 4809 'ARROW_LEFT': 'next', 4810 'ARROW_RIGHT': 'previous' 4811 } 4812 }); 4813 } 4814 Orbit.defaults = { 4815 /** 4816 * Tells the JS to loadBullets. 4817 * @option 4818 * @example true 4819 */ 4820 bullets: true, 4821 /** 4822 * Tells the JS to apply event listeners to nav buttons 4823 * @option 4824 * @example true 4825 */ 4826 navButtons: true, 4827 /** 4828 * motion-ui animation class to apply 4829 * @option 4830 * @example 'slide-in-right' 4831 */ 4832 animInFromRight: 'slide-in-right', 4833 /** 4834 * motion-ui animation class to apply 4835 * @option 4836 * @example 'slide-out-right' 4837 */ 4838 animOutToRight: 'slide-out-right', 4839 /** 4840 * motion-ui animation class to apply 4841 * @option 4842 * @example 'slide-in-left' 4843 * 4844 */ 4845 animInFromLeft: 'slide-in-left', 4846 /** 4847 * motion-ui animation class to apply 4848 * @option 4849 * @example 'slide-out-left' 4850 */ 4851 animOutToLeft: 'slide-out-left', 4852 /** 4853 * Allows Orbit to automatically animate on page load. 4854 * @option 4855 * @example true 4856 */ 4857 autoPlay: true, 4858 /** 4859 * Amount of time, in ms, between slide transitions 4860 * @option 4861 * @example 5000 4862 */ 4863 timerDelay: 5000, 4864 /** 4865 * Allows Orbit to infinitely loop through the slides 4866 * @option 4867 * @example true 4868 */ 4869 infiniteWrap: true, 4870 /** 4871 * Allows the Orbit slides to bind to swipe events for mobile, requires an additional util library 4872 * @option 4873 * @example true 4874 */ 4875 swipe: true, 4876 /** 4877 * Allows the timing function to pause animation on hover. 4878 * @option 4879 * @example true 4880 */ 4881 pauseOnHover: true, 4882 /** 4883 * Allows Orbit to bind keyboard events to the slider, to animate frames with arrow keys 4884 * @option 4885 * @example true 4886 */ 4887 accessible: true, 4888 /** 4889 * Class applied to the container of Orbit 4890 * @option 4891 * @example 'orbit-container' 4892 */ 4893 containerClass: 'orbit-container', 4894 /** 4895 * Class applied to individual slides. 4896 * @option 4897 * @example 'orbit-slide' 4898 */ 4899 slideClass: 'orbit-slide', 4900 /** 4901 * Class applied to the bullet container. You're welcome. 4902 * @option 4903 * @example 'orbit-bullets' 4904 */ 4905 boxOfBullets: 'orbit-bullets', 4906 /** 4907 * Class applied to the `next` navigation button. 4908 * @option 4909 * @example 'orbit-next' 4910 */ 4911 nextClass: 'orbit-next', 4912 /** 4913 * Class applied to the `previous` navigation button. 4914 * @option 4915 * @example 'orbit-previous' 4916 */ 4917 prevClass: 'orbit-previous', 4918 /** 4919 * Boolean to flag the js to use motion ui classes or not. Default to true for backwards compatability. 4920 * @option 4921 * @example true 4922 */ 4923 useMUI: true 4924 }; 4925 /** 4926 * Initializes the plugin by creating jQuery collections, setting attributes, and starting the animation. 4927 * @function 4928 * @private 4929 */ 4930 Orbit.prototype._init = function(){ 4931 this.$wrapper = this.$element.find('.' + this.options.containerClass); 4932 this.$slides = this.$element.find('.' + this.options.slideClass); 4933 var $images = this.$element.find('img'), 4934 initActive = this.$slides.filter('.is-active'); 4935 4936 if(!initActive.length){ 4937 this.$slides.eq(0).addClass('is-active'); 4938 } 4939 if(!this.options.useMUI){ 4940 this.$slides.addClass('no-motionui'); 4941 } 4942 if($images.length){ 4943 Foundation.onImagesLoaded($images, this._prepareForOrbit.bind(this)); 4944 }else{ 4945 this._prepareForOrbit();//hehe 4946 } 4947 4948 if(this.options.bullets){ 4949 this._loadBullets(); 4950 } 4951 4952 this._events(); 4953 4954 if(this.options.autoPlay && this.$slides.length > 1){ 4955 this.geoSync(); 4956 } 4957 if(this.options.accessible){ // allow wrapper to be focusable to enable arrow navigation 4958 this.$wrapper.attr('tabindex', 0); 4959 } 4960 }; 4961 /** 4962 * Creates a jQuery collection of bullets, if they are being used. 4963 * @function 4964 * @private 4965 */ 4966 Orbit.prototype._loadBullets = function(){ 4967 this.$bullets = this.$element.find('.' + this.options.boxOfBullets).find('button'); 4968 }; 4969 /** 4970 * Sets a `timer` object on the orbit, and starts the counter for the next slide. 4971 * @function 4972 */ 4973 Orbit.prototype.geoSync = function(){ 4974 var _this = this; 4975 this.timer = new Foundation.Timer( 4976 this.$element, 4977 {duration: this.options.timerDelay, 4978 infinite: false}, 4979 function(){ 4980 _this.changeSlide(true); 4981 }); 4982 this.timer.start(); 4983 }; 4984 /** 4985 * Sets wrapper and slide heights for the orbit. 4986 * @function 4987 * @private 4988 */ 4989 Orbit.prototype._prepareForOrbit = function(){ 4990 var _this = this; 4991 this._setWrapperHeight(function(max){ 4992 _this._setSlideHeight(max); 4993 }); 4994 }; 4995 /** 4996 * Calulates the height of each slide in the collection, and uses the tallest one for the wrapper height. 4997 * @function 4998 * @private 4999 * @param {Function} cb - a callback function to fire when complete. 5000 */ 5001 Orbit.prototype._setWrapperHeight = function(cb){//rewrite this to `for` loop 5002 var max = 0, temp, counter = 0; 5003 5004 this.$slides.each(function(){ 5005 temp = this.getBoundingClientRect().height;
vendor: 7,356 bytes, lines 5006-5173
5006 $(this).attr('data-slide', counter); 5007 5008 if(counter){//if not the first slide, set css position and display property 5009 $(this).css({'position': 'relative', 'display': 'none'}); 5010 } 5011 max = temp > max ? temp : max; 5012 counter++; 5013 }); 5014 5015 if(counter === this.$slides.length){ 5016 this.$wrapper.css({'height': max});//only change the wrapper height property once. 5017 cb(max);//fire callback with max height dimension. 5018 } 5019 }; 5020 /** 5021 * Sets the max-height of each slide. 5022 * @function 5023 * @private 5024 */ 5025 Orbit.prototype._setSlideHeight = function(height){ 5026 this.$slides.each(function(){ 5027 $(this).css('max-height', height); 5028 }); 5029 }; 5030 /** 5031 * Adds event listeners to basically everything within the element. 5032 * @function 5033 * @private 5034 */ 5035 Orbit.prototype._events = function(){ 5036 var _this = this; 5037 5038 //*************************************** 5039 //**Now using custom event - thanks to:** 5040 //** Yohai Ararat of Toronto ** 5041 //*************************************** 5042 if(this.$slides.length > 1){ 5043 5044 if(this.options.swipe){ 5045 this.$slides.off('swipeleft.zf.orbit swiperight.zf.orbit') 5046 .on('swipeleft.zf.orbit', function(e){ 5047 e.preventDefault(); 5048 _this.changeSlide(true); 5049 }).on('swiperight.zf.orbit', function(e){ 5050 e.preventDefault(); 5051 _this.changeSlide(false); 5052 }); 5053 } 5054 //*************************************** 5055 5056 if(this.options.autoPlay){ 5057 this.$slides.on('click.zf.orbit', function(){ 5058 _this.$element.data('clickedOn', _this.$element.data('clickedOn') ? false : true); 5059 _this.timer[_this.$element.data('clickedOn') ? 'pause' : 'start'](); 5060 }); 5061 if(this.options.pauseOnHover){ 5062 this.$element.on('mouseenter.zf.orbit', function(){ 5063 _this.timer.pause(); 5064 }).on('mouseleave.zf.orbit', function(){ 5065 if(!_this.$element.data('clickedOn')){ 5066 _this.timer.start(); 5067 } 5068 }); 5069 } 5070 } 5071 5072 if(this.options.navButtons){ 5073 var $controls = this.$element.find('.' + this.options.nextClass + ', .' + this.options.prevClass); 5074 $controls.attr('tabindex', 0) 5075 //also need to handle enter/return and spacebar key presses 5076 .on('click.zf.orbit touchend.zf.orbit', function(){ 5077 _this.changeSlide($(this).hasClass(_this.options.nextClass)); 5078 }); 5079 } 5080 5081 if(this.options.bullets){ 5082 this.$bullets.on('click.zf.orbit touchend.zf.orbit', function(){ 5083 if(/is-active/g.test(this.className)){ return false; }//if this is active, kick out of function. 5084 var idx = $(this).data('slide'), 5085 ltr = idx > _this.$slides.filter('.is-active').data('slide'), 5086 $slide = _this.$slides.eq(idx); 5087 5088 _this.changeSlide(ltr, $slide, idx); 5089 }); 5090 } 5091 5092 this.$wrapper.add(this.$bullets).on('keydown.zf.orbit', function(e){ 5093 // handle keyboard event with keyboard util 5094 Foundation.Keyboard.handleKey(e, 'Orbit', { 5095 next: function() { 5096 _this.changeSlide(true); 5097 }, 5098 previous: function() { 5099 _this.changeSlide(false); 5100 }, 5101 handled: function() { // if bullet is focused, make sure focus moves 5102 if ($(e.target).is(_this.$bullets)) { 5103 _this.$bullets.filter('.is-active').focus(); 5104 } 5105 } 5106 }); 5107 }); 5108 } 5109 }; 5110 /** 5111 * Changes the current slide to a new one. 5112 * @function 5113 * @param {Boolean} isLTR - flag if the slide should move left to right. 5114 * @param {jQuery} chosenSlide - the jQuery element of the slide to show next, if one is selected. 5115 * @param {Number} idx - the index of the new slide in its collection, if one chosen. 5116 * @fires Orbit#slidechange 5117 */ 5118 Orbit.prototype.changeSlide = function(isLTR, chosenSlide, idx){ 5119 var $curSlide = this.$slides.filter('.is-active').eq(0); 5120 5121 if(/mui/g.test($curSlide[0].className)){ return false; }//if the slide is currently animating, kick out of the function 5122 5123 var $firstSlide = this.$slides.first(), 5124 $lastSlide = this.$slides.last(), 5125 dirIn = isLTR ? 'Right' : 'Left', 5126 dirOut = isLTR ? 'Left' : 'Right', 5127 _this = this, 5128 $newSlide; 5129 5130 if(!chosenSlide){//most of the time, this will be auto played or clicked from the navButtons. 5131 $newSlide = isLTR ? //if wrapping enabled, check to see if there is a `next` or `prev` sibling, if not, select the first or last slide to fill in. if wrapping not enabled, attempt to select `next` or `prev`, if there's nothing there, the function will kick out on next step. CRAZY NESTED TERNARIES!!!!! 5132 (this.options.infiniteWrap ? $curSlide.next('.' + this.options.slideClass).length ? $curSlide.next('.' + this.options.slideClass) : $firstSlide : $curSlide.next('.' + this.options.slideClass))//pick next slide if moving left to right 5133 : 5134 (this.options.infiniteWrap ? $curSlide.prev('.' + this.options.slideClass).length ? $curSlide.prev('.' + this.options.slideClass) : $lastSlide : $curSlide.prev('.' + this.options.slideClass));//pick prev slide if moving right to left 5135 }else{ 5136 $newSlide = chosenSlide; 5137 } 5138 if($newSlide.length){ 5139 if(this.options.bullets){ 5140 idx = idx || this.$slides.index($newSlide);//grab index to update bullets 5141 this._updateBullets(idx); 5142 } 5143 if(this.options.useMUI){ 5144 5145 Foundation.Motion.animateIn( 5146 $newSlide.addClass('is-active').css({'position': 'absolute', 'top': 0}), 5147 this.options['animInFrom' + dirIn], 5148 function(){ 5149 $newSlide.css({'position': 'relative', 'display': 'block'}) 5150 .attr('aria-live', 'polite'); 5151 }); 5152 5153 Foundation.Motion.animateOut( 5154 $curSlide.removeClass('is-active'), 5155 this.options['animOutTo' + dirOut], 5156 function(){ 5157 $curSlide.removeAttr('aria-live'); 5158 if(_this.options.autoPlay && !_this.timer.isPaused){ 5159 _this.timer.restart(); 5160 } 5161 //do stuff? 5162 }); 5163 }else{ 5164 $curSlide.removeClass('is-active is-in').removeAttr('aria-live').hide(); 5165 $newSlide.addClass('is-active is-in').attr('aria-live', 'polite').show(); 5166 if(this.options.autoPlay && !this.timer.isPaused){ 5167 this.timer.restart(); 5168 } 5169 } 5170 /** 5171 * Triggers when the slide has finished animating in. 5172 * @event Orbit#slidechange 5173 */
5174 this.$element.trigger('slidechange.zf.orbit', [$newSlide]); 5175 } 5176 }; 5177 /** 5178 * Updates the active state of the bullets, if displayed. 5179 * @function 5180 * @private 5181 * @param {Number} idx - the index of the current slide. 5182 */ 5183 Orbit.prototype._updateBullets = function(idx){ 5184 var $oldBullet = this.$element.find('.' + this.options.boxOfBullets) 5185 .find('.is-active').removeClass('is-active').blur(), 5186 span = $oldBullet.find('span:last').detach(), 5187 $newBullet = this.$bullets.eq(idx).addClass('is-active').append(span); 5188 }; 5189 /** 5190 * Destroys the carousel and hides the element. 5191 * @function 5192 */ 5193 Orbit.prototype.destroy = function(){ 5194 this.$element.off('.zf.orbit').find('*').off('.zf.orbit').end().hide(); 5195 Foundation.unregisterPlugin(this); 5196 }; 5197 5198 Foundation.plugin(Orbit, 'Orbit'); 5199 5200 }(jQuery, window.Foundation); 5201 5202/** 5203 * ResponsiveMenu module. 5204 * @module foundation.responsiveMenu 5205 * @requires foundation.util.triggers 5206 * @requires foundation.util.mediaQuery 5207 * @requires foundation.util.accordionMenu 5208 * @requires foundation.util.drilldown 5209 * @requires foundation.util.dropdown-menu 5210 */ 5211!function(Foundation, $) { 5212 'use strict'; 5213 5214 // The plugin matches the plugin classes with these plugin instances. 5215 var MenuPlugins = { 5216 dropdown: { 5217 cssClass: 'dropdown', 5218 plugin: Foundation._plugins['dropdown-menu'] || null 5219 }, 5220 drilldown: { 5221 cssClass: 'drilldown', 5222 plugin: Foundation._plugins['drilldown'] || null 5223 }, 5224 accordion: { 5225 cssClass: 'accordion-menu', 5226 plugin: Foundation._plugins['accordion-menu'] || null 5227 } 5228 }; 5229 5230 /** 5231 * Creates a new instance of a responsive menu. 5232 * @class 5233 * @fires ResponsiveMenu#init 5234 * @param {jQuery} element - jQuery object to make into a dropdown menu. 5235 * @param {Object} options - Overrides to the default plugin settings. 5236 */ 5237 function ResponsiveMenu(element) { 5238 this.$element = $(element); 5239 this.rules = this.$element.data('responsive-menu'); 5240 this.currentMq = null; 5241 this.currentPlugin = null; 5242 5243 this._init(); 5244 this._events(); 5245 5246 Foundation.registerPlugin(this, 'ResponsiveMenu'); 5247 } 5248 5249 ResponsiveMenu.defaults = {}; 5250 5251 /** 5252 * Initializes the Menu by parsing the classes from the 'data-ResponsiveMenu' attribute on the element. 5253 * @function 5254 * @private 5255 */ 5256 ResponsiveMenu.prototype._init = function() { 5257 var rulesTree = {}; 5258 5259 // Parse rules from "classes" in data attribute 5260 var rules = this.rules.split(' '); 5261 5262 // Iterate through every rule found 5263 for (var i = 0; i < rules.length; i++) { 5264 var rule = rules[i].split('-'); 5265 var ruleSize = rule.length > 1 ? rule[0] : 'small'; 5266 var rulePlugin = rule.length > 1 ? rule[1] : rule[0]; 5267 5268 if (MenuPlugins[rulePlugin] !== null) { 5269 rulesTree[ruleSize] = MenuPlugins[rulePlugin]; 5270 } 5271 } 5272 5273 this.rules = rulesTree; 5274 5275 if (!$.isEmptyObject(rulesTree)) { 5276 this._checkMediaQueries(); 5277 } 5278 }; 5279 5280 /** 5281 * Initializes events for the Menu. 5282 * @function 5283 * @private 5284 */ 5285 ResponsiveMenu.prototype._events = function() { 5286 var _this = this; 5287 5288 $(window).on('changed.zf.mediaquery', function() { 5289 _this._checkMediaQueries(); 5290 }); 5291 // $(window).on('resize.zf.ResponsiveMenu', function() { 5292 // _this._checkMediaQueries(); 5293 // }); 5294 }; 5295 5296 /** 5297 * Checks the current screen width against available media queries. If the media query has changed, and the plugin needed has changed, the plugins will swap out. 5298 * @function 5299 * @private 5300 */ 5301 ResponsiveMenu.prototype._checkMediaQueries = function() { 5302 var matchedMq, _this = this; 5303 // Iterate through each rule and find the last matching rule 5304 $.each(this.rules, function(key) { 5305 if (Foundation.MediaQuery.atLeast(key)) { 5306 matchedMq = key; 5307 } 5308 });
5309 5310 // No match? No dice 5311 if (!matchedMq) return; 5312 5313 // Plugin already initialized? We good 5314 if (this.currentPlugin instanceof this.rules[matchedMq].plugin) return; 5315 5316 // Remove existing plugin-specific CSS classes 5317 $.each(MenuPlugins, function(key, value) { 5318 _this.$element.removeClass(value.cssClass); 5319 }); 5320 5321 // Add the CSS class for the new plugin 5322 this.$element.addClass(this.rules[matchedMq].cssClass); 5323 5324 // Create an instance of the new plugin 5325 if (this.currentPlugin) this.currentPlugin.destroy(); 5326 this.currentPlugin = new this.rules[matchedMq].plugin(this.$element, {}); 5327 }; 5328 5329 /** 5330 * Destroys the instance of the current plugin on this element, as well as the window resize handler that switches the plugins out. 5331 * @function 5332 */ 5333 ResponsiveMenu.prototype.destroy = function() { 5334 this.currentPlugin.destroy(); 5335 $(window).off('.zf.ResponsiveMenu'); 5336 Foundation.unregisterPlugin(this); 5337 }; 5338 Foundation.plugin(ResponsiveMenu, 'ResponsiveMenu'); 5339 5340}(Foundation, jQuery); 5341 5342/** 5343 * ResponsiveToggle module. 5344 * @module foundation.responsiveToggle 5345 * @requires foundation.util.mediaQuery 5346 */ 5347!function($, Foundation) { 5348 5349'use strict'; 5350 5351/** 5352 * Creates a new instance of Tab Bar. 5353 * @class 5354 * @fires ResponsiveToggle#init 5355 * @param {jQuery} element - jQuery object to attach tab bar functionality to. 5356 * @param {Object} options - Overrides to the default plugin settings. 5357 */ 5358function ResponsiveToggle(element, options) { 5359 this.$element = $(element); 5360 this.options = $.extend({}, ResponsiveToggle.defaults, this.$element.data(), options); 5361 5362 this._init(); 5363 this._events(); 5364 5365 Foundation.registerPlugin(this, 'ResponsiveToggle'); 5366} 5367 5368ResponsiveToggle.defaults = { 5369 /** 5370 * The breakpoint after which the menu is always shown, and the tab bar is hidden. 5371 * @option 5372 * @example 'medium' 5373 */ 5374 hideFor: 'medium' 5375}; 5376 5377/** 5378 * Initializes the tab bar by finding the target element, toggling element, and running update(). 5379 * @function 5380 * @private 5381 */ 5382ResponsiveToggle.prototype._init = function() { 5383 var targetID = this.$element.data('responsive-toggle'); 5384 if (!targetID) { 5385 console.error('Your tab bar needs an ID of a Menu as the value of data-tab-bar.'); 5386 } 5387 5388 this.$targetMenu = $('#'+targetID); 5389 this.$toggler = this.$element.find('[data-toggle]'); 5390 5391 this._update(); 5392}; 5393 5394/** 5395 * Adds necessary event handlers for the tab bar to work. 5396 * @function 5397 * @private 5398 */ 5399ResponsiveToggle.prototype._events = function() { 5400 var _this = this; 5401 5402 $(window).on('changed.zf.mediaquery', this._update.bind(this)); 5403 5404 this.$toggler.on('click.zf.responsiveToggle', this.toggleMenu.bind(this)); 5405}; 5406 5407/** 5408 * Checks the current media query to determine if the tab bar should be visible or hidden. 5409 * @function 5410 * @private 5411 */ 5412ResponsiveToggle.prototype._update = function() { 5413 // Mobile 5414 if (!Foundation.MediaQuery.atLeast(this.options.hideFor)) { 5415 this.$element.show(); 5416 this.$targetMenu.hide(); 5417 } 5418 5419 // Desktop 5420 else { 5421 this.$element.hide(); 5422 this.$targetMenu.show(); 5423 } 5424}; 5425 5426/** 5427 * Toggles the element attached to the tab bar. The toggle only happens if the screen is small enough to allow it. 5428 * @function 5429 * @fires ResponsiveToggle#toggled 5430 */ 5431ResponsiveToggle.prototype.toggleMenu = function() { 5432 if (!Foundation.MediaQuery.atLeast(this.options.hideFor)) { 5433 this.$targetMenu.toggle(0); 5434 5435 /** 5436 * Fires when the element attached to the tab bar toggles. 5437 * @event ResponsiveToggle#toggled 5438 */ 5439 this.$element.trigger('toggled.zf.responsiveToggle'); 5440 } 5441}; 5442ResponsiveToggle.prototype.destroy = function(){ 5443 //TODO this... 5444}; 5445Foundation.plugin(ResponsiveToggle, 'ResponsiveToggle'); 5446 5447}(jQuery, Foundation); 5448 5449/** 5450 * Reveal module. 5451 * @module foundation.reveal 5452 * @requires foundation.util.keyboard 5453 * @requires foundation.util.box 5454 * @requires foundation.util.triggers 5455 * @requires foundation.util.mediaQuery 5456 * @requires foundation.util.motion if using animations 5457 */ 5458!function(Foundation, $) { 5459 'use strict'; 5460 5461 /** 5462 * Creates a new instance of Reveal. 5463 * @class 5464 * @param {jQuery} element - jQuery object to use for the modal. 5465 * @param {Object} options - optional parameters. 5466 */ 5467 5468 function Reveal(element, options) { 5469 this.$element = element; 5470 this.options = $.extend({}, Reveal.defaults, this.$element.data(), options); 5471 this._init(); 5472 5473 Foundation.registerPlugin(this, 'Reveal'); 5474 Foundation.Keyboard.register('Reveal', { 5475 'ENTER': 'open', 5476 'SPACE': 'open', 5477 'ESCAPE': 'close', 5478 'TAB': 'tab_forward', 5479 'SHIFT_TAB': 'tab_backward' 5480 });
5481 } 5482 5483 Reveal.defaults = { 5484 /** 5485 * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide. 5486 * @option 5487 * @example 'slide-in-left' 5488 */ 5489 animationIn: '', 5490 /** 5491 * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide. 5492 * @option 5493 * @example 'slide-out-right' 5494 */ 5495 animationOut: '', 5496 /** 5497 * Time, in ms, to delay the opening of a modal after a click if no animation used. 5498 * @option 5499 * @example 10 5500 */ 5501 showDelay: 0, 5502 /** 5503 * Time, in ms, to delay the closing of a modal after a click if no animation used. 5504 * @option 5505 * @example 10 5506 */ 5507 hideDelay: 0, 5508 /** 5509 * Allows a click on the body/overlay to close the modal. 5510 * @option 5511 * @example true 5512 */ 5513 closeOnClick: true, 5514 /** 5515 * Allows the modal to close if the user presses the `ESCAPE` key. 5516 * @option 5517 * @example true 5518 */ 5519 closeOnEsc: true, 5520 /** 5521 * If true, allows multiple modals to be displayed at once. 5522 * @option 5523 * @example false 5524 */ 5525 multipleOpened: false, 5526 /** 5527 * Distance, in pixels, the modal should push down from the top of the screen. 5528 * @option 5529 * @example 100 5530 */ 5531 vOffset: 100, 5532 /** 5533 * Distance, in pixels, the modal should push in from the side of the screen. 5534 * @option 5535 * @example 0 5536 */ 5537 hOffset: 0, 5538 /** 5539 * Allows the modal to be fullscreen, completely blocking out the rest of the view. JS checks for this as well. 5540 * @option 5541 * @example false 5542 */ 5543 fullScreen: false, 5544 /** 5545 * Percentage of screen height the modal should push up from the bottom of the view. 5546 * @option 5547 * @example 10 5548 */ 5549 btmOffsetPct: 10, 5550 /** 5551 * Allows the modal to generate an overlay div, which will cover the view when modal opens. 5552 * @option 5553 * @example true 5554 */ 5555 overlay: true, 5556 /** 5557 * Allows the modal to remove and reinject markup on close. Should be true if using video elements w/o using provider's api, otherwise, videos will continue to play in the background. 5558 * @option 5559 * @example false 5560 */ 5561 resetOnClose: false, 5562 /** 5563 * Allows the modal to alter the url on open/close, and allows the use of the `back` button to close modals. ALSO, allows a modal to auto-maniacally open on page load IF the hash === the modal's user-set id. 5564 * @option 5565 * @example false 5566 */ 5567 deepLink: false 5568 }; 5569 5570 /** 5571 * Initializes the modal by adding the overlay and close buttons, (if selected). 5572 * @private 5573 */ 5574 Reveal.prototype._init = function(){ 5575 this.id = this.$element.attr('id'); 5576 this.isActive = false; 5577 5578 this.$anchor = $('[data-open="' + this.id + '"]').length ? $('[data-open="' + this.id + '"]') : $('[data-toggle="' + this.id + '"]'); 5579 5580 if(this.$anchor.length){ 5581 var anchorId = this.$anchor[0].id || Foundation.GetYoDigits(6, 'reveal'); 5582 5583 this.$anchor.attr({ 5584 'aria-controls': this.id, 5585 'id': anchorId, 5586 'aria-haspopup': true, 5587 'tabindex': 0 5588 }); 5589 this.$element.attr({'aria-labelledby': anchorId}); 5590 } 5591 5592 // this.options.fullScreen = this.$element.hasClass('full'); 5593 if(this.options.fullScreen || this.$element.hasClass('full')){ 5594 this.options.fullScreen = true; 5595 this.options.overlay = false; 5596 } 5597 if(this.options.overlay && !this.$overlay){ 5598 this.$overlay = this._makeOverlay(this.id); 5599 } 5600 5601 this.$element.attr({ 5602 'role': 'dialog', 5603 'aria-hidden': true, 5604 'data-yeti-box': this.id, 5605 'data-resize': this.id 5606 }); 5607 5608 this._events(); 5609 if(this.options.deepLink && window.location.hash === ( '#' + this.id)){ 5610 $(window).one('load.zf.reveal', this.open.bind(this)); 5611 } 5612 }; 5613 5614 /** 5615 * Creates an overlay div to display behind the modal. 5616 * @private 5617 */ 5618 Reveal.prototype._makeOverlay = function(id){
5619 var $overlay = $('<div></div>') 5620 .addClass('reveal-overlay') 5621 .attr({'tabindex': -1, 'aria-hidden': true}) 5622 .appendTo('body'); 5623 if(this.options.closeOnClick){ 5624 $overlay.attr({ 5625 'data-close': id 5626 }); 5627 } 5628 return $overlay; 5629 }; 5630 5631 /** 5632 * Adds event handlers for the modal. 5633 * @private 5634 */ 5635 Reveal.prototype._events = function(){ 5636 var _this = this; 5637 5638 this.$element.on({ 5639 'open.zf.trigger': this.open.bind(this), 5640 'close.zf.trigger': this.close.bind(this), 5641 'toggle.zf.trigger': this.toggle.bind(this), 5642 'resizeme.zf.trigger': function(){ 5643 if(_this.$element.is(':visible')){ 5644 _this._setPosition(function(){}); 5645 } 5646 } 5647 }); 5648 5649 if(this.$anchor.length){ 5650 this.$anchor.on('keydown.zf.reveal', function(e){ 5651 if(e.which === 13 || e.which === 32){ 5652 e.stopPropagation(); 5653 e.preventDefault(); 5654 _this.open(); 5655 } 5656 }); 5657 } 5658 5659 5660 if(this.options.closeOnClick && this.options.overlay){ 5661 this.$overlay.off('.zf.reveal').on('click.zf.reveal', this.close.bind(this)); 5662 } 5663 if(this.options.deepLink){ 5664 $(window).on('popstate.zf.reveal:' + this.id, this._handleState.bind(this)); 5665 } 5666 }; 5667 /** 5668 * Handles modal methods on back/forward button clicks or any other event that triggers popstate. 5669 * @private 5670 */ 5671 Reveal.prototype._handleState = function(e){ 5672 if(window.location.hash === ( '#' + this.id) && !this.isActive){ this.open(); } 5673 else{ this.close(); } 5674 }; 5675 /** 5676 * Sets the position of the modal before opening 5677 * @param {Function} cb - a callback function to execute when positioning is complete. 5678 * @private 5679 */ 5680 Reveal.prototype._setPosition = function(cb){ 5681 var eleDims = Foundation.Box.GetDimensions(this.$element); 5682 var elePos = this.options.fullScreen ? 'reveal full' : (eleDims.height >= (0.5 * eleDims.windowDims.height)) ? 'reveal' : 'center'; 5683 5684 if(elePos === 'reveal full'){ 5685 //set to full height/width 5686 this.$element 5687 .offset(Foundation.Box.GetOffsets(this.$element, null, elePos, this.options.vOffset)) 5688 .css({ 5689 'height': eleDims.windowDims.height, 5690 'width': eleDims.windowDims.width 5691 }); 5692 }else if(!Foundation.MediaQuery.atLeast('medium') || !Foundation.Box.ImNotTouchingYou(this.$element, null, true, false)){ 5693 //if smaller than medium, resize to 100% width minus any custom L/R margin 5694 this.$element 5695 .css({ 5696 'width': eleDims.windowDims.width - (this.options.hOffset * 2) 5697 }) 5698 .offset(Foundation.Box.GetOffsets(this.$element, null, 'center', this.options.vOffset, this.options.hOffset)); 5699 //flag a boolean so we can reset the size after the element is closed. 5700 this.changedSize = true; 5701 }else{ 5702 this.$element 5703 .css({ 5704 'max-height': eleDims.windowDims.height - (this.options.vOffset * (this.options.btmOffsetPct / 100 + 1)), 5705 'width': '' 5706 }) 5707 .offset(Foundation.Box.GetOffsets(this.$element, null, elePos, this.options.vOffset)); 5708 //the max height based on a percentage of vertical offset plus vertical offset 5709 } 5710 5711 cb(); 5712 }; 5713 5714 /** 5715 * Opens the modal controlled by `this.$anchor`, and closes all others by default. 5716 * @function 5717 * @fires Reveal#closeme 5718 * @fires Reveal#open 5719 */ 5720 Reveal.prototype.open = function(){ 5721 if(this.options.deepLink){ 5722 var hash = '#' + this.id; 5723 5724 if(window.history.pushState){ 5725 window.history.pushState(null, null, hash); 5726 }else{ 5727 window.location.hash = hash; 5728 } 5729 } 5730 5731 var _this = this; 5732 this.isActive = true; 5733 //make element invisible, but remove display: none so we can get size and positioning 5734 this.$element 5735 .css({'visibility': 'hidden'}) 5736 .show() 5737 .scrollTop(0); 5738 5739 this._setPosition(function(){ 5740 _this.$element.hide() 5741 .css({'visibility': ''}); 5742 if(!_this.options.multipleOpened){ 5743 /** 5744 * Fires immediately before the modal opens. 5745 * Closes any other modals that are currently open 5746 * @event Reveal#closeme 5747 */ 5748 _this.$element.trigger('closeme.zf.reveal', _this.id); 5749 } 5750 if(_this.options.animationIn){ 5751 if(_this.options.overlay){ 5752 Foundation.Motion.animateIn(_this.$overlay, 'fade-in', function(){ 5753 Foundation.Motion.animateIn(_this.$element, _this.options.animationIn, function(){ 5754 _this.focusableElements = Foundation.Keyboard.findFocusable(_this.$element); 5755 }); 5756 }); 5757 }else{ 5758 Foundation.Motion.animateIn(_this.$element, _this.options.animationIn, function(){ 5759 _this.focusableElements = Foundation.Keyboard.findFocusable(_this.$element); 5760 }); 5761 } 5762 }else{ 5763 if(_this.options.overlay){ 5764 _this.$overlay.show(0, function(){ 5765 _this.$element.show(_this.options.showDelay, function(){ 5766 }); 5767 }); 5768 }else{ 5769 _this.$element.show(_this.options.showDelay, function(){ 5770 }); 5771 } 5772 } 5773 });
5774 5775 5776 // handle accessibility 5777 this.$element.attr({'aria-hidden': false}).attr('tabindex', -1).focus() 5778 /** 5779 * Fires when the modal has successfully opened. 5780 * @event Reveal#open 5781 */ 5782 .trigger('open.zf.reveal'); 5783 5784 $('body').addClass('is-reveal-open') 5785 .attr({'aria-hidden': (this.options.overlay || this.options.fullScreen) ? true : false}); 5786 setTimeout(function(){ 5787 _this._extraHandlers(); 5788 }, 0); 5789 }; 5790 5791 /** 5792 * Adds extra event handlers for the body and window if necessary. 5793 * @private 5794 */ 5795 Reveal.prototype._extraHandlers = function(){ 5796 var _this = this; 5797 this.focusableElements = Foundation.Keyboard.findFocusable(this.$element); 5798 5799 if(!this.options.overlay && this.options.closeOnClick && !this.options.fullScreen){ 5800 $('body').on('click.zf.reveal', function(e){ 5801 if(e.target === _this.$element[0] || $.contains(_this.$element[0], e.target)){ return; } 5802 _this.close(); 5803 }); 5804 } 5805 if(this.options.closeOnEsc){ 5806 $(window).on('keydown.zf.reveal', function(e){ 5807 Foundation.Keyboard.handleKey(e, 'Reveal', { 5808 close: function() { 5809 if (_this.options.closeOnEsc) { 5810 _this.close(); 5811 _this.$anchor.focus(); 5812 } 5813 } 5814 }); 5815 if (_this.focusableElements.length === 0) { // no focusable elements inside the modal at all, prevent tabbing in general 5816 e.preventDefault(); 5817 } 5818 }); 5819 } 5820 5821 // lock focus within modal while tabbing 5822 this.$element.on('keydown.zf.reveal', function(e) { 5823 var $target = $(this); 5824 // handle keyboard event with keyboard util 5825 Foundation.Keyboard.handleKey(e, 'Reveal', { 5826 tab_forward: function() { 5827 if (_this.$element.find(':focus').is(_this.focusableElements.eq(-1))) { // left modal downwards, setting focus to first element 5828 _this.focusableElements.eq(0).focus(); 5829 e.preventDefault(); 5830 } 5831 }, 5832 tab_backward: function() { 5833 if (_this.$element.find(':focus').is(_this.focusableElements.eq(0)) || _this.$element.is(':focus')) { // left modal upwards, setting focus to last element 5834 _this.focusableElements.eq(-1).focus(); 5835 e.preventDefault(); 5836 } 5837 }, 5838 open: function() { 5839 if (_this.$element.find(':focus').is(_this.$element.find('[data-close]'))) { 5840 setTimeout(function() { // set focus back to anchor if close button has been activated 5841 _this.$anchor.focus(); 5842 }, 1); 5843 } else if ($target.is(_this.focusableElements)) { // dont't trigger if acual element has focus (i.e. inputs, links, ...) 5844 _this.open(); 5845 } 5846 }, 5847 close: function() { 5848 if (_this.options.closeOnEsc) { 5849 _this.close(); 5850 _this.$anchor.focus(); 5851 } 5852 } 5853 }); 5854 }); 5855 5856 }; 5857 5858 /** 5859 * Closes the modal. 5860 * @function 5861 * @fires Reveal#closed 5862 */ 5863 Reveal.prototype.close = function(){ 5864 if(!this.isActive || !this.$element.is(':visible')){ 5865 return false; 5866 } 5867 var _this = this; 5868 5869 if(this.options.animationOut){ 5870 Foundation.Motion.animateOut(this.$element, this.options.animationOut, function(){ 5871 if(_this.options.overlay){ 5872 Foundation.Motion.animateOut(_this.$overlay, 'fade-out', finishUp); 5873 }else{ finishUp(); } 5874 }); 5875 }else{ 5876 this.$element.hide(_this.options.hideDelay, function(){ 5877 if(_this.options.overlay){ 5878 _this.$overlay.hide(0, finishUp); 5879 }else{ finishUp(); } 5880 }); 5881 } 5882 //conditionals to remove extra event listeners added on open 5883 if(this.options.closeOnEsc){ 5884 $(window).off('keydown.zf.reveal'); 5885 } 5886 if(!this.options.overlay && this.options.closeOnClick){ 5887 $('body').off('click.zf.reveal'); 5888 } 5889 this.$element.off('keydown.zf.reveal'); 5890 function finishUp(){ 5891 //if the modal changed size, reset it 5892 if(_this.changedSize){ 5893 _this.$element.css({ 5894 'height': '', 5895 'width': '' 5896 }); 5897 } 5898 $('body').removeClass('is-reveal-open').attr({'aria-hidden': false, 'tabindex': ''}); 5899 _this.$element.attr({'aria-hidden': true}) 5900 /** 5901 * Fires when the modal is done closing. 5902 * @event Reveal#closed 5903 */ 5904 .trigger('closed.zf.reveal'); 5905 } 5906 5907 5908 /** 5909 * Resets the modal content 5910 * This prevents a running video to keep going in the background 5911 */ 5912 if(this.options.resetOnClose) { 5913 this.$element.html(this.$element.html()); 5914 } 5915 5916 this.isActive = false; 5917 if(_this.options.deepLink){ 5918 if(window.history.replaceState){ 5919 window.history.replaceState("", document.title, window.location.pathname); 5920 }else{ 5921 window.location.hash = ''; 5922 } 5923 } 5924 }; 5925 /** 5926 * Toggles the open/closed state of a modal. 5927 * @function 5928 */ 5929 Reveal.prototype.toggle = function(){ 5930 if(this.isActive){ 5931 this.close(); 5932 }else{ 5933 this.open(); 5934 } 5935 }; 5936 5937 /** 5938 * Destroys an instance of a modal. 5939 * @function 5940 */ 5941 Reveal.prototype.destroy = function() { 5942 if(this.options.overlay){ 5943 this.$overlay.hide().off().remove(); 5944 } 5945 this.$element.hide().off(); 5946 this.$anchor.off('.zf'); 5947 $(window).off('.zf.reveal:' + this.id); 5948 5949 Foundation.unregisterPlugin(this); 5950 }; 5951 5952 Foundation.plugin(Reveal, 'Reveal'); 5953 5954 // Exports for AMD/Browserify 5955 if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') 5956 module.exports = Reveal; 5957 if (typeof define === 'function') 5958 define(['foundation'], function() { 5959 return Reveal; 5960 });
5961 5962}(Foundation, jQuery); 5963 5964/** 5965 * Slider module. 5966 * @module foundation.slider 5967 * @requires foundation.util.motion 5968 * @requires foundation.util.triggers 5969 * @requires foundation.util.keyboard 5970 * @requires foundation.util.touch 5971 */ 5972!function($, Foundation){ 5973 'use strict'; 5974 5975 /** 5976 * Creates a new instance of a drilldown menu. 5977 * @class 5978 * @param {jQuery} element - jQuery object to make into an accordion menu. 5979 * @param {Object} options - Overrides to the default plugin settings. 5980 */ 5981 function Slider(element, options){ 5982 this.$element = element; 5983 this.options = $.extend({}, Slider.defaults, this.$element.data(), options); 5984 5985 this._init(); 5986 5987 Foundation.registerPlugin(this, 'Slider'); 5988 Foundation.Keyboard.register('Slider', { 5989 'ltr': { 5990 'ARROW_RIGHT': 'increase', 5991 'ARROW_UP': 'increase', 5992 'ARROW_DOWN': 'decrease', 5993 'ARROW_LEFT': 'decrease', 5994 'SHIFT_ARROW_RIGHT': 'increase_fast', 5995 'SHIFT_ARROW_UP': 'increase_fast', 5996 'SHIFT_ARROW_DOWN': 'decrease_fast', 5997 'SHIFT_ARROW_LEFT': 'decrease_fast' 5998 }, 5999 'rtl': { 6000 'ARROW_LEFT': 'increase', 6001 'ARROW_RIGHT': 'decrease', 6002 'SHIFT_ARROW_LEFT': 'increase_fast', 6003 'SHIFT_ARROW_RIGHT': 'decrease_fast' 6004 } 6005 }); 6006 } 6007 6008 Slider.defaults = { 6009 /** 6010 * Minimum value for the slider scale. 6011 * @option 6012 * @example 0 6013 */ 6014 start: 0, 6015 /** 6016 * Maximum value for the slider scale. 6017 * @option 6018 * @example 100 6019 */ 6020 end: 100, 6021 /** 6022 * Minimum value change per change event. Not Currently Implemented! 6023 6024 */ 6025 step: 1, 6026 /** 6027 * Value at which the handle/input *(left handle/first input)* should be set to on initialization. 6028 * @option 6029 * @example 0 6030 */ 6031 initialStart: 0, 6032 /** 6033 * Value at which the right handle/second input should be set to on initialization. 6034 * @option 6035 * @example 100 6036 */ 6037 initialEnd: 100, 6038 /** 6039 * Allows the input to be located outside the container and visible. Set to by the JS 6040 * @option 6041 * @example false 6042 */ 6043 binding: false, 6044 /** 6045 * Allows the user to click/tap on the slider bar to select a value. 6046 * @option 6047 * @example true 6048 */ 6049 clickSelect: true, 6050 /** 6051 * Set to true and use the `vertical` class to change alignment to vertical. 6052 * @option 6053 * @example false 6054 */ 6055 vertical: false, 6056 /** 6057 * Allows the user to drag the slider handle(s) to select a value. 6058 * @option 6059 * @example true 6060 */ 6061 draggable: true, 6062 /** 6063 * Disables the slider and prevents event listeners from being applied. Double checked by JS with `disabledClass`. 6064 * @option 6065 * @example false 6066 */ 6067 disabled: false, 6068 /** 6069 * Allows the use of two handles. Double checked by the JS. Changes some logic handling. 6070 * @option 6071 * @example false 6072 */ 6073 doubleSided: false, 6074 /** 6075 * Potential future feature. 6076 */ 6077 // steps: 100, 6078 /** 6079 * Number of decimal places the plugin should go to for floating point precision. 6080 * @option 6081 * @example 2 6082 */ 6083 decimal: 2, 6084 /** 6085 * Time delay for dragged elements. 6086 */ 6087 // dragDelay: 0, 6088 /** 6089 * Time, in ms, to animate the movement of a slider handle if user clicks/taps on the bar. Needs to be manually set if updating the transition time in the Sass settings. 6090 * @option 6091 * @example 200 6092 */ 6093 moveTime: 200,//update this if changing the transition time in the sass 6094 /** 6095 * Class applied to disabled sliders. 6096 * @option 6097 * @example 'disabled' 6098 */ 6099 disabledClass: 'disabled', 6100 invertVertical: false 6101 }; 6102 /** 6103 * Initilizes the plugin by reading/setting attributes, creating collections and setting the initial position of the handle(s). 6104 * @function 6105 * @private 6106 */ 6107 Slider.prototype._init = function(){ 6108 this.inputs = this.$element.find('input');
6109 this.handles = this.$element.find('[data-slider-handle]'); 6110 6111 this.$handle = this.handles.eq(0); 6112 this.$input = this.inputs.length ? this.inputs.eq(0) : $('#' + this.$handle.attr('aria-controls')); 6113 this.$fill = this.$element.find('[data-slider-fill]').css(this.options.vertical ? 'height' : 'width', 0); 6114 6115 var isDbl = false, 6116 _this = this; 6117 if(this.options.disabled || this.$element.hasClass(this.options.disabledClass)){ 6118 this.options.disabled = true; 6119 this.$element.addClass(this.options.disabledClass); 6120 } 6121 if(!this.inputs.length){ 6122 this.inputs = $().add(this.$input); 6123 this.options.binding = true; 6124 } 6125 this._setInitAttr(0); 6126 this._events(this.$handle); 6127 6128 if(this.handles[1]){ 6129 this.options.doubleSided = true; 6130 this.$handle2 = this.handles.eq(1); 6131 this.$input2 = this.inputs.length > 1 ? this.inputs.eq(1) : $('#' + this.$handle2.attr('aria-controls')); 6132 6133 if(!this.inputs[1]){ 6134 this.inputs = this.inputs.add(this.$input2); 6135 } 6136 isDbl = true; 6137 6138 this._setHandlePos(this.$handle, this.options.initialStart, true, function(){ 6139 6140 _this._setHandlePos(_this.$handle2, _this.options.initialEnd, true); 6141 }); 6142 // this.$handle.triggerHandler('click.zf.slider'); 6143 this._setInitAttr(1); 6144 this._events(this.$handle2); 6145 } 6146 6147 if(!isDbl){ 6148 this._setHandlePos(this.$handle, this.options.initialStart, true); 6149 } 6150 }; 6151 /** 6152 * Sets the position of the selected handle and fill bar. 6153 * @function 6154 * @private 6155 * @param {jQuery} $hndl - the selected handle to move. 6156 * @param {Number} location - floating point between the start and end values of the slider bar. 6157 * @param {Function} cb - callback function to fire on completion. 6158 * @fires Slider#moved 6159 */ 6160 Slider.prototype._setHandlePos = function($hndl, location, noInvert, cb){ 6161 //might need to alter that slightly for bars that will have odd number selections. 6162 location = parseFloat(location);//on input change events, convert string to number...grumble. 6163 6164 // prevent slider from running out of bounds, if value exceeds the limits set through options, override the value to min/max 6165 if(location < this.options.start){ location = this.options.start; } 6166 else if(location > this.options.end){ location = this.options.end; } 6167 6168 var isDbl = this.options.doubleSided; 6169 6170 if(isDbl){ //this block is to prevent 2 handles from crossing eachother. Could/should be improved. 6171 if(this.handles.index($hndl) === 0){ 6172 var h2Val = parseFloat(this.$handle2.attr('aria-valuenow')); 6173 location = location >= h2Val ? h2Val - this.options.step : location; 6174 }else{ 6175 var h1Val = parseFloat(this.$handle.attr('aria-valuenow')); 6176 location = location <= h1Val ? h1Val + this.options.step : location; 6177 } 6178 } 6179 6180 //this is for single-handled vertical sliders, it adjusts the value to account for the slider being "upside-down" 6181 //for click and drag events, it's weird due to the scale(-1, 1) css property 6182 if(this.options.vertical && !noInvert){ 6183 location = this.options.end - location; 6184 } 6185 6186 var _this = this, 6187 vert = this.options.vertical, 6188 hOrW = vert ? 'height' : 'width', 6189 lOrT = vert ? 'top' : 'left', 6190 handleDim = $hndl[0].getBoundingClientRect()[hOrW], 6191 elemDim = this.$element[0].getBoundingClientRect()[hOrW], 6192 //percentage of bar min/max value based on click or drag point 6193 pctOfBar = percent(location, this.options.end).toFixed(2), 6194 //number of actual pixels to shift the handle, based on the percentage obtained above 6195 pxToMove = (elemDim - handleDim) * pctOfBar, 6196 //percentage of bar to shift the handle 6197 movement = (percent(pxToMove, elemDim) * 100).toFixed(this.options.decimal), 6198 //fixing the decimal value for the location number, is passed to other methods as a fixed floating-point value 6199 location = parseFloat(location.toFixed(this.options.decimal)), 6200 // declare empty object for css adjustments, only used with 2 handled-sli
6200ders 6201 css = {}; 6202 6203 this._setValues($hndl, location); 6204 6205 // TODO update to calculate based on values set to respective inputs?? 6206 if(isDbl){ 6207 var isLeftHndl = this.handles.index($hndl) === 0, 6208 //empty variable, will be used for min-height/width for fill bar 6209 dim, 6210 //percentage w/h of the handle compared to the slider bar 6211 handlePct = ~~(percent(handleDim, elemDim) * 100); 6212 //if left handle, the math is slightly different than if it's the right handle, and the left/top property needs to be changed for the fill bar 6213 if(isLeftHndl){ 6214 //left or top percentage value to apply to the fill bar. 6215 css[lOrT] = movement + '%'; 6216 //calculate the new min-height/width for the fill bar. 6217 dim = parseFloat(this.$handle2[0].style[lOrT]) - movement + handlePct; 6218 //this callback is necessary to prevent errors and allow the proper placement and initialization of a 2-handled slider 6219 //plus, it means we don't care if 'dim' isNaN on init, it won't be in the future. 6220 if(cb && typeof cb === 'function'){ cb(); }//this is only needed for the initialization of 2 handled sliders 6221 }else{ 6222 //just caching the value of the left/bottom handle's left/top property 6223 var handlePos = parseFloat(this.$handle[0].style[lOrT]); 6224 //calculate the new min-height/width for the fill bar. Use isNaN to prevent false positives for numbers <= 0 6225 //based on the percentage of movement of the handle being manipulated, less the opposing handle's left/top position, plus the percentage w/h of the handle itself 6226 dim = movement - (isNaN(handlePos) ? this.options.initialStart : handlePos) + handlePct; 6227 } 6228 // assign the min-height/width to our css object 6229 css['min-' + hOrW] = dim + '%'; 6230 } 6231 6232 this.$element.one('finished.zf.animate', function(){ 6233 /** 6234 * Fires when the handle is done moving. 6235 * @event Slider#moved 6236 */ 6237 _this.$element.trigger('moved.zf.slider', [$hndl]); 6238 }); 6239 6240 //because we don't know exactly how the handle will be moved, check the amount of time it should take to move. 6241 var moveTime = this.$element.data('dragging') ? 1000/60 : this.options.moveTime; 6242 6243 Foundation.Move(moveTime, $hndl, function(){ 6244 //adjusting the left/top property of the handle, based on the percentage calculated above 6245 $hndl.css(lOrT, movement + '%'); 6246 6247 if(!_this.options.doubleSided){ 6248 //if single-handled, a simple method to expand the fill bar 6249 _this.$fill.css(hOrW, pctOfBar * 100 + '%'); 6250 }else{ 6251 //otherwise, use the css object we created above 6252 _this.$fill.css(css); 6253 } 6254 }); 6255 6256 }; 6257 /** 6258 * Sets the initial attribute for the slider element. 6259 * @function 6260 * @private 6261 * @param {Number} idx - index of the current handle/input to use. 6262 */ 6263 Slider.prototype._setInitAttr = function(idx){ 6264 var id = this.inputs.eq(idx).attr('id') || Foundation.GetYoDigits(6, 'slider'); 6265 this.inputs.eq(idx).attr({ 6266 'id': id, 6267 'max': this.options.end, 6268 'min': this.options.start 6269 6270 }); 6271 this.handles.eq(idx).attr({ 6272 'role': 'slider', 6273 'aria-controls': id, 6274 'aria-valuemax': this.options.end, 6275 'aria-valuemin': this.options.start, 6276 'aria-valuenow': idx === 0 ? this.options.initialStart : this.options.initialEnd, 6277 'aria-orientation': this.options.vertical ? 'vertical' : 'horizontal', 6278 'tabindex': 0 6279 }); 6280 }; 6281 /** 6282 * Sets the input and `aria-valuenow` values for the slider element. 6283 * @function 6284 * @private 6285 * @param {jQuery} $handle - the currently selected handle. 6286 * @param {Number} val - floating point of the new value. 6287 */ 6288 Slider.prototype._setValues = function($handle, val){ 6289 var idx = this.options.doubleSided ? this.handles.index($handle) : 0; 6290 this.inputs.eq(idx).val(val); 6291 $handle.attr('aria-valuenow', val); 6292 }; 6293 /** 6294 * Handles events on the slider element. 6295 * Calculates the new location of the current handle. 6296 * If there are two handles and the bar was clicked, it determines which handle to move. 6297 * @function 6298 * @private 6299 * @param {Object} e - the `event` object passed from the listener. 6300 * @param {jQuery} $handle - the current handle to calculate for, if selected. 6301 * @param {Number} val - floating point number for the new value of the slider. 6302 * TODO clean this up, there's a lot of repeated code between this and the _setHandlePos fn. 6303 */ 6304 Slider.prototype._handleEvent = function(e, $handle, val){ 6305 var value, hasVal; 6306 if(!val){//click or drag events 6307 e.preventDefault(); 6308 var _this = this, 6309 vertical = this.options.vertical, 6310 param = vertical ? 'height' : 'width', 6311 direction = vertical ? 'top' : 'left', 6312 pageXY = vertical ? e.pageY : e.pageX, 6313 halfOfHandle = this.$handle[0].getBoundingClientRect()[param] / 2, 6314 barDim = this.$element[0].getBoundingClientRect()[param], 6315 barOffset = (this.$element.offset()[direction] - pageXY), 6316 //if the cursor position is less than or greater than the elements bounding coordinates, set coordinates within those bounds 6317 barXY = barOffset > 0 ? -halfOfHandle : (barOffset - halfOfHandle) < -barDim ? barDim : Math.abs(barOffset),
6318 offsetPct = percent(barXY, barDim); 6319 value = (this.options.end - this.options.start) * offsetPct; 6320 // turn everything around for RTL, yay math! 6321 if (Foundation.rtl() && !this.options.vertical) {value = this.options.end - value;} 6322 //boolean flag for the setHandlePos fn, specifically for vertical sliders 6323 hasVal = false; 6324 6325 if(!$handle){//figure out which handle it is, pass it to the next function. 6326 var firstHndlPos = absPosition(this.$handle, direction, barXY, param), 6327 secndHndlPos = absPosition(this.$handle2, direction, barXY, param); 6328 $handle = firstHndlPos <= secndHndlPos ? this.$handle : this.$handle2; 6329 } 6330 6331 }else{//change event on input 6332 value = val; 6333 hasVal = true; 6334 } 6335 6336 this._setHandlePos($handle, value, hasVal); 6337 }; 6338 /** 6339 * Adds event listeners to the slider elements. 6340 * @function 6341 * @private 6342 * @param {jQuery} $handle - the current handle to apply listeners to. 6343 */ 6344 Slider.prototype._events = function($handle){ 6345 if(this.options.disabled){ return false; } 6346 6347 var _this = this, 6348 curHandle, 6349 timer; 6350 6351 this.inputs.off('change.zf.slider').on('change.zf.slider', function(e){ 6352 var idx = _this.inputs.index($(this)); 6353 _this._handleEvent(e, _this.handles.eq(idx), $(this).val()); 6354 }); 6355 6356 if(this.options.clickSelect){ 6357 this.$element.off('click.zf.slider').on('click.zf.slider', function(e){ 6358 if(_this.$element.data('dragging')){ return false; } 6359 6360 if(_this.options.doubleSided){ 6361 _this._handleEvent(e); 6362 }else{ 6363 _this._handleEvent(e, _this.$handle); 6364 } 6365 }); 6366 } 6367 6368 if(this.options.draggable){ 6369 this.handles.addTouch(); 6370 6371 var $body = $('body'); 6372 $handle 6373 .off('mousedown.zf.slider') 6374 .on('mousedown.zf.slider', function(e){ 6375 $handle.addClass('is-dragging'); 6376 _this.$fill.addClass('is-dragging');// 6377 _this.$element.data('dragging', true); 6378 6379 curHandle = $(e.currentTarget); 6380 6381 $body.on('mousemove.zf.slider', function(e){ 6382 e.preventDefault(); 6383 6384 _this._handleEvent(e, curHandle); 6385 6386 }).on('mouseup.zf.slider', function(e){ 6387 _this._handleEvent(e, curHandle); 6388 6389 $handle.removeClass('is-dragging'); 6390 _this.$fill.removeClass('is-dragging'); 6391 _this.$element.data('dragging', false); 6392 6393 $body.off('mousemove.zf.slider mouseup.zf.slider'); 6394 }); 6395 }); 6396 } 6397 $handle.off('keydown.zf.slider').on('keydown.zf.slider', function(e){ 6398 var _$handle = $(this), 6399 idx = _this.options.doubleSided ? _this.handles.index(_$handle) : 0, 6400 oldValue = parseFloat(_this.inputs.eq(idx).val()), 6401 newValue; 6402 6403 6404 // handle keyboard event with keyboard util 6405 Foundation.Keyboard.handleKey(e, 'Slider', { 6406 decrease: function() { 6407 newValue = oldValue - _this.options.step; 6408 }, 6409 increase: function() { 6410 newValue = oldValue + _this.options.step; 6411 }, 6412 decrease_fast: function() { 6413 newValue = oldValue - _this.options.step * 10; 6414 }, 6415 increase_fast: function() { 6416 newValue = oldValue + _this.options.step * 10; 6417 }, 6418 handled: function() { // only set handle pos when event was handled specially 6419 e.preventDefault(); 6420 _this._setHandlePos(_$handle, newValue, true); 6421 } 6422 }); 6423 /*if (newValue) { // if pressed key has special function, update value 6424 e.preventDefault(); 6425 _this._setHandlePos(_$handle, newValue); 6426 }*/ 6427 }); 6428 }; 6429 /** 6430 * Destroys the slider plugin. 6431 */ 6432 Slider.prototype.destroy = function(){ 6433 this.handles.off('.zf.slider'); 6434 this.inputs.off('.zf.slider'); 6435 this.$element.off('.zf.slider'); 6436 6437 Foundation.unregisterPlugin(this); 6438 }; 6439 6440 Foundation.plugin(Slider, 'Slider'); 6441 6442 function percent(frac, num){ 6443 return (frac / num); 6444 } 6445 function absPosition($handle, dir, clickPos, param){ 6446 return Math.abs(($handle.position()[dir] + ($handle[param]() / 2)) - clickPos);
6447 } 6448}(jQuery, window.Foundation); 6449 6450//*********this is in case we go to static, absolute positions instead of dynamic positioning******** 6451// this.setSteps(function(){ 6452// _this._events(); 6453// var initStart = _this.options.positions[_this.options.initialStart - 1] || null; 6454// var initEnd = _this.options.initialEnd ? _this.options.position[_this.options.initialEnd - 1] : null; 6455// if(initStart || initEnd){ 6456// _this._handleEvent(initStart, initEnd); 6457// } 6458// }); 6459 6460//***********the other part of absolute positions************* 6461// Slider.prototype.setSteps = function(cb){ 6462// var posChange = this.$element.outerWidth() / this.options.steps; 6463// var counter = 0 6464// while(counter < this.options.steps){ 6465// if(counter){ 6466// this.options.positions.push(this.options.positions[counter - 1] + posChange); 6467// }else{ 6468// this.options.positions.push(posChange); 6469// } 6470// counter++; 6471// } 6472// cb(); 6473// }; 6474 6475/** 6476 * Sticky module. 6477 * @module foundation.sticky 6478 * @requires foundation.util.triggers 6479 * @requires foundation.util.mediaQuery 6480 */ 6481!function($, Foundation){ 6482 'use strict'; 6483 6484 /** 6485 * Creates a new instance of a sticky thing. 6486 * @class 6487 * @param {jQuery} element - jQuery object to make sticky. 6488 * @param {Object} options - options object passed when creating the element programmatically. 6489 */ 6490 function Sticky(element, options){ 6491 this.$element = element; 6492 this.options = $.extend({}, Sticky.defaults, this.$element.data(), options); 6493 6494 this._init(); 6495 6496 Foundation.registerPlugin(this, 'Sticky'); 6497 } 6498 Sticky.defaults = { 6499 /** 6500 * Customizable container template. Add your own classes for styling and sizing. 6501 * @option 6502 * @example '<div data-sticky-container class="small-6 columns"></div>' 6503 */ 6504 container: '<div data-sticky-container></div>', 6505 /** 6506 * Location in the view the element sticks to. 6507 * @option 6508 * @example 'top' 6509 */ 6510 stickTo: 'top', 6511 /** 6512 * If anchored to a single element, the id of that element. 6513 * @option 6514 * @example 'exampleId' 6515 */ 6516 anchor: '', 6517 /** 6518 * If using more than one element as anchor points, the id of the top anchor. 6519 * @option 6520 * @example 'exampleId:top' 6521 */ 6522 topAnchor: '', 6523 /** 6524 * If using more than one element as anchor points, the id of the bottom anchor. 6525 * @option 6526 * @example 'exampleId:bottom' 6527 */ 6528 btmAnchor: '', 6529 /** 6530 * Margin, in `em`'s to apply to the top of the element when it becomes sticky. 6531 * @option 6532 * @example 1 6533 */ 6534 marginTop: 1, 6535 /** 6536 * Margin, in `em`'s to apply to the bottom of the element when it becomes sticky. 6537 * @option 6538 * @example 1 6539 */ 6540 marginBottom: 1, 6541 /** 6542 * Breakpoint string that is the minimum screen size an element should become sticky. 6543 * @option 6544 * @example 'medium' 6545 */ 6546 stickyOn: 'medium', 6547 /** 6548 * Class applied to sticky element, and removed on destruction. Foundation defaults to `sticky`. 6549 * @option 6550 * @example 'sticky' 6551 */ 6552 stickyClass: 'sticky', 6553 /** 6554 * Class applied to sticky container. Foundation defaults to `sticky-container`. 6555 * @option 6556 * @example 'sticky-container' 6557 */ 6558 containerClass: 'sticky-container', 6559 /** 6560 * Number of scroll events between the plugin's recalculating sticky points. Setting it to `0` will cause it to recalc every scroll event, setting it to `-1` will prevent recalc on scroll. 6561 * @option 6562 * @example 50 6563 */ 6564 checkEvery: -1 6565 }; 6566 6567 /** 6568 * Initializes the sticky element by adding classes, getting/setting dimensions, breakpoints and attributes 6569 * @function 6570 * @private 6571 */ 6572 Sticky.prototype._init = function(){ 6573 var $parent = this.$element.parent('[data-sticky-container]'), 6574 id = this.$element[0].id || Foundation.GetYoDigits(6, 'sticky'), 6575 _this = this; 6576 6577 if(!$parent.length){ 6578 this.wasWrapped = true; 6579 } 6580 this.$container = $parent.length ? $parent : $(this.options.container).wrapInner(this.$element); 6581 this.$container.addClass(this.options.containerClass); 6582 6583 6584 this.$element.addClass(this.options.stickyClass) 6585 .attr({'data-resize': id}); 6586 6587 this.scrollCount = this.options.checkEvery; 6588 this.isStuck = false; 6589 6590 if(this.options.anchor !== ''){
6591 this.$anchor = $('#' + this.options.anchor); 6592 }else{ 6593 this._parsePoints(); 6594 } 6595 6596 this._setSizes(function(){ 6597 _this._calc(false); 6598 }); 6599 this._events(id.split('-').reverse().join('-')); 6600 }; 6601 /** 6602 * If using multiple elements as anchors, calculates the top and bottom pixel values the sticky thing should stick and unstick on. 6603 * @function 6604 * @private 6605 */ 6606 Sticky.prototype._parsePoints = function(){ 6607 var top = this.options.topAnchor, 6608 btm = this.options.btmAnchor, 6609 pts = [top, btm], 6610 breaks = {}; 6611 if(top && btm){ 6612 6613 for(var i = 0, len = pts.length; i < len && pts[i]; i++){ 6614 var pt; 6615 if(typeof pts[i] === 'number'){ 6616 pt = pts[i]; 6617 }else{ 6618 var place = pts[i].split(':'), 6619 anchor = $('#' + place[0]); 6620 6621 pt = anchor.offset().top; 6622 if(place[1] && place[1].toLowerCase() === 'bottom'){ 6623 pt += anchor[0].getBoundingClientRect().height; 6624 } 6625 } 6626 breaks[i] = pt; 6627 } 6628 }else{ 6629 breaks = {0: 1, 1: document.documentElement.scrollHeight}; 6630 } 6631 6632 this.points = breaks; 6633 return; 6634 }; 6635 6636 /** 6637 * Adds event handlers for the scrolling element. 6638 * @private 6639 * @param {String} id - psuedo-random id for unique scroll event listener. 6640 */ 6641 Sticky.prototype._events = function(id){ 6642 var _this = this, 6643 scrollListener = this.scrollListener = 'scroll.zf.' + id; 6644 if(this.isOn){ return; } 6645 if(this.canStick){ 6646 this.isOn = true; 6647 $(window).off(scrollListener) 6648 .on(scrollListener, function(e){ 6649 if(_this.scrollCount === 0){ 6650 _this.scrollCount = _this.options.checkEvery; 6651 _this._setSizes(function(){ 6652 _this._calc(false, window.pageYOffset); 6653 }); 6654 }else{ 6655 _this.scrollCount--; 6656 _this._calc(false, window.pageYOffset); 6657 } 6658 }); 6659 } 6660 6661 this.$element.off('resizeme.zf.trigger') 6662 .on('resizeme.zf.trigger', function(e, el){ 6663 _this._setSizes(function(){ 6664 _this._calc(false); 6665 if(_this.canStick){ 6666 if(!_this.isOn){ 6667 _this._events(id); 6668 } 6669 }else if(_this.isOn){ 6670 _this._pauseListeners(scrollListener); 6671 } 6672 }); 6673 }); 6674 }; 6675 6676 /** 6677 * Removes event handlers for scroll and change events on anchor. 6678 * @fires Sticky#pause 6679 * @param {String} scrollListener - unique, namespaced scroll listener attached to `window` 6680 */ 6681 Sticky.prototype._pauseListeners = function(scrollListener){ 6682 this.isOn = false; 6683 $(window).off(scrollListener); 6684 6685 /** 6686 * Fires when the plugin is paused due to resize event shrinking the view. 6687 * @event Sticky#pause 6688 * @private 6689 */ 6690 this.$element.trigger('pause.zf.sticky'); 6691 }; 6692 6693 /** 6694 * Called on every `scroll` event and on `_init` 6695 * fires functions based on booleans and cached values 6696 * @param {Boolean} checkSizes - true if plugin should recalculate sizes and breakpoints. 6697 * @param {Number} scroll - current scroll position passed from scroll event cb function. If not passed, defaults to `window.pageYOffset`. 6698 */ 6699 Sticky.prototype._calc = function(checkSizes, scroll){ 6700 if(checkSizes){ this._setSizes(); } 6701 6702 if(!this.canStick){ 6703 if(this.isStuck){ 6704 this._removeSticky(true); 6705 } 6706 return false; 6707 } 6708 6709 if(!scroll){ scroll = window.pageYOffset; } 6710 6711 if(scroll >= this.topPoint){ 6712 if(scroll <= this.bottomPoint){ 6713 if(!this.isStuck){ 6714 this._setSticky(); 6715 } 6716 }else{ 6717 if(this.isStuck){ 6718 this._removeSticky(false); 6719 } 6720 } 6721 }else{ 6722 if(this.isStuck){ 6723 this._removeSticky(true); 6724 } 6725 } 6726 }; 6727 /** 6728 * Causes the $element to become stuck. 6729 * Adds `position: fixed;`, and helper classes. 6730 * @fires Sticky#stuckto 6731 * @function 6732 * @private 6733 */
6734 Sticky.prototype._setSticky = function(){ 6735 var stickTo = this.options.stickTo, 6736 mrgn = stickTo === 'top' ? 'marginTop' : 'marginBottom', 6737 notStuckTo = stickTo === 'top' ? 'bottom' : 'top', 6738 css = {}; 6739 6740 css[mrgn] = this.options[mrgn] + 'em'; 6741 css[stickTo] = 0; 6742 css[notStuckTo] = 'auto'; 6743 css['left'] = this.$container.offset().left + parseInt(window.getComputedStyle(this.$container[0])["padding-left"], 10); 6744 this.isStuck = true; 6745 this.$element.removeClass('is-anchored is-at-' + notStuckTo) 6746 .addClass('is-stuck is-at-' + stickTo) 6747 .css(css) 6748 /** 6749 * Fires when the $element has become `position: fixed;` 6750 * Namespaced to `top` or `bottom`. 6751 * @event Sticky#stuckto 6752 */ 6753 .trigger('sticky.zf.stuckto:' + stickTo); 6754 }; 6755 6756 /** 6757 * Causes the $element to become unstuck. 6758 * Removes `position: fixed;`, and helper classes. 6759 * Adds other helper classes. 6760 * @param {Boolean} isTop - tells the function if the $element should anchor to the top or bottom of its $anchor element. 6761 * @fires Sticky#unstuckfrom 6762 * @private 6763 */ 6764 Sticky.prototype._removeSticky = function(isTop){ 6765 var stickTo = this.options.stickTo, 6766 stickToTop = stickTo === 'top', 6767 css = {}, 6768 anchorPt = (this.points ? this.points[1] - this.points[0] : this.anchorHeight) - this.elemHeight, 6769 mrgn = stickToTop ? 'marginTop' : 'marginBottom', 6770 notStuckTo = stickToTop ? 'bottom' : 'top', 6771 topOrBottom = isTop ? 'top' : 'bottom'; 6772 6773 css[mrgn] = 0; 6774 6775 if((isTop && !stickToTop) || (stickToTop && !isTop)){ 6776 css[stickTo] = anchorPt; 6777 css[notStuckTo] = 0; 6778 }else{ 6779 css[stickTo] = 0; 6780 css[notStuckTo] = anchorPt; 6781 } 6782 6783 css['left'] = ''; 6784 this.isStuck = false; 6785 this.$element.removeClass('is-stuck is-at-' + stickTo) 6786 .addClass('is-anchored is-at-' + topOrBottom) 6787 .css(css) 6788 /** 6789 * Fires when the $element has become anchored. 6790 * Namespaced to `top` or `bottom`. 6791 * @event Sticky#unstuckfrom 6792 */ 6793 .trigger('sticky.zf.unstuckfrom:' + topOrBottom); 6794 }; 6795 6796 /** 6797 * Sets the $element and $container sizes for plugin. 6798 * Calls `_setBreakPoints`. 6799 * @param {Function} cb - optional callback function to fire on completion of `_setBreakPoints`. 6800 * @private 6801 */ 6802 Sticky.prototype._setSizes = function(cb){ 6803 this.canStick = Foundation.MediaQuery.atLeast(this.options.stickyOn); 6804 if(!this.canStick){ cb(); } 6805 var _this = this, 6806 newElemWidth = this.$container[0].getBoundingClientRect().width, 6807 comp = window.getComputedStyle(this.$container[0]), 6808 pdng = parseInt(comp['padding-right'], 10); 6809 6810 if(this.$anchor && this.$anchor.length){ 6811 this.anchorHeight = this.$anchor[0].getBoundingClientRect().height; 6812 }else{ 6813 this._parsePoints(); 6814 } 6815 6816 this.$element.css({ 6817 'max-width': newElemWidth - pdng + 'px' 6818 }); 6819 6820 var newContainerHeight = this.$element[0].getBoundingClientRect().height || this.containerHeight; 6821 this.containerHeight = newContainerHeight; 6822 this.$container.css({ 6823 height: newContainerHeight 6824 }); 6825 this.elemHeight = newContainerHeight; 6826 6827 if (this.isStuck) { 6828 this.$element.css({"left":this.$container.offset().left + parseInt(comp['padding-left'], 10)}); 6829 } 6830 6831 this._setBreakPoints(newContainerHeight, function(){ 6832 if(cb){ cb(); } 6833 }); 6834 6835 }; 6836 /** 6837 * Sets the upper and lower breakpoints for the element to become sticky/unsticky. 6838 * @param {Number} elemHeight - px value for sticky.$element height, calculated by `_setSizes`. 6839 * @param {Function} cb - optional callback function to be called on completion. 6840 * @private 6841 */ 6842 Sticky.prototype._setBreakPoints = function(elemHeight, cb){ 6843 if(!this.canStick){ 6844 if(cb){ cb(); } 6845 else{ return false; } 6846 } 6847 var mTop = emCalc(this.options.marginTop), 6848 mBtm = emCalc(this.options.marginBottom), 6849 topPoint = this.points ? this.points[0] : this.$anchor.offset().top, 6850 bottomPoint = this.points ? this.points[1] : topPoint + this.anchorHeight, 6851 // topPoint = this.$anchor.offset().top || this.points[0], 6852 // bottomPoint = topPoint + this.anchorHeight || this.points[1], 6853 winHeight = window.innerHeight; 6854
6855 if(this.options.stickTo === 'top'){ 6856 topPoint -= mTop; 6857 bottomPoint -= (elemHeight + mTop); 6858 }else if(this.options.stickTo === 'bottom'){ 6859 topPoint -= (winHeight - (elemHeight + mBtm)); 6860 bottomPoint -= (winHeight - mBtm); 6861 }else{ 6862 //this would be the stickTo: both option... tricky 6863 } 6864 6865 this.topPoint = topPoint; 6866 this.bottomPoint = bottomPoint; 6867 6868 if(cb){ cb(); } 6869 }; 6870 6871 /** 6872 * Destroys the current sticky element. 6873 * Resets the element to the top position first. 6874 * Removes event listeners, JS-added css properties and classes, and unwraps the $element if the JS added the $container. 6875 * @function 6876 */ 6877 Sticky.prototype.destroy = function(){ 6878 this._removeSticky(true); 6879 6880 this.$element.removeClass(this.options.stickyClass + ' is-anchored is-at-top') 6881 .css({ 6882 height: '', 6883 top: '', 6884 bottom: '', 6885 'max-width': '' 6886 }) 6887 .off('resizeme.zf.trigger'); 6888 6889 this.$anchor.off('change.zf.sticky'); 6890 $(window).off(this.scrollListener); 6891 6892 if(this.wasWrapped){ 6893 this.$element.unwrap(); 6894 }else{ 6895 this.$container.removeClass(this.options.containerClass) 6896 .css({ 6897 height: '' 6898 }); 6899 } 6900 Foundation.unregisterPlugin(this); 6901 }; 6902 /** 6903 * Helper function to calculate em values 6904 * @param Number {em} - number of em's to calculate into pixels 6905 */ 6906 function emCalc(em){ 6907 return parseInt(window.getComputedStyle(document.body, null).fontSize, 10) * em; 6908 } 6909 Foundation.plugin(Sticky, 'Sticky'); 6910}(jQuery, window.Foundation); 6911 6912/** 6913 * Tabs module. 6914 * @module foundation.tabs 6915 * @requires foundation.util.keyboard 6916 * @requires foundation.util.timerAndImageLoader if tabs contain images 6917 */ 6918!function($, Foundation) { 6919 'use strict'; 6920 6921 /** 6922 * Creates a new instance of tabs. 6923 * @class 6924 * @fires Tabs#init 6925 * @param {jQuery} element - jQuery object to make into tabs. 6926 * @param {Object} options - Overrides to the default plugin settings. 6927 */ 6928 function Tabs(element, options){ 6929 this.$element = element; 6930 this.options = $.extend({}, Tabs.defaults, this.$element.data(), options); 6931 6932 this._init(); 6933 Foundation.registerPlugin(this, 'Tabs'); 6934 Foundation.Keyboard.register('Tabs', { 6935 'ENTER': 'open', 6936 'SPACE': 'open', 6937 'ARROW_RIGHT': 'next', 6938 'ARROW_UP': 'previous', 6939 'ARROW_DOWN': 'next', 6940 'ARROW_LEFT': 'previous' 6941 // 'TAB': 'next', 6942 // 'SHIFT_TAB': 'previous' 6943 }); 6944 } 6945 6946 Tabs.defaults = { 6947 // /** 6948 // * Allows the JS to alter the url of the window. Not yet implemented. 6949 // */ 6950 // deepLinking: false, 6951 // /** 6952 // * If deepLinking is enabled, allows the window to scroll to content if window is loaded with a hash including a tab-pane id 6953 // */ 6954 // scrollToContent: false, 6955 /** 6956 * Allows the window to scroll to content of active pane on load if set to true. 6957 * @option 6958 * @example false 6959 */ 6960 autoFocus: false, 6961 /** 6962 * Allows keyboard input to 'wrap' around the tab links. 6963 * @option 6964 * @example true 6965 */ 6966 wrapOnKeys: true, 6967 /** 6968 * Allows the tab content panes to match heights if set to true. 6969 * @option 6970 * @example false 6971 */ 6972 matchHeight: false, 6973 /** 6974 * Class applied to `li`'s in tab link list. 6975 * @option 6976 * @example 'tabs-title' 6977 */ 6978 linkClass: 'tabs-title', 6979 // contentClass: 'tabs-content', 6980 /** 6981 * Class applied to the content containers. 6982 * @option 6983 * @example 'tabs-panel' 6984 */ 6985 panelClass: 'tabs-panel' 6986 }; 6987 6988 /** 6989 * Initializes the tabs by showing and focusing (if autoFocus=true) the preset active tab. 6990 * @private 6991 */ 6992 Tabs.prototype._init = function(){ 6993 var _this = this; 6994 6995 this.$tabTitles = this.$element.find('.' + this.options.linkClass); 6996 this.$tabContent = $('[data-tabs-content="' + this.$element[0].id + '"]'); 6997 6998 this.$tabTitles.each(function(){ 6999 var $elem = $(this), 7000 $link = $elem.find('a'), 7001 isActive = $elem.hasClass('is-active'), 7002 hash = $link[0].hash.slice(1), 7003 linkId = $link[0].id ? $link[0].id : hash + '-label', 7004 $tabContent = $('#' + hash); 7005 7006 $elem.attr({'role': 'presentation'}); 7007 7008 $link.attr({ 7009 'role': 'tab', 7010 'aria-controls': hash, 7011 'aria-selected': isActive, 7012 'id': linkId 7013 }); 7014 7015 $tabContent.attr({ 7016 'role': 'tabpanel', 7017 'aria-hidden': !isActive, 7018 'aria-labelledby': linkId 7019 }); 7020 7021 if(isActive && _this.options.autoFocus){ 7022 $link.focus(); 7023 } 7024 });
7025 if(this.options.matchHeight){ 7026 var $images = this.$tabContent.find('img'); 7027 if($images.length){ 7028 Foundation.onImagesLoaded($images, this._setHeight.bind(this)); 7029 }else{ 7030 this._setHeight(); 7031 } 7032 } 7033 this._events(); 7034 }; 7035 /** 7036 * Adds event handlers for items within the tabs. 7037 * @private 7038 */ 7039 Tabs.prototype._events = function(){ 7040 this._addKeyHandler(); 7041 this._addClickHandler(); 7042 if(this.options.matchHeight){ 7043 $(window).on('changed.zf.mediaquery', this._setHeight.bind(this)); 7044 } 7045 }; 7046 7047 /** 7048 * Adds click handlers for items within the tabs. 7049 * @private 7050 */ 7051 Tabs.prototype._addClickHandler = function(){ 7052 var _this = this; 7053 this.$element.off('click.zf.tabs') 7054 .on('click.zf.tabs', '.' + this.options.linkClass, function(e){ 7055 e.preventDefault(); 7056 e.stopPropagation(); 7057 if($(this).hasClass('is-active')){ 7058 return; 7059 } 7060 _this._handleTabChange($(this)); 7061 }); 7062 }; 7063 7064 /** 7065 * Adds keyboard event handlers for items within the tabs. 7066 * @private 7067 */ 7068 Tabs.prototype._addKeyHandler = function(){ 7069 var _this = this; 7070 var $firstTab = _this.$element.find('li:first-of-type'); 7071 var $lastTab = _this.$element.find('li:last-of-type'); 7072 7073 this.$tabTitles.off('keydown.zf.tabs').on('keydown.zf.tabs', function(e){ 7074 if(e.which === 9) return; 7075 e.stopPropagation(); 7076 e.preventDefault(); 7077 7078 var $element = $(this), 7079 $elements = $element.parent('ul').children('li'), 7080 $prevElement, 7081 $nextElement; 7082 7083 $elements.each(function(i) { 7084 if ($(this).is($element)) { 7085 if (_this.options.wrapOnKeys) { 7086 $prevElement = i === 0 ? $elements.last() : $elements.eq(i-1); 7087 $nextElement = i === $elements.length -1 ? $elements.first() : $elements.eq(i+1); 7088 } else { 7089 $prevElement = $elements.eq(Math.max(0, i-1)); 7090 $nextElement = $elements.eq(Math.min(i+1, $elements.length-1)); 7091 } 7092 return; 7093 } 7094 }); 7095 7096 // handle keyboard event with keyboard util 7097 Foundation.Keyboard.handleKey(e, 'Tabs', { 7098 open: function() { 7099 $element.find('[role="tab"]').focus(); 7100 _this._handleTabChange($element); 7101 }, 7102 previous: function() { 7103 $prevElement.find('[role="tab"]').focus(); 7104 _this._handleTabChange($prevElement); 7105 }, 7106 next: function() { 7107 $nextElement.find('[role="tab"]').focus(); 7108 _this._handleTabChange($nextElement); 7109 } 7110 }); 7111 }); 7112 }; 7113 7114 7115 /** 7116 * Opens the tab `$targetContent` defined by `$target`. 7117 * @param {jQuery} $target - Tab to open. 7118 * @fires Tabs#change 7119 * @function 7120 */ 7121 Tabs.prototype._handleTabChange = function($target){ 7122 var $tabLink = $target.find('[role="tab"]'), 7123 hash = $tabLink[0].hash, 7124 $targetContent = $(hash), 7125 $oldTab = this.$element.find('.' + this.options.linkClass + '.is-active') 7126 .removeClass('is-active').find('[role="tab"]') 7127 .attr({'aria-selected': 'false'}).attr('aria-controls'); 7128 7129 $('#'+$oldTab).removeClass('is-active').attr({'aria-hidden': 'true'}); 7130 7131 $target.addClass('is-active'); 7132 7133 $tabLink.attr({'aria-selected': 'true'}); 7134 7135 $targetContent 7136 .addClass('is-active') 7137 .attr({'aria-hidden': 'false'}); 7138 7139 /** 7140 * Fires when the plugin has successfully changed tabs. 7141 * @event Tabs#change 7142 */ 7143 this.$element.trigger('change.zf.tabs', [$target]); 7144 // Foundation.reflow(this.$element, 'tabs'); 7145 }; 7146 7147 /** 7148 * Public method for selecting a content pane to display. 7149 * @param {jQuery | String} elem - jQuery object or string of the id of the pane to display. 7150 * @function 7151 */ 7152 Tabs.prototype.selectTab = function(elem){ 7153 var idStr; 7154 if(typeof elem === 'object'){ 7155 idStr = elem[0].id; 7156 }else{ 7157 idStr = elem; 7158 } 7159 7160 if(idStr.indexOf('#') < 0){ 7161 idStr = '#' + idStr; 7162 } 7163 var $target = this.$tabTitles.find('[href="' + idStr + '"]').parent('.' + this.options.linkClass); 7164 7165 this._handleTabChange($target); 7166 }; 7167 /** 7168 * Sets the height of each panel to the height of the tallest panel. 7169 * If enabled in options, gets called on media query change. 7170 * If loading content via external source, can be called directly or with _reflow. 7171 * @function 7172 * @private 7173 */ 7174 Tabs.prototype._setHeight = function(){ 7175 var max = 0; 7176 this.$tabContent.find('.' + this.options.panelClass) 7177 .css('height', '') 7178 .each(function(){ 7179 var panel = $(this), 7180 isActive = panel.hasClass('is-active'); 7181 7182 if(!isActive){ 7183 panel.css({'visibility': 'hidden', 'display': 'block'});
7184 } 7185 var temp = this.getBoundingClientRect().height; 7186 7187 if(!isActive){ 7188 panel.css({'visibility': '', 'display': ''}); 7189 } 7190 7191 max = temp > max ? temp : max; 7192 }) 7193 .css('height', max + 'px'); 7194 }; 7195 7196 /** 7197 * Destroys an instance of an tabs. 7198 * @fires Tabs#destroyed 7199 */ 7200 Tabs.prototype.destroy = function() { 7201 this.$element.find('.' + this.options.linkClass) 7202 .off('.zf.tabs').hide().end() 7203 .find('.' + this.options.panelClass) 7204 .hide(); 7205 if(this.options.matchHeight){ 7206 $(window).off('changed.zf.mediaquery'); 7207 } 7208 Foundation.unregisterPlugin(this); 7209 }; 7210 7211 Foundation.plugin(Tabs, 'Tabs'); 7212 7213 function checkClass($elem){ 7214 return $elem.hasClass('is-active'); 7215 } 7216}(jQuery, window.Foundation); 7217 7218/** 7219 * Toggler module. 7220 * @module foundation.toggler 7221 * @requires foundation.util.motion 7222 * @requires foundation.util.triggers 7223 */ 7224 7225!function(Foundation, $) { 7226 'use strict'; 7227 7228 /** 7229 * Creates a new instance of Toggler. 7230 * @class 7231 * @fires Toggler#init 7232 * @param {Object} element - jQuery object to add the trigger to. 7233 * @param {Object} options - Overrides to the default plugin settings. 7234 */ 7235 function Toggler(element, options) { 7236 this.$element = element; 7237 this.options = $.extend({}, Toggler.defaults, element.data(), options); 7238 this.className = ''; 7239 7240 this._init(); 7241 this._events(); 7242 7243 Foundation.registerPlugin(this, 'Toggler'); 7244 } 7245 7246 Toggler.defaults = { 7247 /** 7248 * Tells the plugin if the element should animated when toggled. 7249 * @option 7250 * @example false 7251 */ 7252 animate: false 7253 }; 7254 7255 /** 7256 * Initializes the Toggler plugin by parsing the toggle class from data-toggler, or animation classes from data-animate. 7257 * @function 7258 * @private 7259 */ 7260 Toggler.prototype._init = function() { 7261 var input; 7262 // Parse animation classes if they were set 7263 if (this.options.animate) { 7264 input = this.options.animate.split(' '); 7265 7266 this.animationIn = input[0]; 7267 this.animationOut = input[1] || null; 7268 } 7269 // Otherwise, parse toggle class 7270 else { 7271 input = this.$element.data('toggler'); 7272 // Allow for a . at the beginning of the string 7273 this.className = input[0] === '.' ? input.slice(1) : input; 7274 } 7275 7276 // Add ARIA attributes to triggers 7277 var id = this.$element[0].id; 7278 $('[data-open="'+id+'"], [data-close="'+id+'"], [data-toggle="'+id+'"]') 7279 .attr('aria-controls', id); 7280 // If the target is hidden, add aria-hidden 7281 this.$element.attr('aria-expanded', this.$element.is(':hidden') ? false : true); 7282 }; 7283 7284 /** 7285 * Initializes events for the toggle trigger. 7286 * @function 7287 * @private 7288 */ 7289 Toggler.prototype._events = function() { 7290 this.$element.off('toggle.zf.trigger').on('toggle.zf.trigger', this.toggle.bind(this)); 7291 }; 7292 7293 /** 7294 * Toggles the target class on the target element. An event is fired from the original trigger depending on if the resultant state was "on" or "off". 7295 * @function 7296 * @fires Toggler#on 7297 * @fires Toggler#off 7298 */ 7299 Toggler.prototype.toggle = function() { 7300 this[ this.options.animate ? '_toggleAnimate' : '_toggleClass'](); 7301 }; 7302 7303 Toggler.prototype._toggleClass = function() { 7304 this.$element.toggleClass(this.className); 7305 7306 var isOn = this.$element.hasClass(this.className); 7307 if (isOn) { 7308 /** 7309 * Fires if the target element has the class after a toggle. 7310 * @event Toggler#on 7311 */ 7312 this.$element.trigger('on.zf.toggler'); 7313 } 7314 else { 7315 /** 7316 * Fires if the target element does not have the class after a toggle. 7317 * @event Toggler#off 7318 */ 7319 this.$element.trigger('off.zf.toggler'); 7320 } 7321 7322 this._updateARIA(isOn); 7323 }; 7324 7325 Toggler.prototype._toggleAnimate = function() { 7326 var _this = this; 7327 7328 if (this.$element.is(':hidden')) { 7329 Foundation.Motion.animateIn(this.$element, this.animationIn, function() { 7330 this.trigger('on.zf.toggler'); 7331 _this._updateARIA(true); 7332 }); 7333 } 7334 else { 7335 Foundation.Motion.animateOut(this.$element, this.animationOut, function() { 7336 this.trigger('off.zf.toggler'); 7337 _this._updateARIA(false); 7338 }); 7339 } 7340 }; 7341 7342 Toggler.prototype._updateARIA = function(isOn) { 7343 this.$element.attr('aria-expanded', isOn ? true : false); 7344 }; 7345 7346 /** 7347 * Destroys the instance of Toggler on the element. 7348 * @function 7349 */ 7350 Toggler.prototype.destroy= function() { 7351 this.$element.off('.zf.toggler'); 7352 Foundation.unregisterPlugin(this); 7353 }; 7354 7355 Foundation.plugin(Toggler, 'Toggler'); 7356 7357 // Exports for AMD/Browserify 7358 if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') 7359 module.exports = Toggler; 7360 if (typeof define === 'function') 7361 define(['foundation'], function() { 7362 return Toggler; 7363 });
7364 7365}(Foundation, jQuery); 7366 7367/** 7368 * Tooltip module. 7369 * @module foundation.tooltip 7370 * @requires foundation.util.box 7371 * @requires foundation.util.triggers 7372 */ 7373!function($, document, Foundation){ 7374 'use strict'; 7375 7376 /** 7377 * Creates a new instance of a Tooltip. 7378 * @class 7379 * @fires Tooltip#init 7380 * @param {jQuery} element - jQuery object to attach a tooltip to. 7381 * @param {Object} options - object to extend the default configuration. 7382 */ 7383 function Tooltip(element, options){ 7384 this.$element = element; 7385 this.options = $.extend({}, Tooltip.defaults, this.$element.data(), options); 7386 7387 this.isActive = false; 7388 this.isClick = false; 7389 this._init(); 7390 7391 Foundation.registerPlugin(this, 'Tooltip'); 7392 } 7393 7394 Tooltip.defaults = { 7395 disableForTouch: false, 7396 /** 7397 * Time, in ms, before a tooltip should open on hover. 7398 * @option 7399 * @example 200 7400 */ 7401 hoverDelay: 200, 7402 /** 7403 * Time, in ms, a tooltip should take to fade into view. 7404 * @option 7405 * @example 150 7406 */ 7407 fadeInDuration: 150, 7408 /** 7409 * Time, in ms, a tooltip should take to fade out of view. 7410 * @option 7411 * @example 150 7412 */ 7413 fadeOutDuration: 150, 7414 /** 7415 * Disables hover events from opening the tooltip if set to true 7416 * @option 7417 * @example false 7418 */ 7419 disableHover: false, 7420 /** 7421 * Optional addtional classes to apply to the tooltip template on init. 7422 * @option 7423 * @example 'my-cool-tip-class' 7424 */ 7425 templateClasses: '', 7426 /** 7427 * Non-optional class added to tooltip templates. Foundation default is 'tooltip'. 7428 * @option 7429 * @example 'tooltip' 7430 */ 7431 tooltipClass: 'tooltip', 7432 /** 7433 * Class applied to the tooltip anchor element. 7434 * @option 7435 * @example 'has-tip' 7436 */ 7437 triggerClass: 'has-tip', 7438 /** 7439 * Minimum breakpoint size at which to open the tooltip. 7440 * @option 7441 * @example 'small' 7442 */ 7443 showOn: 'small', 7444 /** 7445 * Custom template to be used to generate markup for tooltip. 7446 * @option 7447 * @example '<div class="tooltip"></div>' 7448 */ 7449 template: '', 7450 /** 7451 * Text displayed in the tooltip template on open. 7452 * @option 7453 * @example 'Some cool space fact here.' 7454 */ 7455 tipText: '', 7456 touchCloseText: 'Tap to close.', 7457 /** 7458 * Allows the tooltip to remain open if triggered with a click or touch event. 7459 * @option 7460 * @example true 7461 */ 7462 clickOpen: true, 7463 /** 7464 * Additional positioning classes, set by the JS 7465 * @option 7466 * @example 'top' 7467 */ 7468 positionClass: '', 7469 /** 7470 * Distance, in pixels, the template should push away from the anch
7470or on the Y axis. 7471 * @option 7472 * @example 10 7473 */ 7474 vOffset: 10, 7475 /** 7476 * Distance, in pixels, the template should push away from the anchor on the X axis, if aligned to a side. 7477 * @option 7478 * @example 12 7479 */ 7480 hOffset: 12 7481 }; 7482 7483 /** 7484 * Initializes the tooltip by setting the creating the tip element, adding it's text, setting private variables and setting attributes on the anchor. 7485 * @private 7486 */ 7487 Tooltip.prototype._init = function(){ 7488 var elemId = this.$element.attr('aria-describedby') || Foundation.GetYoDigits(6, 'tooltip'); 7489 7490 this.options.positionClass = this._getPositionClass(this.$element); 7491 this.options.tipText = this.options.tipText || this.$element.attr('title'); 7492 this.template = this.options.template ? $(this.options.template) : this._buildTemplate(elemId); 7493 7494 this.template.appendTo(document.body) 7495 .text(this.options.tipText) 7496 .hide(); 7497 7498 this.$element.attr({ 7499 'title': '', 7500 'aria-describedby': elemId, 7501 'data-yeti-box': elemId, 7502 'data-toggle': elemId, 7503 'data-resize': elemId 7504 }).addClass(this.triggerClass); 7505 7506 //helper variables to track movement on collisions 7507 this.usedPositions = []; 7508 this.counter = 4; 7509 this.classChanged = false; 7510 7511 this._events(); 7512 }; 7513 7514 /** 7515 * Grabs the current positioning class, if present, and returns the value or an empty string. 7516 * @private 7517 */ 7518 Tooltip.prototype._getPositionClass = function(element){ 7519 if(!element){ return ''; } 7520 // var position = element.attr('class').match(/top|left|right/g); 7521 var position = element[0].className.match(/\b(top|left|right)\b/g); 7522 position = position ? position[0] : ''; 7523 return position; 7524 }; 7525 /** 7526 * builds the tooltip element, adds attributes, and returns the template. 7527 * @private 7528 */ 7529 Tooltip.prototype._buildTemplate = function(id){ 7530 var templateClasses = (this.options.tooltipClass + ' ' + this.options.positionClass + ' ' + this.options.templateClasses).trim(); 7531 var $template = $('<div></div>').addClass(templateClasses).attr({ 7532 'role': 'tooltip', 7533 'aria-hidden': true, 7534 'data-is-active': false, 7535 'data-is-focus': false, 7536 'id': id 7537 }); 7538 return $template; 7539 }; 7540 7541 /** 7542 * Function that gets called if a collision event is detected. 7543 * @param {String} position - positioning class to try 7544 * @private 7545 */ 7546 Tooltip.prototype._reposition = function(position){ 7547 this.usedPositions.push(position ? position : 'bottom'); 7548 7549 //default, try switching to opposite side 7550 if(!position && (this.usedPositions.indexOf('top') < 0)){ 7551 this.template.addClass('top'); 7552 }else if(position === 'top' && (this.usedPositions.indexOf('bottom') < 0)){ 7553 this.template.removeClass(position); 7554 }else if(position === 'left' && (this.usedPositions.indexOf('right') < 0)){ 7555 this.template.removeClass(position) 7556 .addClass('right'); 7557 }else if(position === 'right' && (this.usedPositions.indexOf('left') < 0)){ 7558 this.template.removeClass(position) 7559 .addClass('left'); 7560 } 7561 7562 //if default change didn't work, try bottom or left first 7563 else if(!position && (this.usedPositions.indexOf('top') > -1) && (this.usedPositions.indexOf('left') < 0)){ 7564 this.template.addClass('left'); 7565 }else if(position === 'top' && (this.usedPositions.indexOf('bottom') > -1) && (this.usedPositions.indexOf('left') < 0)){ 7566 this.template.removeClass(position) 7567 .addClass('left'); 7568 }else if(position === 'left' && (this.usedPositions.indexOf('right') > -1) && (this.usedPositions.indexOf('bottom') < 0)){ 7569 this.template.removeClass(position); 7570 }else if(position === 'right' && (this.usedPositions.indexOf('left') > -1) && (this.usedPositions.indexOf('bottom') < 0)){ 7571 this.template.removeClass(position); 7572 } 7573 //if nothing cleared, set to bottom 7574 else{ 7575 this.template.removeClass(position); 7576 } 7577 this.classChanged = true; 7578 this.counter--; 7579 7580 }; 7581 7582 /** 7583 * sets the position class of an element and recursively calls itself until there are no more possible positions to attempt, or the tooltip element is no longer colliding. 7584 * if the tooltip is larger than the screen width, default to full width - any user selected margin 7585 * @private 7586 */ 7587 Tooltip.prototype._setPosition = function(){ 7588 var position = this._getPositionClass(this.template), 7589 $tipDims = Foundation.Box.GetDimensions(this.template), 7590 $anchorDims = Foundation.Box.GetDimensions(this.$element), 7591 direction = (position === 'left' ? 'left' : ((position === 'right') ? 'left' : 'top')), 7592 param = (direction === 'top') ? 'height' : 'width', 7593 offset = (param === 'height') ? this.options.vOffset : this.options.hOffset, 7594 _this = this; 7595 7596 if(($tipDims.width >= $tipDims.windowDims.width) || (!this.counter && !Foundation.Box.ImNotTouchingYou(this.template))){ 7597 this.template.offset(Foundation.Box.GetOffsets(this.template, this.$element, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({ 7598 // this.$element.offset(Foundation.GetOffsets(this.template, this.$element, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({ 7599 'width': $anchorDims.windowDims.width - (this.options.hOffset * 2), 7600 'height': 'auto' 7601 }); 7602 return false; 7603 } 7604 7605 this.template.offset(Foundation.Box.GetOffsets(this.template, this.$element,'center ' + (position || 'bottom'), this.options.vOffset, this.options.hOffset)); 7606 7607 while(!Foundation.Box.ImNotTouchingYou(this.template) && this.counter){ 7608 this._reposition(position); 7609 this._setPosition(); 7610 } 7611 }; 7612 7613 /** 7614 * reveals the tooltip, and fires an event to close any other open tooltips on the page 7615 * @fires Tooltip#closeme 7616 * @fires Tooltip#show 7617 * @function 7618 */ 7619 Tooltip.prototype.show = function(){ 7620 if(this.options.showOn !== 'all' && !Foundation.MediaQuery.atLeast(this.options.showOn)){ 7621 // console.error('The screen is too small to display this tooltip'); 7622 return false; 7623 } 7624 7625 var _this = this; 7626 this.template.css('visibility', 'hidden').show(); 7627 this._setPosition(); 7628 7629 /** 7630 * Fires to close all other open tooltips on the page 7631 * @event Closeme#tooltip 7632 */ 7633 this.$element.trigger('closeme.zf.tooltip', this.template.attr('id')); 7634 7635 7636 this.template.attr({ 7637 'data-is-active': true, 7638 'aria-hidden': false 7639 });
7640 _this.isActive = true; 7641 // console.log(this.template); 7642 this.template.stop().hide().css('visibility', '').fadeIn(this.options.fadeInDuration, function(){ 7643 //maybe do stuff? 7644 }); 7645 /** 7646 * Fires when the tooltip is shown 7647 * @event Tooltip#show 7648 */ 7649 this.$element.trigger('show.zf.tooltip'); 7650 }; 7651 7652 /** 7653 * Hides the current tooltip, and resets the positioning class if it was changed due to collision 7654 * @fires Tooltip#hide 7655 * @function 7656 */ 7657 Tooltip.prototype.hide = function(){ 7658 // console.log('hiding', this.$element.data('yeti-box')); 7659 var _this = this; 7660 this.template.stop().attr({ 7661 'aria-hidden': true, 7662 'data-is-active': false 7663 }).fadeOut(this.options.fadeOutDuration, function(){ 7664 _this.isActive = false; 7665 _this.isClick = false; 7666 if(_this.classChanged){ 7667 _this.template 7668 .removeClass(_this._getPositionClass(_this.template)) 7669 .addClass(_this.options.positionClass); 7670 7671 _this.usedPositions = []; 7672 _this.counter = 4; 7673 _this.classChanged = false; 7674 } 7675 }); 7676 /** 7677 * fires when the tooltip is hidden 7678 * @event Tooltip#hide 7679 */ 7680 this.$element.trigger('hide.zf.tooltip'); 7681 }; 7682 7683 /** 7684 * adds event listeners for the tooltip and its anchor 7685 * TODO combine some of the listeners like focus and mouseenter, etc. 7686 * @private 7687 */ 7688 Tooltip.prototype._events = function(){ 7689 var _this = this; 7690 var $template = this.template; 7691 var isFocus = false; 7692 7693 if(!this.options.disableHover){ 7694 7695 this.$element 7696 .on('mouseenter.zf.tooltip', function(e){ 7697 if(!_this.isActive){ 7698 _this.timeout = setTimeout(function(){ 7699 _this.show(); 7700 }, _this.options.hoverDelay); 7701 } 7702 }) 7703 .on('mouseleave.zf.tooltip', function(e){ 7704 clearTimeout(_this.timeout); 7705 if(!isFocus || (!_this.isClick && _this.options.clickOpen)){ 7706 _this.hide(); 7707 } 7708 }); 7709 } 7710 if(this.options.clickOpen){ 7711 this.$element.on('mousedown.zf.tooltip', function(e){ 7712 e.stopImmediatePropagation(); 7713 if(_this.isClick){ 7714 _this.hide(); 7715 // _this.isClick = false; 7716 }else{ 7717 _this.isClick = true; 7718 if((_this.options.disableHover || !_this.$element.attr('tabindex')) && !_this.isActive){ 7719 _this.show(); 7720 } 7721 } 7722 }); 7723 } 7724 7725 if(!this.options.disableForTouch){ 7726 this.$element 7727 .on('tap.zf.tooltip touchend.zf.tooltip', function(e){ 7728 _this.isActive ? _this.hide() : _this.show(); 7729 }); 7730 } 7731 7732 this.$element.on({ 7733 // 'toggle.zf.trigger': this.toggle.bind(this), 7734 // 'close.zf.trigger': this.hide.bind(this) 7735 'close.zf.trigger': this.hide.bind(this) 7736 }); 7737 7738 this.$element 7739 .on('focus.zf.tooltip', function(e){ 7740 isFocus = true; 7741 // console.log(_this.isClick); 7742 if(_this.isClick){ 7743 return false; 7744 }else{ 7745 // $(window) 7746 _this.show(); 7747 } 7748 }) 7749 7750 .on('focusout.zf.tooltip', function(e){ 7751 isFocus = false; 7752 _this.isClick = false; 7753 _this.hide(); 7754 }) 7755 7756 .on('resizeme.zf.trigger', function(){ 7757 if(_this.isActive){ 7758 _this._setPosition(); 7759 } 7760 }); 7761 }; 7762 /** 7763 * adds a toggle method, in addition to the static show() & hide() functions 7764 * @function 7765 */ 7766 Tooltip.prototype.toggle = function(){ 7767 if(this.isActive){ 7768 this.hide(); 7769 }else{ 7770 this.show(); 7771 } 7772 }; 7773 /** 7774 * Destroys an instance of tooltip, removes template element from the view. 7775 * @function 7776 */ 7777 Tooltip.prototype.destroy = function(){ 7778 this.$element.attr('title', this.template.text()) 7779 .off('.zf.trigger .zf.tootip') 7780 // .removeClass('has-tip') 7781 .removeAttr('aria-describedby') 7782 .removeAttr('data-yeti-box') 7783 .removeAttr('data-toggle') 7784 .removeAttr('data-resize'); 7785 7786 this.template.remove(); 7787 7788 Foundation.unregisterPlugin(this); 7789 }; 7790 /** 7791 * TODO utilize resize event trigger 7792 */ 7793 7794 Foundation.plugin(Tooltip, 'Tooltip'); 7795}(jQuery, window.document, window.Foundation);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.