PageSourceSearch

https://zakkai.dk/static/editor-loadable-scripts/mobileMenu.js?v=1790255956531

js zakkai.dk collected 2026-09-27 10:08:23 UTC 8,257 bytes, 266 lines download raw bytes

1(() => {
2  // Get common utilities and validate they exist
3  const common = window?.brickSite?.common;
4  if (!common) return;
5
6  const { getBricksContext } = common;
7
8  // Get the top-level mobile menu section
9  const bricks = getBricksContext('.js-brick-mobile-menu');
10  if (!bricks || bricks.length === 0) return;
11
12  /**
13   * Helper function to get DOM elements needed for mobile menu
14   * @returns {Object} Object containing menu elements
15   */
16  const getMenuElements = () => ({
17    sideMenu: common.document.getElementById('sideMenu'),
18    burgerIcon: common.document.getElementById('burgerIcon'),
19    body: common.document.body,
20    headerCta: common.document.getElementById('headerCta'),
21    mobileHeader: document.querySelector('.mobile-header-container'),
22  });
23
24  /**
25   * Helper to find the CTA button in the side menu
26   * @returns {HTMLElement|null} The CTA button element or null if not found
27   */
28  const findCtaButton = () => {
29    const { sideMenu } = getMenuElements();
30    if (!sideMenu) return null;
31
32    // Find CTA by class
33    const ctaButton = sideMenu.querySelector('.cta-button');
34    if (ctaButton) return ctaButton;
35
36    // Fallback: find by button classes
37    const selectors = [
38      'a[class*="b-primary"]',
39      'a[class*="b-secondary"]',
40      'button[class*="b-primary"]',
41      'button[class*="b-secondary"]',
42    ];
43
44    for (const selector of selectors) {
45      const cta = sideMenu.querySelector(selector);
46      if (cta) return cta;
47    }
48
49    return null;
50  };
51
52  /**
53   * Check if there are nav items in the side menu
54   * @returns {boolean} True if nav items exist
55   */
56  const hasNavItems = () => {
57    const { sideMenu } = getMenuElements();
58    if (!sideMenu) return false;
59
60    // Check for nav element first
61    const navElement = sideMenu.querySelector('nav');
62    if (!navElement) return false;
63
64    // Check if nav has any links inside
65    const links = navElement.querySelectorAll('a');
66    return links.length > 0;
67  };
68
69  /**
70   * Handle the CTA display case for when there are no nav items
71   */
72  const handleCtaDisplay = () => {
73    const { sideMenu, mobileHeader, headerCta } = getMenuElements();
74    if (!sideMenu || !mobileHeader) return;
75
76    const ctaButton = findCtaButton();
77    const hasNav = hasNavItems();
78
79    // If we have a CTA but no nav items, we need special handling
80    if (!hasNav && ctaButton) {
81      // Hide the side menu
82      sideMenu.style.display = 'none';
83
84      // Create header CTA if it doesn't exist
85      if (!headerCta && mobileHeader) {
86        // Create a new CTA by cloning the original to preserve all styling and classes
87        const newHeaderCta = ctaButton.cloneNode(true);
88
89        // Set ID while keeping original brick class
90        newHeaderCta.id = 'headerCta';
91
92        // Ensure it has the cta-button class for styling
93        if (!newHeaderCta.classList.contains('cta-button')) {
94          newHeaderCta.classList.add('cta-button');
95        }
96
97        // Add to the mobile header
98        mobileHeader.appendChild(newHeaderCta);
99
100        // Add click event to handle navigation
101        newHeaderCta.addEventListener('click', (e) => {
102          const href = newHeaderCta.getAttribute('href');
103          if (!href || href === '#' || href.includes('undefined')) {
104            e.preventDefault();
105            return; // Don't navigate to invalid URLs
106          }
107        });
108      }
109    } else {
110      // Normal case - show side menu, hide header CTA
111      sideMenu.style.display = '';
112
113      // Remove header CTA if it exists
114      if (headerCta) {
115        headerCta.remove();
116      }
117    }
118  };
119
120  /**
121   * Restores the scroll position after menu closes
122   * @param {HTMLElement} body - The document body element
123   */
124  const restoreScrollPosition = (body) => {
125    const scrollY = body.style.top;
126    body.style.top = '';
127    window.scrollTo(0, Number.parseInt(scrollY || '0', 10) * -1);
128  };
129
130  /**
131   * Handles closing the mobile menu and restoring page scroll
132   * @returns {Promise} Resolves when menu is closed and animation completes
133   */
134  const closeMobileMenu = () => {
135    return new Promise((resolve) => {
136      const { sideMenu, burgerIcon, body } = getMenuElements();
137
138      if (sideMenu?.classList.contains('open')) {
139        // Remove menu-related classes
140        sideMenu.classList.remove('open');
141        burgerIcon?.classList.remove('open');
142        body.classList.remove('no-scroll');
143
144        // Restore scroll position
145        restoreScrollPosition(body);
146
147        // Wait for menu close animation
148        setTimeout(resolve, 300);
149      } else {
150        resolve();
151      }
152    });
153  };
154
155  /**
156   * Handles opening the mobile menu and storing scroll position
157   */
158  const openMobileMenu = () => {
159    const { sideMenu, burgerIcon, body } = getMenuElements();
160
161    sideMenu.classList.add('open');
162    if (burgerIcon) burgerIcon.classList.add('open');
163    body.classList.add('no-scroll');
164    body.style.top = `-${window.scrollY}px`;
165  };
166
167  /**
168   * Handles burger icon click to toggle menu state
169   * @param {Event} event - Click event
170   */
171  const onBurgerClick = (event) => {
172    event.preventDefault();
173    const { sideMenu } = getMenuElements();
174
175    if (sideMenu.classList.contains('open')) {
176      closeMobileMenu();
177    } else {
178      openMobileMenu();
179    }
180  };
181
182  /**
183   * Handles clicks on menu item arrows for submenus
184   * @param {Event} event - Click event
185   */
186  const onMenuItemArrowClick = (event) => {
187    const arrowButton = event.currentTarget;
188    const menuItem = arrowButton.parentNode;
189    const menuBlock = menuItem.parentNode;
190    const menuSubpagesContainer = menuItem.nextSibling;
191    const arrowImg = arrowButton.querySelector('svg');
192    const isOpen = menuBlock.classList.contains('brick-nav-content-item-opened');
193
194    // Toggle submenu height
195    menuSubpagesContainer.style.height = isOpen ? '0px' : `${menuSubpagesContainer.scrollHeight}px`;
196    menuBlock.classList.toggle('brick-nav-content-item-opened');
197    arrowImg.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(180deg)';
198  };
199
200  /**
201   * Handles clicks on navigation links
202   * @param {Event} event - Click event
203   */
204  const onNavLinkClick = async (event) => {
205    try {
206      const href = event.currentTarget.getAttribute('href');
207
208      // Handle same-page anchor links
209      if (href?.includes('#')) {
210        const [pathname, hash] = href.split('#');
211        const isSamePage = pathname === '' || pathname === window.location.pathname;
212
213        if (isSamePage && hash) {
214          event.preventDefault();
215
216          // Close menu first
217          await closeMobileMenu();
218
219          // Scroll to anchor
220          const targetElement = document.getElementById(hash);
221          if (targetElement) {
222            targetElement.scrollIntoView({ behavior: 'smooth' });
223          }
224        }
225      }
226
227      // For other links (different pages), just close the menu
228      // and let default navigation happen
229      await closeMobileMenu();
230    } catch (error) {
231      console.error('Error handling navigation link click:', error);
232      await closeMobileMenu();
233    }
234  };
235
236  // Initialize mobile menu functionality
237  [...bricks].forEach((brickScope) => {
238    // Add click handlers for burger menu buttons
239    const burgerButtons = brickScope.querySelectorAll('#burgerIcon');
240    burgerButtons.forEach((button) => {
241      // Remove any existing listeners before adding new ones
242      const newButton = button.cloneNode(true);
243      button.parentNode.replaceChild(newButton, button);
244      newButton.addEventListener('click', onBurgerClick);
245    });
246
247    // Add click handlers for submenu arrows
248    [...brickScope.querySelectorAll('.brick-nav-mobile-dropdown-button')].forEach((button) => {
249      button.addEventListener('click', onMenuItemArrowClick);
250    });
251
252    // Add click handlers for all navigation links
253    [...brickScope.getElementsByTagName('a')].forEach((link) => {
254      link.addEventListener('click', onNavLinkClick);
255    });
256
257    // Run this on a slight delay to ensure DOM is fully rendered
258    setTimeout(() => {
259      // Handle special case for CTA-only menus
260      handleCtaDisplay();
261    }, 100);
262
263    // Recheck CTA display on window resize
264    window.addEventListener('resize', handleCtaDisplay);
265  });
266})();

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.