PageSourceSearch

https://edinburghpentlands.church/wp-content/themes/eppc/assets/js…?v=c98a16fe9358abc80ef138a999be5b66dccc5247

js edinburghpentlands.church collected 2026-10-09 05:16:39 UTC 24,860 bytes, 809 lines download raw bytes

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