1/*! responsive-nav.js 1.0.38 2 * https://github.com/viljamis/responsive-nav.js 3 * http://responsive-nav.com 4 * 5 * Copyright (c) 2015 @viljamis 6 * Available under the MIT license 7 */ 8 9/* global Event */ 10(function (document, window, index) { 11 // Index is used to keep multiple navs on the same page namespaced 12 13 "use strict"; 14 15 var responsiveNav = function (el, options) { 16 17 var computed = !!window.getComputedStyle; 18 19 /** 20 * getComputedStyle polyfill for old browsers 21 */ 22 if (!computed) { 23 window.getComputedStyle = function(el) { 24 this.el = el; 25 this.getPropertyValue = function(prop) { 26 var re = /(\-([a-z]){1})/g; 27 if (prop === "float") { 28 prop = "styleFloat"; 29 } 30 if (re.test(prop)) { 31 prop = prop.replace(re, function () { 32 return arguments[2].toUpperCase(); 33 }); 34 } 35 return el.currentStyle[prop] ? el.currentStyle[prop] : null; 36 }; 37 return this; 38 }; 39 } 40 /* exported addEvent, removeEvent, getChildren, setAttributes, addClass, removeClass, forEach */ 41 42 /** 43 * Add Event 44 * fn arg can be an object or a function, thanks to handleEvent 45 * read more at: http://www.thecssninja.com/javascript/handleevent 46 * 47 * @param {element} element 48 * @param {event} event 49 * @param {Function} fn 50 * @param {boolean} bubbling 51 */ 52 var addEvent = function (el, evt, fn, bubble) { 53 if ("addEventListener" in el) { 54 // BBOS6 doesn't support handleEvent, catch and polyfill 55 try { 56 el.addEventListener(evt, fn, bubble); 57 } catch (e) { 58 if (typeof fn === "object" && fn.handleEvent) { 59 el.addEventListener(evt, function (e) { 60 // Bind fn as this and set first arg as event object 61 fn.handleEvent.call(fn, e); 62 }, bubble); 63 } else { 64 throw e; 65 } 66 } 67 } else if ("attachEvent" in el) { 68 // check if the callback is an object and contains handleEvent 69 if (typeof fn === "object" && fn.handleEvent) { 70 el.attachEvent("on" + evt, function () { 71 // Bind fn as this 72 fn.handleEvent.call(fn); 73 }); 74 } else { 75 el.attachEvent("on" + evt, fn); 76 } 77 } 78 }, 79 80 /** 81 * Remove Event 82 * 83 * @param {element} element 84 * @param {event} event 85 * @param {Function} fn 86 * @param {boolean} bubbling 87 */ 88 removeEvent = function (el, evt, fn, bubble) { 89 if ("removeEventListener" in el) { 90 try { 91 el.removeEventListener(evt, fn, bubble); 92 } catch (e) { 93 if (typeof fn === "object" && fn.handleEvent) { 94 el.removeEventListener(evt, function (e) { 95 fn.handleEvent.call(fn, e); 96 }, bubble); 97 } else { 98 throw e; 99 } 100 } 101 } else if ("detachEvent" in el) { 102 if (typeof fn === "object" && fn.handleEvent) { 103 el.detachEvent("on" + evt, function () { 104 fn.handleEvent.call(fn); 105 }); 106 } else { 107 el.detachEvent("on" + evt, fn); 108 } 109 } 110 }, 111 112 /** 113 * Get the children of any element 114 * 115 * @param {element} 116 * @return {array} Returns matching elements in an array 117 */ 118 getChildren = function (e) { 119 if (e.children.length < 1) { 120 throw new Error("The Nav container has no containing elements"); 121 } 122 // Store all children in array 123 var children = []; 124 // Loop through children and store in array if child != TextNode 125 for (var i = 0; i < e.children.length; i++) { 126 if (e.children[i].nodeType === 1) { 127 children.push(e.children[i]); 128 } 129 } 130 return children; 131 }, 132 133 /** 134 * Sets multiple attributes at once 135 * 136 * @param {element} element 137 * @param {attrs} attrs 138 */ 139 setAttributes = function (el, attrs) { 140 for (var key in attrs) { 141 el.setAttribute(key, attrs[key]); 142 } 143 }, 144 145 /** 146 * Adds a class to any element 147 * 148 * @param {element} element 149 * @param {string} class 150 */ 151 addClass = function (el, cls) { 152 if (el.className.indexOf(cls) !== 0) { 153 el.className += " " + cls; 154 el.className = el.className.replace(/(^\s*)|(\s*$)/g,""); 155 } 156 }, 157 158 /** 159 * Remove a class from any element 160 * 161 * @param {element} element 162 * @param {string} class 163 */ 164 removeClass = function (el, cls) { 165 var reg = new RegExp("(\\s|^)" + cls + "(\\s|$)"); 166 el.className = el.className.replace(reg, " ").replace(/(^\s*)|(\s*$)/g,""); 167 }, 168 169 /** 170 * forEach method that passes back the stuff we need 171 * 172 * @param {array} array 173 * @param {Function} callback 174 * @param {scope} scope 175 */
176 forEach = function (array, callback, scope) { 177 for (var i = 0; i < array.length; i++) { 178 callback.call(scope, i, array[i]); 179 } 180 }; 181 182 var nav, 183 opts, 184 navToggle, 185 styleElement = document.createElement("style"), 186 htmlEl = document.documentElement, 187 hasAnimFinished, 188 isMobile, 189 navOpen; 190 191 var ResponsiveNav = function (el, options) { 192 var i; 193 194 /** 195 * Default options 196 * @type {Object} 197 */ 198 this.options = { 199 animate: true, // Boolean: Use CSS3 transitions, true or false 200 transition: 284, // Integer: Speed of the transition, in milliseconds 201 label: "Menu", // String: Label for the navigation toggle 202 insert: "before", // String: Insert the toggle before or after the navigation 203 customToggle: "", // Selector: Specify the ID of a custom toggle 204 closeOnNavClick: false, // Boolean: Close the navigation when one of the links are clicked 205 openPos: "relative", // String: Position of the opened nav, relative or static 206 navClass: "nav-collapse", // String: Default CSS class. If changed, you need to edit the CSS too! 207 navActiveClass: "js-nav-active", // String: Class that is added to <html> element when nav is active 208 jsClass: "js", // String: 'JS enabled' class which is added to <html> element 209 init: function(){}, // Function: Init callback 210 open: function(){}, // Function: Open callback 211 close: function(){} // Function: Close callback 212 }; 213 214 // User defined options 215 for (i in options) { 216 this.options[i] = options[i]; 217 } 218 219 // Adds "js" class for <html> 220 addClass(htmlEl, this.options.jsClass); 221 222 // Wrapper 223 this.wrapperEl = el.replace("#", ""); 224 225 // Try selecting ID first 226 if (document.getElementById(this.wrapperEl)) { 227 this.wrapper = document.getElementById(this.wrapperEl); 228 229 // If element with an ID doesn't exist, use querySelector 230 } else if (document.querySelector(this.wrapperEl)) { 231 this.wrapper = document.querySelector(this.wrapperEl); 232 233 // If element doesn't exists, stop here. 234 } else { 235 throw new Error("The nav element you are trying to select doesn't exist"); 236 } 237 238 // Inner wrapper 239 this.wrapper.inner = getChildren(this.wrapper); 240 241 // For minification 242 opts = this.options; 243 nav = this.wrapper; 244 245 // Init 246 this._init(this); 247 }; 248 249 ResponsiveNav.prototype = { 250 251 /** 252 * Unattaches events and removes any classes that were added 253 */ 254 destroy: function () { 255 this._removeStyles(); 256 removeClass(nav, "closed"); 257 removeClass(nav, "opened"); 258 removeClass(nav, opts.navClass); 259 removeClass(nav, opts.navClass + "-" + this.index); 260 removeClass(htmlEl, opts.navActiveClass); 261 nav.removeAttribute("style"); 262 nav.removeAttribute("aria-hidden"); 263 264 removeEvent(window, "resize", this, false); 265 removeEvent(window, "focus", this, false); 266 removeEvent(document.body, "touchmove", this, false); 267 removeEvent(navToggle, "touchstart", this, false); 268 removeEvent(navToggle, "touchend", this, false); 269 removeEvent(navToggle, "mouseup", this, false); 270 removeEvent(navToggle, "keyup", this, false); 271 removeEvent(navToggle, "click", this, false); 272 273 if (!opts.customToggle) { 274 navToggle.parentNode.removeChild(navToggle); 275 } else { 276 navToggle.removeAttribute("aria-hidden"); 277 } 278 }, 279 280 /** 281 * Toggles the navigation open/close 282 */ 283 toggle: function () { 284 if (hasAnimFinished === true) { 285 if (!navOpen) { 286 this.open(); 287 } else { 288 this.close(); 289 } 290 } 291 }, 292 293 /** 294 * Opens the navigation 295 */ 296 open: function () { 297 if (!navOpen) { 298 removeClass(nav, "closed"); 299 addClass(nav, "opened"); 300 addClass(htmlEl, opts.navActiveClass); 301 addClass(navToggle, "active"); 302 nav.style.position = opts.openPos; 303 setAttributes(nav, {"aria-hidden": "true"}); 304 navOpen = true; 305 opts.open(); 306 } 307 }, 308 309 /** 310 * Closes the navigation 311 */ 312 close: function () { 313 if (navOpen) { 314 addClass(nav, "closed"); 315 removeClass(nav, "opened"); 316 removeClass(htmlEl, opts.navActiveClass); 317 removeClass(navToggle, "active"); 318 setAttributes(nav, {"aria-hidden": "true"}); 319 320 // If animations are enabled, wait until they finish 321 if (opts.animate) { 322 hasAnimFinished = false;
323 setTimeout(function () { 324 nav.style.position = "absolute"; 325 hasAnimFinished = true; 326 }, opts.transition + 10); 327 328 // Animations aren't enabled, we can do these immediately 329 } else { 330 nav.style.position = "absolute"; 331 } 332 333 navOpen = false; 334 opts.close(); 335 } 336 }, 337 338 /** 339 * Resize is called on window resize and orientation change. 340 * It initializes the CSS styles and height calculations. 341 */ 342 resize: function () { 343 344 // Resize watches navigation toggle's display state 345 if (window.getComputedStyle(navToggle, null).getPropertyValue("display") !== "none") { 346 347 isMobile = true; 348 setAttributes(navToggle, {"aria-hidden": "false"}); 349 350 // If the navigation is hidden 351 if (nav.className.match(/(^|\s)closed(\s|$)/)) { 352 setAttributes(nav, {"aria-hidden": "true"}); 353 nav.style.position = "absolute"; 354 } 355 356 this._createStyles(); 357 this._calcHeight(); 358 } else { 359 360 isMobile = false; 361 setAttributes(navToggle, {"aria-hidden": "false"}); 362 setAttributes(nav, {"aria-hidden": "true"}); 363 nav.style.position = "absolute"; 364 this._removeStyles(); 365 } 366 }, 367 368 /** 369 * Takes care of all even handling 370 * 371 * @param {event} event 372 * @return {type} returns the type of event that should be used 373 */ 374 handleEvent: function (e) { 375 var evt = e || window.event; 376 377 switch (evt.type) { 378 case "touchstart": 379 this._onTouchStart(evt); 380 break; 381 case "touchmove": 382 this._onTouchMove(evt); 383 break; 384 case "touchend": 385 case "mouseup": 386 this._onTouchEnd(evt); 387 break; 388 case "click": 389 this._preventDefault(evt); 390 break; 391 case "keyup": 392 this._onKeyUp(evt); 393 break; 394 case "focus": 395 case "resize": 396 this.resize(evt); 397 break; 398 } 399 }, 400 401 /** 402 * Initializes the widget 403 */ 404 _init: function () { 405 this.index = index++; 406 407 addClass(nav, opts.navClass); 408 addClass(nav, opts.navClass + "-" + this.index); 409 addClass(nav, "closed"); 410 hasAnimFinished = true; 411 navOpen = false; 412 413 this._closeOnNavClick(); 414 this._createToggle(); 415 this._transitions(); 416 this.resize(); 417 418 /** 419 * On IE8 the resize event triggers too early for some reason 420 * so it's called here again on init to make sure all the 421 * calculated styles are correct. 422 */ 423 var self = this; 424 setTimeout(function () { 425 self.resize(); 426 }, 20); 427 428 addEvent(window, "resize", this, false); 429 addEvent(window, "focus", this, false); 430 addEvent(document.body, "touchmove", this, false); 431 addEvent(navToggle, "touchstart", this, false); 432 addEvent(navToggle, "touchend", this, false); 433 addEvent(navToggle, "mouseup", this, false); 434 addEvent(navToggle, "keyup", this, false); 435 addEvent(navToggle, "click", this, false); 436 437 /** 438 * Init callback here 439 */ 440 opts.init(); 441 }, 442 443 /** 444 * Creates Styles to the <head> 445 */ 446 _createStyles: function () { 447 if (!styleElement.parentNode) { 448 styleElement.type = "text/css"; 449 document.getElementsByTagName("head")[0].appendChild(styleElement); 450 } 451 }, 452 453 /** 454 * Removes styles from the <head> 455 */ 456 _removeStyles: function () { 457 if (styleElement.parentNode) { 458 styleElement.parentNode.removeChild(styleElement); 459 } 460 }, 461 462 /** 463 * Creates Navigation Toggle 464 */ 465 _createToggle: function () { 466 467 // If there's no toggle, let's create one 468 if (!opts.customToggle) { 469 var toggle = document.createElement("a"); 470 toggle.innerHTML = opts.label; 471 setAttributes(toggle, { 472 "href": "#", 473 "class": "nav-toggle device-fixed-width" 474 }); 475 476 // Determine where to insert the toggle 477 if (opts.insert === "after") { 478 nav.parentNode.insertBefore(toggle, nav.nextSibling); 479 } else { 480 nav.parentNode.insertBefore(toggle, nav); 481 } 482 483 navToggle = toggle; 484 485 // There is a toggle already, let's use that one 486 } else { 487 var toggleEl = opts.customToggle.replace("#", ""); 488 489 if (document.getElementById(toggleEl)) { 490 navToggle = document.getElementById(toggleEl); 491 } else if (document.querySelector(toggleEl)) { 492 navToggle = document.querySelector(toggleEl); 493 } else { 494 throw new Error("The custom nav toggle you are trying to select doesn't exist"); 495 } 496 } 497 }, 498 499 /** 500 * Closes the navigation when a link inside is clicked. 501 */ 502 _closeOnNavClick: function () { 503 if (opts.closeOnNavClick) { 504 var links = nav.getElementsByTagName("a"), 505 self = this;
506 forEach(links, function (i, el) { 507 addEvent(links[i], "click", function () { 508 if (isMobile) { 509 self.toggle(); 510 } 511 }, false); 512 }); 513 } 514 }, 515 516 /** 517 * Prevents the default functionality. 518 * 519 * @param {event} event 520 */ 521 _preventDefault: function(e) { 522 if (e.preventDefault) { 523 if (e.stopImmediatePropagation) { 524 e.stopImmediatePropagation(); 525 } 526 e.preventDefault(); 527 e.stopPropagation(); 528 return false; 529 530 // This is strictly for old IE 531 } else { 532 e.returnValue = false; 533 } 534 }, 535 536 /** 537 * On touch start we get the location of the touch. 538 * 539 * @param {event} event 540 */ 541 _onTouchStart: function (e) { 542 if (!Event.prototype.stopImmediatePropagation) { 543 this._preventDefault(e); 544 } 545 this.startX = e.touches[0].clientX; 546 this.startY = e.touches[0].clientY; 547 this.touchHasMoved = false; 548 549 /** 550 * Remove mouseup event completely here to avoid 551 * double triggering the event. 552 */ 553 removeEvent(navToggle, "mouseup", this, false); 554 }, 555 556 /** 557 * Check if the user is scrolling instead of tapping. 558 * 559 * @param {event} event 560 */ 561 _onTouchMove: function (e) { 562 if (Math.abs(e.touches[0].clientX - this.startX) > 10 || 563 Math.abs(e.touches[0].clientY - this.startY) > 10) { 564 this.touchHasMoved = true; 565 } 566 }, 567 568 /** 569 * On touch end toggle the navigation. 570 * 571 * @param {event} event 572 */ 573 _onTouchEnd: function (e) { 574 this._preventDefault(e); 575 if (!isMobile) { 576 return; 577 } 578 579 // If the user isn't scrolling 580 if (!this.touchHasMoved) { 581 582 // If the event type is touch 583 if (e.type === "touchend") { 584 this.toggle(); 585 return; 586 587 // Event type was click, not touch 588 } else { 589 var evt = e || window.event; 590 591 // If it isn't a right click, do toggling 592 if (!(evt.which === 3 || evt.button === 2)) { 593 this.toggle(); 594 } 595 } 596 } 597 }, 598 599 /** 600 * For keyboard accessibility, toggle the navigation on Enter 601 * keypress too. 602 * 603 * @param {event} event 604 */ 605 _onKeyUp: function (e) { 606 var evt = e || window.event; 607 if (evt.keyCode === 13) { 608 this.toggle(); 609 } 610 }, 611 612 /** 613 * Adds the needed CSS transitions if animations are enabled 614 */ 615 _transitions: function () { 616 if (opts.animate) { 617 var objStyle = nav.style, 618 transition = "max-height " + opts.transition + "ms"; 619 620 objStyle.WebkitTransition = 621 objStyle.MozTransition = 622 objStyle.OTransition = 623 objStyle.transition = transition; 624 } 625 }, 626 627 /** 628 * Calculates the height of the navigation and then creates 629 * styles which are later added to the page <head> 630 */ 631 _calcHeight: function () { 632 var savedHeight = 0; 633 for (var i = 0; i < nav.inner.length; i++) { 634 savedHeight += nav.inner[i].offsetHeight; 635 } 636 637 var innerStyles = "." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened{max-height:" + savedHeight + "px !important} ." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened.dropdown-active {max-height:9999px !important}"; 638 639 if (styleElement.styleSheet) { 640 styleElement.styleSheet.cssText = innerStyles; 641 } else { 642 styleElement.innerHTML = innerStyles; 643 } 644 645 innerStyles = ""; 646 } 647 648 }; 649 650 /** 651 * Return new Responsive Nav 652 */ 653 return new ResponsiveNav(el, options); 654 655 }; 656 657 window.responsiveNav = responsiveNav; 658 659}(document, window, 0));
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.