PageSourceSearch

https://www.vistra.com/themes/custom/vistra2024/components/paragraphs/mega-menu/mega-menu.js?tlu1y1

js vistra.com collected 2026-09-24 18:12:45 UTC 15,048 bytes, 352 lines download raw bytes

1"use strict";
2
3const secondaryNav = document.getElementById('top-nav-secondary');
4const primaryNav = document.getElementById('top-nav-primary');
5
6if (primaryNav) {
7  const topNavs = document.getElementById('top-navs');
8  const primaryNavBlock = primaryNav.querySelectorAll('.block-colossal-menu > ul > li');
9
10  const mobile = window.matchMedia("(max-width: 1023px)");
11  const toggleNavBtns = document.querySelectorAll('[data-js="toggleNavBtn"]');
12  const eventListeners = [];
13  const createdElements = [];
14  let resizeTimeout;
15
16  let isMobile = mobile.matches;
17
18  let windowWidth = window.innerWidth;
19  window.addEventListener('resize', function(e) {
20    // Only fire event if the width changes, this is needed due to search on mobile
21    // when clicking the search box the keyboard opens
22    // could use interactive-widget=resizes-content but not working in all browsers
23    if (window.innerWidth !== windowWidth) {
24      clearTimeout(resizeTimeout);
25      resizeTimeout = setTimeout(onResize, 200);
26    }
27  });
28
29  function onResize() {
30    isMobile = mobile.matches;
31    closeMenu();
32    removeAllTrackedEventListeners();
33    removeCreatedElements();
34    setupMenu();
35  }
36
37  // Function to add an event listener and store its reference
38  function addEventListenerWithTracking(element, event, handler) {
39    element.addEventListener(event, handler);
40    eventListeners.push({ element, event, handler });
41  }
42
43  // Function to remove all tracked event listeners
44  function removeAllTrackedEventListeners() {
45    eventListeners.forEach(({ element, event, handler }) => {
46      element.removeEventListener(event, handler);
47    });
48    eventListeners.length = 0; // Clear the array
49  }
50
51  function createElementWithTracking(el) {
52    let created = document.createElement(el);
53    createdElements.push({created, el});
54    return created;
55  }
56
57  function removeCreatedElements() {
58    createdElements.forEach(({created, el}) => {
59      created.remove();
60    });
61    createdElements.length = 0; // Clear the array
62  }
63
64  function closeMenu() {
65    document.querySelectorAll('.clicked').forEach((clicked) => {
66      clicked.classList.remove('clicked');
67    });
68    document.querySelectorAll('.selected').forEach((selected) => {
69      selected.classList.remove('selected');
70    });
71    document.querySelectorAll('.grid-show').forEach((gridShow) => {
72      gridShow.classList.remove('grid-show');
73    });
74    document.querySelectorAll('.mobile-show').forEach((mobileShow) => {
75      mobileShow.classList.remove('mobile-show');
76    });
77    document.querySelectorAll('.primary-show').forEach((primaryShow) => {
78      primaryShow.classList.remove('primary-show');
79    });
80
81    // Mobile Menu
82    document.querySelector('html').classList.remove('overflow-hidden');
83    document.querySelectorAll('.cloned').forEach((cloned) => {
84      cloned.remove();
85    });
86
87    const searchToggle = document.querySelector('[data-js="toggleSearch"]');
88    if (searchToggle) {
89      searchToggle.classList.remove('search--mobile-open');
90    }
91
92    // Reset the open/close buttons to their default state when the menu closes
93    const closeBtn = document.querySelector('.button-close');
94    const hamburgerBtn = document.querySelector('.nav-mobile-icon');
95    if (closeBtn) {
96      closeBtn.classList.add('hidden');
97    }
98    if (hamburgerBtn) {
99      hamburgerBtn.classList.remove('hidden');
100    }
101  }
102
103  setupMenu();
104
105  // Primary Menu
106  // ------------------
107  function setupMenu() {
108    if (!mobile.matches && primaryNavBlock) {
109      primaryNavBlock.forEach((item) => {
110        addEventListenerWithTracking(item.querySelector('span'), 'click', function (e) {
111          secondaryNav.querySelectorAll('nav > .nav__secondary > li.dropdown .clicked').forEach((clicked) => {
112            clicked.classList.remove('clicked');
113          });
114          primaryNav.querySelectorAll('.block-colossal-menu > ul > li span.clicked').forEach((clicked) => {
115            if (clicked !== e.target) {
116              clicked.classList.remove('clicked');
117            }
118          });
119
120          e.target.classList.toggle('clicked');
121          item.querySelectorAll('.menu-item-link.selected').forEach((selectedItem) => {
122            selectedItem.classList.remove('selected');
123          });
124
125          if (item.querySelector('.field--name-field-blocks') != null) {
126            let menuBlock = item.querySelector('.field--name-field-blocks');
127            close(menuBlock);
128            menuBlock.classList.toggle('grid-show');
129
130            // When we first select the menu item show the first submenu in the middle column
131            let clonedMenu = item.querySelector('.sub-menu-level-1').cloneNode(true);
132            clonedMenu.classList.add('grid');
133            let appendTo = item.querySelector('.mega-menu-submenu');
134            appendTo.innerHTML = '';
135            appendTo.appendChild(clonedMenu);
136            item.querySelector('.link-media').classList.add('selected');
137
138            let $items = item.querySelectorAll('.menu-level-0 > li > .menu-item-link');
139
140            // On click display the relevent submenu
141            $items.forEach((subItem) => {
142              if (subItem.querySelector('a.card-2030--clickable') != null) {
143                // Disable link so we can use to tab through items
144                addEventListenerWithTracking(subItem.querySelector('a.card-2030--clickable'), 'click', function (e) {
145                  e.preventDefault();
146                });
147
148                // If there is only one link remove styles
149                if ($items.length === 1) {
150                  subItem.querySelector('a.card-2030--clickable').className = "";
151                }
152                else {
153                  addEventListenerWithTracking(subItem, 'click', function (e) {
154                    let itemCloned = subItem.parentElement.querySelector('.sub-menu-level-1').cloneNode(true);
155                    itemCloned.classList.add('grid');
156                    appendTo.innerHTML = '';
157                    appendTo.appendChild(itemCloned);
158                    // Go over them again and remove selecdted
159                    primaryNav.querySelectorAll('.selected').forEach((selected) => {
160                      selected.classList.remove('selected');
161                    });
162                    subItem.classList.add('selected');
163                  });
164                }
165              }
166            });
167          }
168        });
169      });
170
171      // if the mouse leaves from the bottom close the menu
172      document.querySelectorAll('.block-colossal-menu > ul > li .field--name-field-blocks').forEach((item) => {
173        addEventListenerWithTracking(item, 'mouseleave', function (e) {
174          primaryNav.querySelectorAll('.block-colossal-menu > ul > li span.clicked').forEach((clicked) => {
175            if (clicked !== e.target) {
176              clicked.classList.remove('clicked');
177            }
178          });
179          if (e.target.classList.contains('grid-show')) {
180            const rect = item.getBoundingClientRect();
181            const mouseY = e.clientY;
182            if (Math.round(mouseY) >= Math.round(rect.bottom)) {
183              e.target.classList.remove('grid-show');
184            }
185          }
186        });
187      });
188    }
189    // Mobile JS
190    else {
191
192      // Loop through both buttons so clicking either triggers the logic, and toggle visibility
193      toggleNavBtns.forEach((btn) => {
194        addEventListenerWithTracking(btn, 'click', function(e) {
195          e.preventDefault();
196          primaryNav.classList.toggle('primary-show');
197          topNavs.classList.toggle('mobile-show');
198          document.querySelector('html').classList.toggle('overflow-hidden');
199
200          const searchToggle = document.querySelector('[data-js="toggleSearch"]');
201          if (searchToggle) {
202            searchToggle.classList.add('search--mobile-open');
203          }
204
205          // Swap the visibility of the hamburger and the cross
206          document.querySelector('.nav-mobile-icon').classList.toggle('hidden');
207          document.querySelector('.button-close').classList.toggle('hidden');
208        });
209      });
210
211      primaryNav.querySelectorAll('.block-colossal-menu > ul > li').forEach((item) => {
212        if (item.querySelector('.field--name-field-blocks') != null) {
213
214          let menu = item.querySelector('.field--name-field-blocks');
215          let back = createElementWithTracking('div');
216          back.classList.add('back-link');
217          back.innerHTML = '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" transform="scale(-1 1)" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" fill="none" xml:space="preserve"><path d="M9,3L8.3,3.7l3.8,3.8H2v1h10.1l-3.8,3.8L9,13l5-5L9,3z" fill="currentColor"></path></svg> ' + item.querySelector('span').innerHTML;
218          menu.prepend(back);
219          addEventListenerWithTracking(back, 'click', function() {
220            menu.classList.toggle('mobile-show');
221          });
222
223          // subItems is level 0
224          // eg sections for services such as corp, fund, private client solutions
225          let subItems = item.querySelectorAll('.menu-level-0 > li > .menu-item-link');
226
227          // If there is only one link here eg about us we want to select the level 1 menu straight away
228          if (subItems.length > 1) {
229            addEventListenerWithTracking(item.querySelector('span'), 'click', function (e) {
230              menu.classList.toggle('mobile-show');
231            });
232
233            subItems.forEach((subItem) => {
234
235              // Disable link so we can use to tab through items
236              addEventListenerWithTracking(subItem.querySelector('a.card-2030--clickable'), 'click', function (e) {
237                e.preventDefault();
238              });
239
240              let subMenu = subItem.parentElement.querySelector('.sub-menu-level-1');
241              let back = createElementWithTracking('div');
242              back.classList.add('back-link');
243              back.innerHTML = '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" transform="scale(-1 1)" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" fill="none" xml:space="preserve"><path d="M9,3L8.3,3.7l3.8,3.8H2v1h10.1l-3.8,3.8L9,13l5-5L9,3z" fill="currentColor"></path></svg> ' + subItem.querySelector('.card__heading').innerHTML;
244              subMenu.prepend(back);
245              // Clone featured content to each pane
246              let nodetoclone;
247              if (nodetoclone = item.querySelector('.paragraph--type--mega-menu-ds-col1')) {
248                let featured = nodetoclone.cloneNode(true);
249                // make it easy to tidy up on resize
250                featured.classList.add('cloned');
251                subMenu.appendChild(featured);
252              }
253
254              addEventListenerWithTracking(back, 'click', function() {
255                subMenu.classList.toggle('mobile-show');
256              });
257
258              addEventListenerWithTracking(subItem, 'click', function (e) {
259                subMenu.classList.toggle('mobile-show');
260              });
261            });
262          }
263          else {
264            // Should only be 1 item but we have to loop due to queryselectorAll
265            subItems.forEach((subItem) => {
266
267              let subMenu = subItem.parentElement.querySelector('.sub-menu-level-1');
268              let back = createElementWithTracking('div');
269              back.classList.add('back-link');
270              back.innerHTML = '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" transform="scale(-1 1)" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" fill="none" xml:space="preserve"><path d="M9,3L8.3,3.7l3.8,3.8H2v1h10.1l-3.8,3.8L9,13l5-5L9,3z" fill="currentColor"></path></svg> ' + subMenu.querySelector('.card__heading').innerHTML;
271              subMenu.prepend(back);
272              // Clone featued content to each pane
273              let nodetoclone;
274              if (nodetoclone = item.querySelector('.paragraph--type--mega-menu-ds-col1')) {
275                let featured = nodetoclone.cloneNode(true);
276                // make it easy to tidy up on resize
277                featured.classList.add('cloned');
278                subMenu.appendChild(featured);
279              }
280
281              addEventListenerWithTracking(back, 'click', function() {
282                menu.classList.toggle('mobile-show');
283                subMenu.classList.toggle('mobile-show');
284              });
285              addEventListenerWithTracking(item.querySelector('span'), 'click', function (e) {
286                menu.classList.toggle('mobile-show');
287                subMenu.classList.toggle('mobile-show');
288              });
289            });
290          }
291        }
292      });
293    }
294
295    // Secondary Menu
296    // ---------------------------------
297
298    // Normal Dropdown
299    if (!mobile.matches) {
300      secondaryNav.querySelectorAll('nav > .nav__secondary > li.dropdown').forEach((item) => {
301        addEventListenerWithTracking(item, 'click', function (e) {
302          primaryNav.querySelectorAll('.block-colossal-menu > ul > li span.clicked').forEach((clicked) => {
303            clicked.classList.remove('clicked');
304          });
305          secondaryNav.querySelectorAll('nav > .nav__secondary > li.dropdown .clicked').forEach((clicked) => {
306            if (clicked !== e.target) {
307              clicked.classList.remove('clicked');
308            }
309          });
310          e.target.classList.toggle('clicked');
311
312          close(item.querySelector('.site-menu'));
313          item.querySelector('.site-menu').classList.toggle('grid-show');
314        });
315        addEventListenerWithTracking(item.querySelector('.nav__secondary-link'), 'click', function (e) {
316          e.preventDefault();
317        });
318      });
319
320      // Show the search box
321      secondaryNav.querySelectorAll('nav > .nav__secondary > li.link-search').forEach((item) => {
322        addEventListenerWithTracking(item, 'click', function (e) {
323          primaryNav.querySelectorAll('.block-colossal-menu > ul > li span.clicked').forEach((clicked) => {
324            clicked.classList.remove('clicked');
325          });
326          secondaryNav.querySelectorAll('nav > .nav__secondary > li.dropdown .clicked').forEach((clicked) => {
327            clicked.classList.remove('clicked');
328          });
329          close(document.querySelector('[data-js="toggleSearch"]'));
330          document.querySelector('[data-js="toggleSearch"]').classList.toggle('grid-show');
331        });
332        addEventListenerWithTracking(item.querySelector('.nav__secondary-link'), 'click', function (e) {
333          e.preventDefault();
334        });
335      });
336    }
337  }
338
339  // Close everything else on click
340  function close(item) {
341    if (!item) {
342      return;
343    }
344    item.classList.add('clicked');
345    document.querySelectorAll('.grid-show').forEach((all) => {
346      if (all.classList.contains('clicked') === false) {
347        all.classList.remove('grid-show');
348      }
349    });
350    item.classList.remove('clicked');
351  }
352}

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.