PageSourceSearch

https://www.aquariumparadise.eu/bower_components/priority-nav/dist/priority-nav.js

js aquariumparadise.eu collected 2026-10-08 17:16:23 UTC 23,187 bytes, 724 lines download raw bytes

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.