1/* 2 * priority-nav - v1.0.12 | (c) 2016
vendor: 7,847 bytes, lines 2-216
2 @gijsroge | MIT license 3 * Repository: https://github.com/gijsroge/priority-navigation.git 4 * Description: Priority+ pattern navigation that hides menu items if they don't fit on screen. 5 * Demo: http://gijsroge.github.io/priority-nav.js/ 6 */ 7(function (root, factory) { 8 if (typeof define === "function" && define.amd) { 9 define("priorityNav", factory(root)); 10 } else if (typeof exports === "object") { 11 module.exports = factory(root); 12 } else { 13 root.priorityNav = factory(root); 14 } 15})(window || this, function (root) { 16 17 "use strict"; 18 19 /** 20 * Variables 21 */ 22 var priorityNav = {}; // Object for public APIs 23 var breaks = []; // Object to store instances with breakpoints where the instances menu item"s didin"t fit. 24 var supports = !!document.querySelector && !!root.addEventListener; // Feature test 25 var settings = {}; 26 var instance = 0; 27 var count = 0; 28 var mainNavWrapper, totalWidth, restWidth, mainNav, navDropdown, navDropdownToggle, dropDownWidth, toggleWrapper; 29 var viewportWidth = 0; 30 31 /** 32 * Default settings 33 * @type {{initClass: string, navDropdown: string, navDropdownToggle: string, mainNavWrapper: string, moved: Function, movedBack: Function}} 34 */ 35 var defaults = { 36 initClass: "js-priorityNav", // Class that will be printed on html element to allow conditional css styling. 37 mainNavWrapper: "nav", // mainnav wrapper selector (must be direct parent from mainNav) 38 mainNav: "ul", // mainnav selector. (must be inline-block) 39 navDropdownClassName: "nav__dropdown", // class used for the dropdown. 40 navDropdownToggleClassName: "nav__dropdown-toggle", // class used for the dropdown toggle. 41 navDropdownLabel: "more", // Text that is used for the dropdown toggle. 42 navDropdownBreakpointLabel: "menu", //button label for navDropdownToggle when the breakPoint is reached. 43 breakPoint: 500, //amount of pixels when all menu items should be moved to dropdown to simulate a mobile menu 44 throttleDelay: 50, // this will throttle the calculating logic on resize because i'm a responsible dev. 45 offsetPixels: 0, // increase to decrease the time it takes to move an item. 46 count: true, // prints the amount of items are moved to the attribute data-count to style with css counter. 47 48 //Callbacks 49 moved: function () { 50 }, 51 movedBack: function () { 52 } 53 }; 54 55 56 /** 57 * A simple forEach() implementation for Arrays, Objects and NodeLists 58 * @private 59 * @param {Array|Object|NodeList} collection Collection of items to iterate 60 * @param {Function} callback Callback function for each iteration 61 * @param {Array|Object|NodeList} scope Object/NodeList/Array that forEach is iterating over (aka `this`) 62 */ 63 var forEach = function (collection, callback, scope) { 64 if (Object.prototype.toString.call(collection) === "[object Object]") { 65 for (var prop in collection) { 66 if (Object.prototype.hasOwnProperty.call(collection, prop)) { 67 callback.call(scope, collection[prop], prop, collection); 68 } 69 } 70 } else { 71 for (var i = 0, len = collection.length; i < len; i++) { 72 callback.call(scope, collection[i], i, collection); 73 } 74 } 75 }; 76 77 78 /** 79 * Get the closest matching element up the DOM tree 80 * @param {Element} elem Starting element 81 * @param {String} selector Selector to match against (class, ID, or data attribute) 82 * @return {Boolean|Element} Returns false if not match found 83 */ 84 var getClosest = function (elem, selector) { 85 var firstChar = selector.charAt(0); 86 for (; elem && elem !== document; elem = elem.parentNode) { 87 if (firstChar === ".") { 88 if (elem.classList.contains(selector.substr(1))) { 89 return elem; 90 } 91 } else if (firstChar === "#") { 92 if (elem.id === selector.substr(1)) { 93 return elem; 94 } 95 } else if (firstChar === "[") { 96 if (elem.hasAttribute(selector.substr(1, selector.length - 2))) { 97 return elem; 98 } 99 } 100 } 101 return false; 102 }; 103 104 105 /** 106 * Merge defaults with user options 107 * @private 108 * @param {Object} defaults Default settings 109 * @param {Object} options User options 110 * @returns {Object} Merged values of defaults and options 111 */ 112 var extend = function (defaults, options) { 113 var extended = {}; 114 forEach(defaults, function (value, prop) { 115 extended[prop] = defaults[prop]; 116 }); 117 forEach(options, function (value, prop) { 118 extended[prop] = options[prop]; 119 }); 120 return extended; 121 }; 122 123 124 /** 125 * Debounced resize to throttle execution 126 * @param func 127 * @param wait 128 * @param immediate 129 * @returns {Function} 130 */ 131 function debounce(func, wait, immediate) { 132 var timeout; 133 return function () { 134 var context = this, args = arguments; 135 var later = function () { 136 timeout = null; 137 if (!immediate) func.apply(context, args); 138 }; 139 var callNow = immediate && !timeout; 140 clearTimeout(timeout); 141 timeout = setTimeout(later, wait); 142 if (callNow) func.apply(context, args); 143 }; 144 } 145 146 147 /** 148 * Toggle class on element 149 * @param el 150 * @param className 151 */ 152 var toggleClass = function (el, className) { 153 if (el.classList) { 154 el.classList.toggle(className); 155 } else { 156 var classes = el.className.split(" "); 157 var existingIndex = classes.indexOf(className); 158 159 if (existingIndex >= 0) 160 classes.splice(existingIndex, 1); else 161 classes.push(className); 162 163 el.className = classes.join(" "); 164 } 165 }; 166 167 168 /** 169 * Check if dropdown menu is already on page before creating it 170 * @param mainNavWrapper 171 */ 172 var prepareHtml = function (_this, settings) { 173 174 /** 175 * Create dropdow menu 176 * @type {HTMLElement} 177 */ 178 toggleWrapper = document.createElement("span"); 179 navDropdown = document.createElement("ul"); 180 navDropdownToggle = document.createElement("button"); 181 182 /** 183 * Set label for dropdown toggle 184 * @type {string} 185 */ 186 navDropdownToggle.innerHTML = settings.navDropdownLabel; 187 188 /** 189 * Set aria attributes for accessibility 190 */ 191 navDropdownToggle.setAttribute("aria-controls", "menu"); 192 navDropdownToggle.setAttribute("type", "button"); 193 navDropdown.setAttribute("aria-hidden", "true"); 194 195 196 /** 197 * Move elements to the right spot 198 */ 199 if(_this.querySelector(mainNav).parentNode !== _this){ 200 console.warn("mainNav is not a direct child of mainNavWrapper, double check please"); 201 return; 202 } 203 204 _this.insertAfter(toggleWrapper, _this.querySelector(mainNav)); 205 206 toggleWrapper.appendChild(navDropdownToggle); 207 toggleWrapper.appendChild(navDropdown); 208 209 /** 210 * Add classes so we can target elements 211 */ 212 navDropdown.classList.add(settings.navDropdownClassName); 213 navDropdown.classList.add("priority-nav__dropdown"); 214 215 navDropdownToggle.classList.add(settings.navDropdownToggleClassName); 216 navDropdownToggle.classList.add("priority-nav__dropdown-toggle");
vendor: 15,301 bytes, lines 217-724
217 218 toggleWrapper.classList.add(settings.navDropdownClassName+"-wrapper"); 219 toggleWrapper.classList.add("priority-nav__wrapper"); 220 221 _this.classList.add("priority-nav"); 222 }; 223 224 225 /** 226 * Get innerwidth without padding 227 * @param element 228 * @returns {number} 229 */ 230 var getElementContentWidth = function(element) { 231 var styles = window.getComputedStyle(element); 232 var padding = parseFloat(styles.paddingLeft) + 233 parseFloat(styles.paddingRight); 234 235 return element.clientWidth - padding; 236 }; 237 238 239 /** 240 * Get viewport size 241 * @returns {{width: number, height: number}} 242 */ 243 var viewportSize = function() { 244 var doc = document, w = window; 245 var docEl = (doc.compatMode && doc.compatMode === "CSS1Compat")? 246 doc.documentElement: doc.body; 247 248 var width = docEl.clientWidth; 249 var height = docEl.clientHeight; 250 251 // mobile zoomed in? 252 if ( w.innerWidth && width > w.innerWidth ) { 253 width = w.innerWidth; 254 height = w.innerHeight; 255 } 256 257 return {width: width, height: height}; 258 }; 259 260 261 /** 262 * Get width 263 * @param elem 264 * @returns {number} 265 */ 266 var calculateWidths = function (_this) { 267 totalWidth = getElementContentWidth(_this); 268 //Check if parent is the navwrapper before calculating its width 269 if (_this.querySelector(navDropdown).parentNode === _this) { 270 dropDownWidth = _this.querySelector(navDropdown).offsetWidth; 271 } else { 272 dropDownWidth = 0; 273 } 274 restWidth = getChildrenWidth(_this) + settings.offsetPixels; 275 viewportWidth = viewportSize().width; 276 }; 277 278 279 /** 280 * Move item to array 281 * @param item 282 */ 283 priorityNav.doesItFit = function (_this) { 284 285 /** 286 * Check if it is the first run 287 */ 288 var delay = _this.getAttribute("instance") === 0 ? delay : settings.throttleDelay; 289 290 /** 291 * Increase instance 292 */ 293 instance++; 294 295 /** 296 * Debounced execution of the main logic 297 */ 298 (debounce(function () { 299 300 /** 301 * Get the current element"s instance 302 * @type {string} 303 */ 304 var identifier = _this.getAttribute("instance"); 305 306 /** 307 * Update width 308 */ 309 calculateWidths(_this); 310 311 /** 312 * Keep executing until all menu items that are overflowing are moved 313 */ 314 while (totalWidth <= restWidth && _this.querySelector(mainNav).children.length > 0 || viewportWidth < settings.breakPoint && _this.querySelector(mainNav).children.length > 0) { 315 //move item to dropdown 316 priorityNav.toDropdown(_this, identifier); 317 //recalculate widths 318 calculateWidths(_this, identifier); 319 //update dropdownToggle label 320 if(viewportWidth < settings.breakPoint) updateLabel(_this, identifier, settings.navDropdownBreakpointLabel); 321 } 322 323 /** 324 * Keep executing until all menu items that are able to move back are moved 325 */ 326 while (totalWidth >= breaks[identifier][breaks[identifier].length - 1] && viewportWidth > settings.breakPoint) { 327 //move item to menu 328 priorityNav.toMenu(_this, identifier); 329 //update dropdownToggle label 330 if(viewportWidth > settings.breakPoint) updateLabel(_this, identifier, settings.navDropdownLabel); 331 } 332 333 /** 334 * If there are no items in dropdown hide dropdown 335 */ 336 if (breaks[identifier].length < 1) { 337 _this.querySelector(navDropdown).classList.remove("show"); 338 //show navDropdownLabel 339 updateLabel(_this, identifier, settings.navDropdownLabel); 340 } 341 342 /** 343 * If there are no items in menu 344 */ 345 if (_this.querySelector(mainNav).children.length < 1) { 346 //show navDropdownBreakpointLabel 347 _this.classList.add("is-empty"); 348 updateLabel(_this, identifier, settings.navDropdownBreakpointLabel); 349 }else{ 350 _this.classList.remove("is-empty"); 351 } 352 353 /** 354 * Check if we need to show toggle menu button 355 */ 356 showToggle(_this, identifier); 357 358 }, delay ))(); 359 }; 360 361 362 /** 363 * Show/hide toggle button 364 */ 365 var showToggle = function (_this, identifier) { 366 if (breaks[identifier].length < 1) { 367 _this.querySelector(navDropdownToggle).classList.add("priority-nav-is-hidden"); 368 _this.querySelector(navDropdownToggle).classList.remove("priority-nav-is-visible"); 369 _this.classList.remove("priority-nav-has-dropdown"); 370 371 /** 372 * Set aria attributes for accessibility 373 */ 374 _this.querySelector(".priority-nav__wrapper").setAttribute("aria-haspopup", "false"); 375 376 } else { 377 _this.querySelector(navDropdownToggle).classList.add("priority-nav-is-visible"); 378 _this.querySelector(navDropdownToggle).classList.remove("priority-nav-is-hidden"); 379 _this.classList.add("priority-nav-has-dropdown"); 380 381 /** 382 * Set aria attributes for accessibility 383 */ 384 _this.querySelector(".priority-nav__wrapper").setAttribute("aria-haspopup", "true"); 385 } 386 }; 387 388 389 /** 390 * Update count on dropdown toggle button 391 */ 392 var updateCount = function (_this, identifier) { 393 _this.querySelector(navDropdownToggle).setAttribute("priorityNav-count", breaks[identifier].length); 394 }; 395 396 var updateLabel = function(_this, identifier, label){ 397 _this.querySelector(navDropdownToggle).innerHTML = label; 398 }; 399 400 401 /** 402 * Move item to dropdown 403 */ 404 priorityNav.toDropdown = function (_this, identifier) { 405 406 407 /** 408 * move last child of navigation menu to dropdown 409 */ 410 if (_this.querySelector(navDropdown).firstChild && _this.querySelector(mainNav).children.length > 0) { 411 _this.querySelector(navDropdown).insertBefore(_this.querySelector(mainNav).lastElementChild, _this.querySelector(navDropdown).firstChild); 412 } else if (_this.querySelector(mainNav).children.length > 0) { 413 _this.querySelector(navDropdown).appendChild(_this.querySelector(mainNav).lastElementChild); 414 } 415 416 /** 417 * store breakpoints 418 */ 419 breaks[identifier].push(restWidth); 420 421 /** 422 * check if we need to show toggle menu button 423 */ 424 showToggle(_this, identifier); 425 426 /** 427 * update count on dropdown toggle button 428 */ 429 if (_this.querySelector(mainNav).children.length > 0 && settings.count) { 430 updateCount(_this, identifier); 431 } 432 433 /** 434 * If item has been moved to dropdown trigger the callback 435 */ 436 settings.moved(); 437 }; 438 439 440 /** 441 * Move item to menu 442 */ 443 priorityNav.toMenu = function (_this, identifier) { 444 445 /** 446 * move last child of navigation menu to dropdown 447 */ 448 if (_this.querySelector(navDropdown).children.length > 0) _this.querySelector(mainNav).appendChild(_this.querySelector(navDropdown).firstElementChild); 449 450 /** 451 * remove last breakpoint 452 */ 453 breaks[identifier].pop(); 454 455 /** 456 * Check if we need to show toggle menu button 457 */ 458 showToggle(_this, identifier); 459 460 /** 461 * update count on dropdown toggle button 462 */ 463 if (_this.querySelector(mainNav).children.length > 0 && settings.count) { 464 updateCount(_this, identifier); 465 } 466 467 /** 468 * If item has been moved back to the main menu trigger the callback 469 */ 470 settings.movedBack(); 471 }; 472 473 474 /** 475 * Count width of children and return the value 476 * @param e 477 */ 478 var getChildrenWidth = function (e) { 479 var children = e.childNodes; 480 var sum = 0; 481 for (var i = 0; i < children.length; i++) { 482 if (children[i].nodeType !== 3) { 483 if(!isNaN(children[i].offsetWidth)){ 484 sum += children[i].offsetWidth; 485 } 486 487 } 488 } 489 return sum; 490 }; 491 492 493 494 /** 495 * Bind eventlisteners 496 */ 497 var listeners = function (_this, settings) { 498 499 // Check if an item needs to move 500 if(window.attachEvent) { 501 window.attachEvent("onresize", function() { 502 if(priorityNav.doesItFit)priorityNav.doesItFit(_this); 503 }); 504 } 505 else if(window.addEventListener) { 506 window.addEventListener("resize", function() { 507 if(priorityNav.doesItFit)priorityNav.doesItFit(_this); 508 }, true); 509 } 510 511 // Toggle dropdown 512 _this.querySelector(navDropdownToggle).addEventListener("click", function () { 513 toggleClass(_this.querySelector(navDropdown), "show"); 514 toggleClass(this, "is-open"); 515 toggleClass(_this, "is-open"); 516 517 /** 518 * Toggle aria hidden for accessibility 519 */ 520 if(-1 !== _this.className.indexOf( "is-open" )){ 521 _this.querySelector(navDropdown).setAttribute("aria-hidden", "false"); 522 }else{ 523 _this.querySelector(navDropdown).setAttribute("aria-hidden", "true"); 524 _this.querySelector(navDropdown).blur(); 525 } 526 }); 527 528 /* 529 * Remove when clicked outside dropdown 530 */ 531 document.addEventListener("click", function (event) { 532 if (!getClosest(event.target, "."+settings.navDropdownClassName) && event.target !== _this.querySelector(navDropdownToggle)) { 533 _this.querySelector(navDropdown).classList.remove("show"); 534 _this.querySelector(navDropdownToggle).classList.remove("is-open"); 535 _this.classList.remove("is-open"); 536 } 537 }); 538 539 /** 540 * Remove when escape key is pressed 541 */ 542 document.onkeydown = function (evt) { 543 evt = evt || window.event; 544 if (evt.keyCode === 27) { 545 document.querySelector(navDropdown).classList.remove("show"); 546 document.querySelector(navDropdownToggle).classList.remove("is-open"); 547 mainNavWrapper.classList.remove("is-open"); 548 } 549 }; 550 }; 551 552 553 /** 554 * Remove function 555 */ 556 Element.prototype.remove = function() { 557 this.parentElement.removeChild(this); 558 }; 559 560 /*global HTMLCollection */ 561 NodeList.prototype.remove = HTMLCollection.prototype.remove = function() { 562 for(var i = 0, len = this.length; i < len; i++) { 563 if(this[i] && this[i].parentElement) { 564 this[i].parentElement.removeChild(this[i]); 565 } 566 } 567 }; 568 569 570 /** 571 * Destroy the current initialization. 572 * @public 573 */ 574 priorityNav.destroy = function () { 575 // If plugin isn"t already initialized, stop 576 if (!settings) return; 577 // Remove feedback class 578 document.documentElement.classList.remove(settings.initClass); 579 // Remove toggle 580 toggleWrapper.remove(); 581 // Remove settings 582 settings = null; 583 delete priorityNav.init; 584 delete priorityNav.doesItFit; 585 }; 586 587 588 /** 589 * insertAfter function 590 * @param n 591 * @param r 592 */ 593 if (supports && typeof Node !== "undefined"){ 594 Node.prototype.insertAfter = function(n,r) {this.insertBefore(n,r.nextSibling);}; 595 } 596 597 var checkForSymbols = function(string){ 598 var firstChar = string.charAt(0); 599 if (firstChar === "." || firstChar === "#") { 600 return false; 601 }else{ 602 return true; 603 } 604 }; 605 606 607 /** 608 * Initialize Plugin 609 * @public 610 * @param {Object} options User settings 611 */ 612 priorityNav.init = function (options) { 613 614 /** 615 * Merge user options with defaults 616 * @type {Object} 617 */ 618 settings = extend(defaults, options || {}); 619 620 // Feature test. 621 if (!supports && typeof Node === "undefined"){ 622 console.warn("This browser doesn't support priorityNav"); 623 return; 624 } 625 626 // Options check 627 if (!checkForSymbols(settings.navDropdownClassName) || !checkForSymbols(settings.navDropdownToggleClassName)){ 628 console.warn("No symbols allowed in navDropdownClassName & navDropdownToggleClassName. These are not selectors."); 629 return; 630 } 631 632 /** 633 * Store nodes 634 * @type {NodeList} 635 */ 636 var elements = document.querySelectorAll(settings.mainNavWrapper); 637 638 /** 639 * Loop over every instance and reference _this 640 */ 641 forEach(elements, function(_this){ 642 643 /** 644 * Create breaks array 645 * @type {number} 646 */ 647 breaks[count] = []; 648 649 /** 650 * Set the instance number as data attribute 651 */ 652 _this.setAttribute("instance", count++); 653 654 /** 655 * Store the wrapper element 656 */ 657 mainNavWrapper = _this; 658 if (!mainNavWrapper) { 659 console.warn("couldn't find the specified mainNavWrapper element"); 660 return; 661 } 662 663 /** 664 * Store the menu elementStore the menu element 665 */ 666 mainNav = settings.mainNav; 667 if (!_this.querySelector(mainNav)) { 668 console.warn("couldn't find the specified mainNav element"); 669 return; 670 } 671 672 /** 673 * Check if we need to create the dropdown elements 674 */ 675 prepareHtml(_this, settings); 676 677 /** 678 * Store the dropdown element 679 */ 680 navDropdown = "."+settings.navDropdownClassName; 681 if (!_this.querySelector(navDropdown)) { 682 console.warn("couldn't find the specified navDropdown element"); 683 return; 684 } 685 686 /** 687 * Store the dropdown toggle element 688 */ 689 navDropdownToggle = "."+settings.navDropdownToggleClassName; 690 if (!_this.querySelector(navDropdownToggle)) { 691 console.warn("couldn't find the specified navDropdownToggle element"); 692 return; 693 } 694 695 /** 696 * Event listeners 697 */ 698 listeners(_this, settings); 699 700 /** 701 * Start first check 702 */ 703 priorityNav.doesItFit(_this); 704 705 }); 706 707 /** 708 * Count amount of instances 709 */ 710 instance++; 711 712 /** 713 * Add class to HTML element to activate conditional CSS 714 */ 715 document.documentElement.classList.add(settings.initClass); 716 }; 717 718 719 /** 720 * Public APIs 721 */ 722 return priorityNav; 723 724});
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.