priority-nav 1.0.12 → 1.0.14

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