PageSourceSearch

https://almerecity.nl/wp-content/themes/Impreza/common/js/elements/menu.js?ver=8.46.1

js almerecity.nl collected 2026-10-02 11:43:32 UTC 15,906 bytes, 519 lines download raw bytes

1/**
2 * Header navigation with all the possible states
3 *
4 * @requires $us.canvas
5 */
6! function( $, _undefined ) {
7	"use strict";
8
9	window.$us = window.$us || {};
10
11	$us.mobileNavOpened = 0;
12
13	const SLIDE_DURATION = 250;
14
15	function usNav( container ) {
16		const self = this;
17
18		self.$container = $( container );
19		if ( self.$container.length === 0 ) {
20			return;
21		}
22
23		// Elements
24		self.$items = $( '.menu-item', self.$container );
25		self.$list = $( '.w-nav-list.level_1', self.$container );
26		self.$anchors = $( '.w-nav-anchor', self.$container );
27		self.$arrows = $( '.w-nav-arrow', self.$container );
28		self.$mobileMenu = $( '.w-nav-control', self.$container );
29		self.$itemsHasChildren = $( '.menu-item-has-children', self.$list );
30		self.$childLists = $( '.menu-item-has-children > .w-nav-list', self.$list );
31		self.$reusableBlocksLinks = $( '.menu-item-object-us_page_block a', self.$container );
32
33		// Private "Variables"
34		self.type = self.$container.usMod( 'type' );
35		self.layout = self.$container.usMod( 'layout' );
36		self.openDropdownOnClick = self.$container.hasClass( 'open_on_click' );
37		self.mobileNavOpened = false;
38		self.keyboardNavEvent = false;
39		self.opts = {};
40
41		// Bindable events
42		self._events = {
43			closeMobileMenu: self.closeMobileMenu.bind( self ),
44			closeMobileMenuOnClick: self.closeMobileMenuOnClick.bind( self ),
45			closeMobileMenuOnFocusIn: self.closeMobileMenuOnFocusIn.bind( self ),
46			closeOnClickOutside: self.closeOnClickOutside.bind( self ),
47			handleKeyboardNav: self.handleKeyboardNav.bind( self ),
48			handleMobileClick: self.handleMobileClick.bind( self ),
49			resize: self.resize.bind( self ),
50			toggleMenuOnClick: self.toggleMenuOnClick.bind( self ),
51			toggleMobileMenu: self.toggleMobileMenu.bind( self ),
52		}
53
54		// Load nav options
55		const $opts = $( '.w-nav-options:first', self.$container );
56		if ( $opts.is( '[onclick]' ) ) {
57			self.opts = $opts[0].onclick() || {};
58			$opts.remove();
59		}
60
61		// Events
62		self.$container
63			// Detach animation after transition
64			.on( 'transitionend', () => self.$container.removeClass( 'us_animate_this' ) )
65			// Keyboard navigation accessibility
66			.on( 'keydown.upsolution', self._events.handleKeyboardNav )
67			.on( 'click', '.w-nav-close', self._events.closeMobileMenu )
68			.on( 'click', '.w-nav-anchor, .w-hwrapper-link', self._events.closeMobileMenuOnClick )
69			.on( 'click', '.w-nav-control', self._events.toggleMobileMenu )
70			.on( 'closeMobileMenu', self._events.closeMobileMenu );
71
72		// Open dropdown on click
73		if ( self.openDropdownOnClick ) {
74			self.$container.on( 'click', '.menu-item.togglable > .w-nav-anchor', self._events.toggleMenuOnClick );
75
76			// Close on outside menu click
77			$us.$document.on( 'mouseup touchend.noPreventDefault', self._events.closeOnClickOutside );
78		}
79
80		// Triggered in `$us.header.setView()`
81		$us.$document
82			.on( 'usHeader.update_view', self._events.resize );
83
84		$us.$window
85			.on( 'resize', $ush.debounce( self._events.resize, 5 ) );
86
87		// Fix "Safari outline webkit bug #243289"
88		if ( $ush.isSafari ) {
89			self.$mobileMenu.on( 'mouseup', () => {
90				self.$mobileMenu.attr( 'style', 'outline: none' );
91			} );
92		}
93
94		// Add anchors from reusable blocks
95		self.$reusableBlocksLinks.each( ( index, anchor ) => {
96			if ( $( anchor ).parents( '.w-popup-wrap' ).length === 0 ) {
97				self.$anchors.push( anchor );
98			}
99		} );
100
101		// Close the main menu on tablets after a submenu click (since tablets don’t support hover)
102		if ( $.isMobile && self.type === 'desktop' ) {
103			self.$list.on( 'click', '.w-nav-anchor[class*="level_"]', ( e ) => {
104				const $target = $( e.currentTarget );
105				const $menuItem = $target.closest( '.menu-item' );
106				if ( $target.usMod('level') > 1 && ! $menuItem.hasClass( 'menu-item-has-children' ) ) {
107					$target.parents( '.menu-item.opened' ).removeClass( 'opened' );
108				}
109			} );
110		}
111
112		self.$itemsHasChildren.each( ( _, menuItem ) => {
113			const $menuItem = $( menuItem );
114
115			// Mark toggleable items (skip descendants of mega menus)
116			const isInMegaMenu = $menuItem.parents( '.menu-item.has_cols, .menu-item.has_side_panel' ).length > 0;
117			if ( ! isInMegaMenu ) {
118				$menuItem.addClass( 'togglable' );
119			}
120
121			// Behavior of menu items on mobile
122			const $arrow = $( '.w-nav-arrow:first', $menuItem );
123			const $subAnchor = $( '.w-nav-anchor:first', $menuItem );
124			const dropByLabel = $menuItem.hasClass( 'mobile-drop-by_label' ) || $menuItem.parents( '.menu-item' ).hasClass( 'mobile-drop-by_label' );
125			const dropByArrow = $menuItem.hasClass( 'mobile-drop-by_arrow' ) || $menuItem.parents( '.menu-item' ).hasClass( 'mobile-drop-by_arrow' );
126
127			if ( dropByLabel || ( self.opts.mobileBehavior && ! dropByArrow ) ) {
128				$subAnchor.on( 'click', self._events.handleMobileClick );
129			} else {
130				$arrow.on( 'click', self._events.handleMobileClick );
131				$arrow.on( 'click', self._events.handleKeyboardNav );
132			}
133		} );
134
135		// Touchscreen handling on desktop devices
136		if ( ! $us.$html.hasClass( 'no-touch' ) ) {
137			self.$list.on( 'click', '.menu-item-has-children.togglable > .w-nav-anchor', ( e ) => {
138				if ( self.type === 'mobile' ) {
139					return;
140				}
141				e.preventDefault();
142				const $target = $( e.currentTarget );
143				const $menuItem = $target.parent();
144				if ( $menuItem.hasClass( 'opened' ) ) {
145					return location.assign( $target.attr( 'href' ) );
146				}
147				$menuItem.addClass( 'opened' );
148				const onOutsideClick = ( e ) => {
149					if ( $.contains( $menuItem[0], e.target ) ) {
150						return;
151					}
152					$menuItem.removeClass( 'opened' );
153					$us.$body.off( 'touchstart', onOutsideClick );
154				};
155				$us.$body.on( 'touchstart.noPreventDefault', onOutsideClick );
156			} );
157		}
158
159		$us.$document.on( 'keydown', ( e ) => {
160
161			// Fix Safari outline webkit bug #243289
162			if ( $ush.isSafari ) {
163				self.$mobileMenu.removeAttr( 'style' );
164			}
165
166			// Close mobile menu on ESC
167			if ( e.keyCode === $ush.ESC_KEYCODE && self.type === 'mobile' && self.mobileNavOpened ) {
168				self.closeMobileMenu();
169			}
170
171			// Close submenus when navigating outside the menu
172			if ( e.keyCode === $ush.TAB_KEYCODE && self.type === 'desktop' && ! $( e.target ).closest( '.w-nav' ).length ) {
173				self.$items.removeClass( 'opened' );
174			}
175		} );
176
177		$ush.timeout( () => {
178			self.resize();
179			$us.header.$container.trigger( 'contentChange' );
180		}, 50 );
181	};
182
183	// Mobile navigation API.
184	$.extend( usNav.prototype, {
185
186		/**
187		 * Mobile menu toggle button.
188		 *
189		 * @param {Event} e
190		 */
191		toggleMobileMenu: function( e ) {
192			const self = this;
193
194			e.preventDefault();
195
196			self.mobileNavOpened = ! self.mobileNavOpened;
197
198			// Close on outside menu click
199			$us.$document.on( 'mouseup touchend.noPreventDefault', self._events.closeOnClickOutside );
200
201			// Make empty links focusable and clickable to open dropdown
202			self.$anchors.each( ( _, node ) => { node.href = node.href || 'javascript:void(0)'; } );
203
204			if ( self.mobileNavOpened ) {
205
206				// Close other opened menus
207				$( '.l-header .w-nav' ).not( self.$container ).each( ( _, node ) => {
208					$( node ).trigger( 'closeMobileMenu' );
209				} );
210
211				// Close opened sublists
212				self.$mobileMenu.addClass( 'active' ).focus();
213				self.$items.filter( '.opened' ).removeClass( 'opened' );
214				self.$childLists.resetInlineCSS( 'display', 'height', 'opacity' );
215				if ( self.layout === 'dropdown' ) {
216					self.$list.slideDownCSS( SLIDE_DURATION, () => $us.header.$container.trigger( 'contentChange' ) );
217				}
218				$us.$html.addClass( 'w-nav-open' );
219				self.$mobileMenu.attr( 'aria-expanded', 'true' );
220				$us.mobileNavOpened++;
221
222				// Close mobile menu when focus is outside the menu
223				$us.$document.on( 'focusin', self._events.closeMobileMenuOnFocusIn );
224
225			} else {
226				self.closeMobileMenu();
227			}
228
229			$us.$canvas.trigger( 'contentChange' );
230		},
231
232		/**
233		 * Handle click on mobile.
234		 *
235		 * @param {Event} e
236		 */
237		handleMobileClick: function( e ) {
238			const self = this;
239			if ( self.type === 'mobile' ) {
240				e.stopPropagation();
241				e.preventDefault();
242				const $menuItem = $( e.currentTarget ).closest( '.menu-item' );
243				self.switchMobileSubMenu( $menuItem, ! $menuItem.hasClass( 'opened' ) );
244			}
245		},
246
247		/**
248		 * Switch mobile submenu items.
249		 *
250		 * @param {Node} $menuItem
251		 * @param {Boolean} opened
252		 */
253		switchMobileSubMenu: function( $menuItem, opened ) {
254			const self = this;
255			if ( self.type !== 'mobile' ) {
256				return;
257			}
258			const $subMenu = $menuItem.children( '.w-nav-list' );
259			if ( opened ) {
260				$menuItem.addClass( 'opened' );
261				$subMenu.slideDownCSS( SLIDE_DURATION, () => $us.header.$container.trigger( 'contentChange' ) );
262			} else {
263				$menuItem.removeClass( 'opened' );
264				$subMenu.slideUpCSS( SLIDE_DURATION, () => $us.header.$container.trigger( 'contentChange' ) );
265			}
266		},
267
268		/**
269		 * Close mobile menu on focus leave.
270		 *
271		 * @event handler
272		 */
273		closeMobileMenuOnFocusIn: function() {
274			const self = this;
275			if ( ! $.contains( self.$container[0], document.activeElement ) ) {
276				self.closeMobileMenu();
277			}
278		},
279
280		/**
281		 * Close mobile menu.
282		 */
283		closeMobileMenu: function() {
284			const self = this;
285			if ( self.type !== 'mobile' ) {
286				return;
287			}
288			self.mobileNavOpened = false;
289			self.$mobileMenu.removeClass( 'active' );
290			$us.$html.removeClass( 'w-nav-open' );
291			self.$mobileMenu.attr( 'aria-expanded', 'false' );
292
293			if ( self.$list && self.layout === 'dropdown' ) {
294				self.$list.slideUpCSS( SLIDE_DURATION );
295			}
296
297			$us.mobileNavOpened--;
298			self.$mobileMenu[0].focus();
299			$us.$canvas.trigger( 'contentChange' );
300
301			$us.$document
302				.off( 'focusin', self._events.closeMobileMenuOnFocusIn )
303				.off( 'mouseup touchend.noPreventDefault', self._events.closeOnClickOutside );
304		},
305
306		/**
307		 * Close on anchor click (mobile).
308		 *
309		 * @param {Event} e
310		 */
311		closeMobileMenuOnClick: function( e ) {
312			const self = this;
313			const $menuItem = $( e.currentTarget ).closest( '.menu-item' );
314			const dropByLabel = $menuItem.hasClass( 'mobile-drop-by_label' ) || $menuItem.parents( '.menu-item' ).hasClass( 'mobile-drop-by_label' );
315			const dropByArrow = $menuItem.hasClass( 'mobile-drop-by_arrow' ) || $menuItem.parents( '.menu-item' ).hasClass( 'mobile-drop-by_arrow' );
316
317			if ( self.type !== 'mobile' || $us.header.isVertical() ) {
318				return;
319			}
320			if (
321				dropByLabel
322				|| ( self.opts.mobileBehavior && $menuItem.hasClass( 'menu-item-has-children' ) && ! dropByArrow )
323			) {
324				return;
325			}
326
327			self.closeMobileMenu();
328		},
329
330	} );
331
332	// Global navigation API
333	$.extend( usNav.prototype, {
334
335		/**
336		 * Open/close dropdown on click.
337		 *
338		 * @param {Event} e
339		 */
340		toggleMenuOnClick: function( e ) {
341			const self = this;
342
343			if ( self.type === 'mobile' ) {
344				return;
345			}
346
347			const $menuItem = $( e.currentTarget ).closest( '.menu-item' );
348			const isOpened = $menuItem.hasClass( 'opened' );
349
350			if ( ! self.keyboardNavEvent ) {
351				e.preventDefault();
352				e.stopPropagation();
353			} else {
354				return;
355			}
356
357			$menuItem.toggleClass( 'opened', ! isOpened );
358
359			// Events for open items
360			if ( ! isOpened ) {
361				self.closeOnMouseOver( e );
362
363			} else {
364				$( '.menu-item-has-children.opened', $menuItem ).removeClass( 'opened' );
365			}
366		},
367
368		/**
369		 * Close menu on click outside.
370		 *
371		 * @param {Event} e
372		 */
373		closeOnClickOutside: function( e ) {
374			const self = this;
375
376			if ( self.mobileNavOpened && self.type === 'mobile' ) {
377				if (
378					! self.$mobileMenu.is( e.target )
379					&& ! self.$mobileMenu.has( e.target ).length
380					&& ! self.$list.is( e.target )
381					&& ! self.$list.has( e.target ).length
382				) {
383					self.closeMobileMenu();
384				}
385
386			} else if (
387				self.$itemsHasChildren.hasClass( 'opened' )
388				&& ! $.contains( self.$container[0], e.target )
389			) {
390				self.$itemsHasChildren.removeClass( 'opened' );
391			}
392		},
393
394		/**
395		 * Close tab-opened items on hover of other items.
396		 *
397		 * @param {Event} e
398		 */
399		closeOnMouseOver: function( e ) {
400			const self = this;
401			if ( self.type === 'mobile' ) {
402				return;
403			}
404			const $target = $( e.target );
405			const $itemHasChildren = $target.closest( '.menu-item-has-children' );
406			const $menuItemLevel1 = $target.closest( '.menu-item.level_1' );
407			self.$itemsHasChildren
408				.not( $itemHasChildren )
409				.not( $itemHasChildren.parents( '.menu-item-has-children' ) )
410				.not( $menuItemLevel1 )
411				.removeClass( 'opened' );
412		},
413
414		/**
415		 * Handle keydown events.
416		 *
417		 * @param {Event} e
418		 */
419		handleKeyboardNav: function( e ) {
420			const self = this;
421			const keyCode = e.keyCode || e.which;
422			const $target = $( e.target );
423			const $menuItem = $target.closest( '.menu-item' );
424			const $menuItemLevel1 = $target.closest( '.menu-item.level_1' );
425			const $itemHasChildren = $target.closest( '.menu-item-has-children' );
426
427			// Mobile menu handlers
428			if ( self.type === 'mobile' ) {
429				if ( [ $ush.ENTER_KEYCODE, $ush.SPACE_KEYCODE ].includes( keyCode ) && $target.is( self.$arrows ) ) {
430					e.stopPropagation();
431					e.preventDefault();
432					self.switchMobileSubMenu( $menuItem, ! $menuItem.hasClass( 'opened' ) );
433				}
434				if ( keyCode === $ush.TAB_KEYCODE ) {
435					if ( e.shiftKey && self.$anchors.index( $target ) === 0 ) {
436						self.closeMobileMenu();
437					}
438				}
439			}
440
441			// Desktop menu handlers
442			if ( self.type === 'desktop' ) {
443				if ( [ $ush.ENTER_KEYCODE, $ush.SPACE_KEYCODE ].includes( keyCode ) && $target.is( self.$arrows ) ) {
444					e.preventDefault();
445
446					// Close items opened via tab on hover over other items
447					self.$itemsHasChildren
448						.off( 'mouseover', self._events.closeOnMouseOver )
449						.one( 'mouseover', self._events.closeOnMouseOver );
450
451					if ( ! $itemHasChildren.hasClass( 'opened' ) ) {
452						$itemHasChildren
453							.addClass( 'opened' )
454							.siblings()
455							.removeClass( 'opened' );
456
457						self.$itemsHasChildren.not( $itemHasChildren ).not( $menuItemLevel1 ).removeClass( 'opened' );
458
459						self.$arrows.attr( 'aria-expanded', 'false' );
460						$target.attr( 'aria-expanded', 'true' );
461
462					} else {
463						$itemHasChildren.removeClass( 'opened' );
464						$target.attr( 'aria-expanded', 'false' );
465					}
466				}
467				if ( keyCode === $ush.ESC_KEYCODE ) {
468					if ( $menuItemLevel1.hasClass( 'opened' ) ) {
469						$( '.w-nav-arrow:first', $menuItemLevel1 ).focus();
470					}
471					self.$items.removeClass( 'opened' );
472					self.$arrows.attr( 'aria-expanded', 'false' );
473				}
474
475				self.keyboardNavEvent = true;
476				$ush.timeout( () => { self.keyboardNavEvent = false; }, 1 );
477			}
478		},
479
480		/**
481		 * Resize handler.
482		 */
483		resize: function() {
484			const self = this;
485
486			if ( self.$container.length === 0 ) {
487				return;
488			}
489
490			const newType = ( window.innerWidth < self.opts.mobileWidth ) ? 'mobile' : 'desktop';
491
492			if ( $us.header.orientation !== self.headerOrientation || newType !== self.type ) {
493
494				self.$childLists.resetInlineCSS( 'display', 'height', 'opacity' );
495
496				if ( self.headerOrientation === 'hor' && self.type === 'mobile' ) {
497					self.$list.resetInlineCSS( 'display', 'height', 'opacity' );
498				}
499
500				self.$items.removeClass( 'opened' );
501				self.headerOrientation = $us.header.orientation;
502				self.type = newType;
503				self.$container.usMod( 'type', newType );
504			}
505
506			self.$list.removeClass( 'hide_for_mobiles' );
507		},
508
509	} );
510
511	$.fn.usNav = function() {
512		return this.each( function() {
513			$( this ).data( 'usNav', new usNav( this ) );
514		} );
515	};
516
517	$( '.l-header .w-nav' ).usNav();
518
519}( 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.