PageSourceSearch

https://www.researchcommons.org/assets/scripts/dc-mobile/dc-responsive-nav.js

js researchcommons.org collected 2026-10-01 12:09:14 UTC 19,422 bytes, 659 lines download raw bytes

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.