PageSourceSearch

https://www.pacourts.us/Themes/AOPC/Vendor/navTree/NavTree.js?cache=639256579353445156

js pacourts.us collected 2026-09-24 08:44:41 UTC 24,353 bytes, 694 lines download raw bytes

1NavTreeItem = (function ($) {
2
3    function NavTreeItem(navTree, parentItem, linkElem, isVertical, expandOnHover, isMobileDevice) {
4        this._initialize(navTree, parentItem, linkElem, isVertical, expandOnHover, isMobileDevice);
5        // After item is initialized, add item to parent's list of children
6        if (this.hasParent()) {
7            this.getParentItem().addChild(this);
8        }
9    }
10
11    // Constants
12    // ----------
13
14    NavTreeItem.keyCodes = Object.freeze({
15        ARROW_DOWN:  40,
16        ARROW_LEFT:  37,
17        ARROW_RIGHT: 39,
18        ARROW_UP:    38,
19        END:         35,
20        HOME:        36
21    });
22
23    NavTreeItem.actions = Object.freeze({
24        MOVE_NEXT: 1,
25        MOVE_PREV: 2,
26        STEP_INTO: 3,
27        STEP_OUT:  4,
28        TO_END:    5,
29        TO_START:  6
30    });
31
32    NavTreeItem.prototype = {
33
34        // Properties
35        // ----------
36
37        _children:      [],
38        _hasChildren:   false,
39        _hasParent:     false,
40        _isCurrentPath: false,
41        _isVertical:    false,
42        _isVisible:     false,
43        _navLevel:      0,
44        _navTree:       null,
45        _parentItem:    null,
46        _$linkElem:     null,
47
48
49        // Initialization
50        // --------------
51
52        _initialize: function (navTree, parentItem, linkElem, isVertical, expandTreeOnHover, isMobileDevice) {
53            var hasExpanderIcon = false,
54                $parentListItem;
55
56            // Initalize settings
57            this._navTree = navTree;
58            this._parentItem = parentItem;
59            this._$linkElem = $(linkElem);
60            this._isVertical = (isVertical === true);
61
62            if (this._parentItem) {
63                this._hasParent = true;
64            }
65            this.setTabIndex(-1);
66
67            // Check the class on the parent list item to determine the nav level
68            $parentListItem = this._$linkElem.parent();
69            if ($parentListItem.hasClass('parent')) {
70                this._navLevel = 1;
71            } else if ($parentListItem.hasClass('child')) {
72                this._navLevel = 2;
73            } else {
74                this._navLevel = 3;
75            }
76
77            // Check the class on the link element to determine if this nav item is on the current page path
78            if (this._$linkElem.hasClass('current')) {
79                this._isCurrentPath = true;
80            }
81
82            // If hover expansion is disabled and this list item has a child list, add an expander element
83            if (!expandTreeOnHover) {
84                hasExpanderIcon = ($parentListItem.children('ul').length > 0);
85                if (hasExpanderIcon) {
86                    $parentListItem.prepend('<span class="expander"></span>');
87                } else {
88                    $parentListItem.addClass('no-children');
89                }
90            }
91
92            // Set up event handlers
93            this._$linkElem.on('keydown', $.proxy(this._handleLinkKeyDown, this));
94            if (expandTreeOnHover && !isMobileDevice) {
95                this._$linkElem.on('mouseenter', $.proxy(this._handleLinkMouseEnter, this));
96            }
97            if (hasExpanderIcon) {
98                this._$linkElem.on('click', $.proxy(this._handleLinkClick, this));
99                $parentListItem.children('.expander').on('click', $.proxy(this._handleExpanderClick, this));
100            }
101        },
102
103
104        // Public Methods
105        // ---------------
106
107        addChild: function (childItem) {
108            if (childItem) {
109                this._children.push(childItem);
110                this._hasChildren = true;
111            }
112        },
113
114        blur: function () {
115            this._$linkElem.blur();
116        },
117
118        collapse: function () {
119            this._$linkElem.attr('aria-expanded', 'false');
120            this._$linkElem.next().css('display', 'none');
121            this._$linkElem.parent().removeClass('active');
122        },
123
124        collapseChildren: function () {
125            var length;
126            if (this._hasChildren) {
127                length = this._children.length;
128                for (var i = 0; i < length; i += 1) {
129                    this._children[i].collapse();
130                }
131            }
132        },
133
134        expand: function () {
135            this._$linkElem.attr('aria-expanded', 'true');
136            this._$linkElem.next('ul').css('display', 'block');
137            this._$linkElem.parent().addClass('active');
138        },
139
140        focus: function () {
141            this._$linkElem.focus();
142        },
143
144        getNavLevel: function () {
145            return this._navLevel;
146        },
147
148        getParentItem: function () {
149            return this._parentItem;
150        },
151
152        hasParent: function () {
153            return this._hasParent;
154        },
155
156        isCurrentPath: function () {
157            return this._isCurrentPath;
158        },
159
160        isExpandable: function () {
161            return this._hasChildren;
162        },
163
164        isExpanded: function () {
165            return (this.isExpandable() && this._$linkElem.attr('aria-expanded') === 'true');
166        },
167
168        isVisible: function () {
169            return this._isVisible;
170        },
171
172        setTabIndex: function (tabIndex) {
173            this._$linkElem.attr('tabIndex', tabIndex.toString());
174        },
175
176        updateVisibilityFromParent: function () {
177            var parent;
178            if (this.hasParent()) {
179                // Item is visible if its parent is both visible and expanded
180                parent = this.getParentItem();
181                this._isVisible = (parent.isVisible() && parent.isExpanded());
182            } else {
183                // Top level items are always visible
184                this._isVisible = true;
185            }
186        },
187
188
189        // Private Methods
190        // ---------------
191
192        _executeAction: function (actionCode) {
193            var actionWasHandled = false;
194
195            switch (actionCode) {
196
197                case NavTreeItem.actions.MOVE_NEXT:
198                    this._navTree.focusNextItem(this);
199                    actionWasHandled = true;
200                    break;
201
202                case NavTreeItem.actions.MOVE_PREV:
203                    this._navTree.focusPreviousItem(this);
204                    actionWasHandled = true;
205                    break;
206
207                case NavTreeItem.actions.STEP_INTO:
208                    if (this.isExpandable()) {
209                        if (this.isExpanded()) {
210                            this._navTree.focusNextItem(this);
211                        } else {
212                            this._navTree.expandItem(this);
213                        }
214                    }
215                    actionWasHandled = true;
216                    break;
217
218                case NavTreeItem.actions.STEP_OUT:
219                    if (this.isExpandable() && this.isExpanded()) {
220                        this._navTree.collapseItem(this);
221                        actionWasHandled = true;
222                    } else if (this.hasParent()) {
223                        this._navTree.focusParentItem(this);
224                        actionWasHandled = true;
225                    }
226                    break;
227
228                case NavTreeItem.actions.TO_START:
229                    this._navTree.focusFirstItem();
230                    actionWasHandled = true;
231                    break;
232
233                case NavTreeItem.actions.TO_END:
234                    this._navTree.focusLastItem();
235                    actionWasHandled = true;
236                    break;
237            }
238            return actionWasHandled;
239        },
240
241        _executeKeyAction: function (keyCode) {
242            var actionCode = null,
243                keyWasHandled = false;
244
245            switch (keyCode) {
246
247                case NavTreeItem.keyCodes.ARROW_DOWN:
248                    if (this._isVertical) {
249                        actionCode = NavTreeItem.actions.MOVE_NEXT;
250                    } else {
251                        actionCode = NavTreeItem.actions.STEP_INTO;
252                    }
253                    break;
254
255                case NavTreeItem.keyCodes.ARROW_LEFT:
256                    if (this._isVertical) {
257                        actionCode = NavTreeItem.actions.STEP_OUT;
258                    } else {
259                        actionCode = NavTreeItem.actions.MOVE_PREV;
260                    }
261                    break;
262
263                case NavTreeItem.keyCodes.ARROW_RIGHT:
264                    if (this._isVertical) {
265                        actionCode = NavTreeItem.actions.STEP_INTO;
266                    } else {
267                        actionCode = NavTreeItem.actions.MOVE_NEXT;
268                    }
269                    break;
270
271                case NavTreeItem.keyCodes.ARROW_UP:
272                    if (this._isVertical) {
273                        actionCode = NavTreeItem.actions.MOVE_PREV;
274                    } else {
275                        actionCode = NavTreeItem.actions.STEP_OUT;
276                    }
277                    break;
278
279                case NavTreeItem.keyCodes.END:
280                    actionCode = NavTreeItem.actions.TO_END;
281                    break;
282
283                case NavTreeItem.keyCodes.HOME:
284                    actionCode = NavTreeItem.actions.TO_START;
285                    break;
286            }
287
288            // If the key is mapped to an action, execute the action
289            if (actionCode) {
290                keyWasHandled = this._executeAction(actionCode);
291            }
292
293            // Return whether the action was actually handled
294            return keyWasHandled;
295        },
296
297        // Event Handlers
298        // --------------
299
300        _handleExpanderClick: function (e) {
301            if (this.isExpanded()) {
302                this._navTree.collapseItem(this);
303            } else if (this.isExpandable()) {
304                this._navTree.expandItem(this);
305            }
306        },
307
308        _handleLinkClick: function (e) {
309            if (this.isExpandable() && !this.isExpanded()) {
310                e.preventDefault();
311                this._navTree.expandItem(this);
312            }
313        },
314
315        _handleLinkKeyDown: function (e) {
316            if (e.altKey || e.ctrlKey || e.metaKey) {
317                return;
318            }
319            if (this._executeKeyAction(e.keyCode)) {
320                e.stopPropagation();
321                e.preventDefault();
322            }
323        },
324
325        _handleLinkMouseEnter: function (e) {
326            this._navTree.unfocusNavItems(this._navLevel);
327            if (this.isExpandable() && !this.isExpanded()) {
328                this._navTree.collapseSiblings(this);
329                this._navTree.expandItem(this);
330            }
331            e.stopPropagation();
332            e.preventDefault();
333        }
334    };
335
336    NavTreeItem.prototype.constructor = NavTreeItem;
337    NavTreeItem.prototype.parent = Object.prototype;
338
339    return NavTreeItem;
340})(jQuery);
341
342NavTree = (function ($) {
343
344    function NavTree() {
345        this._initialize();
346    }
347
348    NavTree.prototype = {
349
350        // Properties
351        // ----------
352
353        _allowMultiOpenSiblings: false,
354        _currentPathItems:       [],
355        _expandTreeOnHover:      true,
356        _firstVisibleItem:       null,
357        _isMobile:               false,
358        _isMobileDevice:         false,
359        _isVertical:             false,
360        _lastVisibleItem:        null,
361        _navItems:               [],
362        _numNavItems:            0,
363        _openToInitialPage:      true,
364        _topLevelItems:          [],
365        _$container:             null,
366        _$navElement:            null,
367        _$navMainList:           null,
368        _$navParentItems:        null,
369
370
371        // Initialization
372        // --------------
373
374        _initialize: function () {
375            var isMobile = (navigator.userAgent.match(/(iPhone|iPod|Android|BlackBerry)/) !== null),
376                isiPad = (navigator.userAgent.match(/(iPad)/) !== null),
377                isAppleDevice = (navigator.userAgent.match(/(iPhone|iPod|iPad)/) !== null),
378                isAndroidDevice = (navigator.userAgent.match(/(Android)/) !== null),
379                isMobileDevice = (isMobile || isiPad || isAppleDevice || isAndroidDevice);
380            // Init settings
381            this._isMobile               = isMobile === true || isiPad === true;
382            this._isMobileDevice         = isMobileDevice
383            this._isVertical             = this._isMobile;
384            this._allowMultiOpenSiblings = this._isMobile;
385            this._openToInitialPage      = !this._isMobile;
386            this._expandTreeOnHover      = !this._isMobile;
387
388            // Cache DOM elements
389            this._$container      = $('.header-nav');
390            this._$navElement     = this._isMobile ? this._$container.find('#menu-mobile') : this._$container.find('#menu-desktop');
391            this._$navMainList    = this._$navElement.children('ul[role="menu"]');
392            this._$navParentItems = this._$navMainList.find('li.parent');
393
394            if (!this._$navMainList.length) {
395                return false;
396            }
397
398            // Set the ARIA Orientation
399            if (this._isVertical) {
400                this._$navMainList.attr('aria-orientation', 'vertical');
401            } else {
402                this._$navMainList.attr('aria-orientation', 'horizontal');
403            }
404
405            // Initialize the tree structure
406            this._loadNavTreeItems(this, null, this._$navMainList[0]);
407            this._updateNavItemsVisibility();
408            this._firstVisibleItem.setTabIndex(0);
409
410            if (this._openToInitialPage) {
411                // Open the navigation to show the current page
412                this._expandNavToCurrentPage();
413            }
414        },
415
416
417        // Public Methods
418        // ---------------
419
420        collapseItem: function (focusedItem) {
421            var navLevel;
422            if (focusedItem.isExpanded()) {
423                // Collapse the currently focused item
424                navLevel = focusedItem.getNavLevel();
425                focusedItem.collapse();
426                this._animateContainerHeight(navLevel);
427                this._updateNavItemsVisibility();
428            }
429        },
430
431        collapseSiblings: function (focusedItem) {
432            if (focusedItem.hasParent()) {
433                var p = focusedItem.getParentItem();
434                focusedItem.getParentItem().collapseChildren();
435            } else {
436                for (var i = 0; i < this._topLevelItems.length; i += 1) {
437                    this._topLevelItems[i].collapse();
438                }
439            }
440        },
441
442        expandItem: function (focusedItem) {
443            var navLevel,
444                currentPathLeaf;
445            if (focusedItem.isExpandable()) {
446                if (!this._allowMultiOpenSiblings) {
447                    // First collapse any open sibling items
448                    this.collapseSiblings(focusedItem);
449                }
450                if(navLevel === undefined) {
451                    navLevel = (focusedItem.getNavLevel() + 1);
452                }
453
454                this._animateContainerHeight(navLevel, $.proxy(function () {
455                    focusedItem.expand();
456                    this._updateNavItemsVisibility();
457                }, this));
458            }
459        },
460
461        focusFirstItem: function () {
462            this._focusItem(this._firstVisibleItem);
463        },
464
465        focusLastItem: function () {
466            this._focusItem(this._lastVisibleItem);
467        },
468
469        focusNextItem: function (focusedItem) {
470            var item = null,
471                itemToFocus = null;
472
473            // Loop backwards through visible items up until the current focused item is reached
474            for (var i = (this._numNavItems - 1) ; i >= 0; i-=1) {
475                item = this._navItems[i];
476                if (item === focusedItem) {
477                    break;
478                }
479                if (item.isVisible()) {
480                    itemToFocus = item;
481                }
482            }
483            // If a next item was found, focus it
484            if (itemToFocus) {
485                this._focusItem(itemToFocus);
486            }
487        },
488
489        focusParentItem: function (focusedItem) {
490            if (focusedItem.hasParent()) {
491                this._focusItem(focusedItem.getParentItem());
492            }
493        },
494
495        focusPreviousItem: function (focusedItem) {
496            var item = null,
497                itemToFocus = null;
498
499            // Loop foward through visible items up until the current focused item is reached
500            for (var i = 0; i < this._numNavItems; i += 1) {
501                item = this._navItems[i];
502                if (item === focusedItem) {
503                    break;
504                }
505                if (item.isVisible()) {
506                    itemToFocus = item;
507                }
508            }
509            // If a previous item was found, focus it
510            if (itemToFocus) {
511                this._focusItem(itemToFocus);
512            }
513        },
514
515        unfocusNavItems: function (navLevel) {
516            var item;
517            for (var i = 0; i < this._numNavItems; i += 1) {
518                item = this._navItems[i];
519                if (item._navLevel >= navLevel) {
520                    this.collapseItem(item);
521                }
522                this._navItems[i].blur();
523            }
524        },
525
526
527        // Private Methods
528        // ---------------
529
530        _animateContainerHeight: function (navLevel, animateCompleteCallbackFunc) {
531            var duration = 250,
532                height = this._getNavPixelHeight(navLevel);
533
534            // Stop any current animations
535            this._$container.stop();
536            // If the height already matches the desired height, make the duration shorter
537            // Note: duration should not be set to zero because the brief flash between displaying helps visually with the transition
538            if (this._$container.height() === height) {
539                duration = 50;
540            }
541            // Do the animation
542            this._$container.animate({ 'height': height.toString() }, duration, animateCompleteCallbackFunc);
543        },
544
545        _expandNavToCurrentPage: function (collapseAllFirst) {
546            var item,
547                navLevel = 1;
548
549            // Collapse all nav items on init
550            if (collapseAllFirst) {
551                for (var i = 0; i < this._numNavItems; i += 1) {
552                    item = this._navItems[i];
553                    if (item.isExpanded()) {
554                        item.collapse();
555                    }
556                }
557            }
558
559            if (this._isMobile) {
560                this._animateContainerHeight(navLevel, $.proxy(function () {
561                    for (var i = 0; i < this._numNavItems; i += 1) {
562                        // This item is the page we're on
563                        item = this._navItems[i];
564                        if (item.isExpandable()) {
565                            item.expand();
566                        } else {
567                            // focus should be on current page not first item in the tree
568                            this._firstVisibleItem.setTabIndex(-1);
569                            item.setTabIndex(0);
570                        }
571                    }
572                    this._updateNavItemsVisibility();
573                }, this));
574            } else {
575                // Animate the nav and then show the items upon completion
576                this._animateContainerHeight(navLevel, $.proxy(function () {
577                    this._firstVisibleItem.setTabIndex(-1);
578                    item.setTabIndex(0);
579                    this._updateNavItemsVisibility();
580                }, this));
581            }
582        },
583
584        _focusItem: function (navItem) {
585            var item;
586            for (var i = 0; i < this._numNavItems; i += 1) {
587                item = this._navItems[i];
588                if (item === navItem) {
589                    item.setTabIndex(0);
590                    item.focus();
591                } else {
592                    item.setTabIndex(-1);
593                }
594            }
595        },
596
597        _getCurrentPathLeaf: function (navItem) {
598            // navItems are stored in a flat structure
599            for (var i = this._numNavItems - 1; i >= 0; i--) {
600                if (this._navItems[i].isCurrentPath()) {
601                    return this._navItems[i];
602                }
603            }
604
605            return false;
606        },
607
608        _getNavPixelHeight: function (navDisplayLevel) {
609            if (navDisplayLevel === 1) { return 57; }
610            if (navDisplayLevel === 2) { return 111; }
611            return 169;
612        },
613
614        _loadNavTreeItems: function (navTree, parentItem, listElem) {
615            var childListElems,
616                listItemElem,
617                navItem;
618
619            // Iterate through the list items in the current list
620            listItemElem = listElem.firstElementChild;
621            while (listItemElem) {
622                // Create a NavTreeItem based on the link tag inside the current list item
623                navItem = new NavTreeItem(navTree, parentItem, listItemElem.firstElementChild, this._isVertical, this._expandTreeOnHover, this._isMobileDevice);
624                this._navItems.push(navItem);
625                if (!navItem.hasParent()) {
626                    this._topLevelItems.push(navItem);
627                }
628                if (navItem.isCurrentPath()) {
629                    this._currentPathItems.push(navItem);
630                }
631                // If the list item contains a child list, recursively load its items
632                childListElems = listItemElem.getElementsByTagName('ul');
633                if (childListElems.length) {
634                    this._loadNavTreeItems(navTree, navItem, childListElems[0]);
635                }
636                // Move to the next sibling in the list
637                listItemElem = listItemElem.nextElementSibling;
638            }
639            this._numNavItems = this._navItems.length;
640        },
641
642        _toggleMobileNavItem: function($elem, active) {
643            if (active) {
644                $elem.addClass('active');
645                $elem.siblings().addClass('inactive');
646            } else {
647                $elem.removeClass('active');
648                $elem.siblings().removeClass('inactive');
649            }
650        },
651
652        _updateNavItemsVisibility: function () {
653            this._firstVisibleItem = this._navItems[0];
654
655            for (var i = 0; i < this._numNavItems; i += 1) {
656                var item = this._navItems[i];
657                item.updateVisibilityFromParent();
658                if (item.isVisible()) {
659                    this._lastVisibleItem = item;
660                }
661            }
662        },
663
664
665        // Event Handlers
666        // --------------
667
668        _handleMobileNavExpanderClick: function (e) {
669            var $expander = $(e.currentTarget);
670            if ($expander.parent().hasClass('active')) {
671                this._toggleMobileNavItem($expander.parent(), false);
672            } else {
673                this._toggleMobileNavItem($expander.parent(), true);
674            }
675        },
676
677        _handleMobileNavLinkClick: function (e) {
678            var $link = $(e.currentTarget);
679            if (!$link.parent().hasClass('active')) {
680                e.preventDefault();
681                this._toggleMobileNavItem($link.parent(), true);
682            }
683        },
684
685        _handleNavContainerMouseLeave: function () {
686            this._expandNavToCurrentPage(true);
687        }
688    };
689
690    NavTree.prototype.constructor = NavTree;
691    NavTree.prototype.parent = Object.prototype;
692
693    return NavTree;
694})(jQuery);

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.