PageSourceSearch

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

js almerecity.nl collected 2026-10-02 11:43:51 UTC 20,219 bytes, 661 lines download raw bytes

1/**
2 * UpSolution Element: Tabs
3 *
4 * @requires $us.canvas
5 */
6! function( $, _undefined ) {
7	"use strict";
8
9	$us.WTabs = function( container, options ) {
10		const self = this;
11
12		const _defaults = {
13			easing: 'cubic-bezier(.78,.13,.15,.86)',
14			duration: 300
15		};
16
17		self.options = $.extend( {}, _defaults, options );
18		self.isRtl = $( '.l-body' ).hasClass( 'rtl' );
19
20		// Elements
21		self.$container = $( container );
22		self.$tabsList = $( '> .w-tabs-list:first', self.$container );
23		self.$tabs = $( '.w-tabs-item', self.$tabsList );
24		self.$sectionsWrapper = $( '> .w-tabs-sections:first', self.$container );
25		self.$sections = $( '> .w-tabs-section', self.$sectionsWrapper );
26		self.$headers = self.$sections.children( '.w-tabs-section-header' );
27		self.$contents = self.$sections.children( '.w-tabs-section-content' );
28		self.$tabsBar = $();
29
30		// Overriding specific to Web Accessibility, it is not allowed to have several identical id and
31		// aria-content, aria-control. http://web-accessibility.carnegiemuseums.org/code/accordions/
32		if ( self.$container.hasClass( 'accordion' ) ) {
33			self.$tabs = self.$headers;
34		}
35
36		// Private "Variables"
37		self.accordionAtWidth = self.$container.data( 'accordion-at-width' );
38		self.align = self.$tabsList.usMod( 'align' );
39		self.count = self.$tabs.length;
40		self.hasScrolling = self.$container.hasClass( 'has_scrolling' ) || false;
41		self.isAccordionAtWidth = $ush.parseInt( self.accordionAtWidth ) !== 0;
42		self.isScrolling = false;
43		self.isTogglable = self.$container.usMod( 'type' ) === 'togglable';
44		self.minWidth = 0; // container width at which we should switch to accordion layout.
45		self.tabHeights = [];
46		self.tabLefts = [];
47		self.tabTops = [];
48		self.tabWidths = [];
49		self.width = 0;
50
51		// If there are no tabs, abort further execution.
52		if ( self.count === 0 ) {
53			return;
54		}
55
56		// Basic layout
57		if ( self.$container.hasClass( 'accordion' ) ) {
58			self.basicLayout = 'accordion';
59
60		} else {
61			self.basicLayout = self.$container.usMod( 'layout' ) || 'hor';
62		}
63
64		// Current active layout (may be switched to 'accordion').
65		self.curLayout = self.basicLayout;
66
67		// Array of active tabs indexes.
68		self.active = [];
69		self.activeOnInit = [];
70		self.definedActive = [];
71
72		// Preparing arrays of jQuery objects for easier manipulating in future.
73		self.tabs = $.map( self.$tabs.toArray(), $ );
74		self.sections = $.map( self.$sections.toArray(), $ );
75		self.headers = $.map( self.$headers.toArray(), $ );
76		self.contents = $.map( self.$contents.toArray(), $ );
77
78		// Do nothing it there are no sections.
79		if ( ! self.sections.length ) {
80			return;
81		}
82
83		$.each( self.tabs, ( index ) => {
84
85			if ( self.sections[ index ].hasClass( 'content-empty' ) ) {
86				self.tabs[ index ].hide();
87				self.sections[ index ].hide();
88			}
89
90			if ( self.tabs[ index ].hasClass( 'active' ) ) {
91				self.active.push( index );
92				self.activeOnInit.push( index );
93			}
94
95			if ( self.tabs[ index ].hasClass( 'defined-active' ) ) {
96				self.definedActive.push( index );
97			}
98
99			self.tabs[ index ]
100				.add( self.headers[ index ] )
101				.on( 'click mouseover', ( e ) => {
102
103					var $link = self.tabs[ index ];
104					if ( ! $link.is( 'a' ) ) {
105						$link = $( 'a', $link );
106					}
107
108					if ( $link.length ) {
109						if ( e.type == 'click' && ! $ush.toString( $link.attr( 'href' ) ) ) {
110							e.preventDefault();
111						}
112					} else {
113						e.preventDefault();
114					}
115
116					if (
117						e.type == 'mouseover'
118						&& (
119							self.$container.hasClass( 'accordion' )
120							|| ! self.$container.hasClass( 'switch_hover' )
121						)
122					) {
123						return;
124					}
125
126					// Toggling accordion sections.
127					if ( self.curLayout === 'accordion' && self.isTogglable ) {
128						self.toggleSection( index ); // cannot toggle the only active item.
129
130						// Setup active tab
131					} else {
132
133						if ( index != self.active[0] ) {
134							self.headerClicked = true;
135							self.openSection( index );
136
137						} else if ( self.curLayout === 'accordion' ) {
138
139							self.contents[ index ]
140								.css( 'display', 'block' )
141								.slideUp( self.options.duration, self._events.contentChanged );
142							self.tabs[ index ]
143								.attr( 'aria-expanded', 'true' )
144								.removeClass( 'active' );
145							self.sections[ index ]
146								.removeClass( 'active' );
147							self.active[0] = _undefined;
148						}
149					}
150				} );
151		} );
152
153		// Bindable events
154		self._events = {
155			resize: self.resize.bind( self ),
156			hashchange: self.hashchange.bind( self ),
157			contentChanged: function() {
158				$.each( self.tabs, ( _, item ) => {
159					var $content = $( item );
160					$content.attr( 'aria-expanded', $content.hasClass( 'active' ) );
161				} )
162				$us.$canvas.trigger( 'contentChange', { elm: self.$container } );
163			},
164			wheel: function() {
165				if ( self.isScrolling ) {
166					$us.$htmlBody.stop( true, false ); // stop animation when scrolling wheel
167				}
168			}
169		};
170
171		// Starting everything.
172		self.switchLayout( self.curLayout );
173
174		$us.$window
175			.on( 'resize', $ush.debounce( self._events.resize, 5 ) )
176			.on( 'hashchange', self._events.hashchange )
177			.on( 'wheel.noPreventDefault', $ush.debounce( self._events.wheel.bind( self ), 5 ) );
178
179		$us.$document.ready( () => {
180			self.resize();
181			$ush.timeout( self._events.resize, 50 );
182			$ush.timeout( () => {
183				// TODO: move to a class function for code reading improvement.
184				// Open tab on page load by hash.
185				if ( window.location.hash ) {
186					var hash = window.location.hash.substr( 1 ),
187						$linkedSection = $( `.w-tabs-section[id="${hash}"]`, self.$sectionsWrapper );
188					if ( $linkedSection.length && ! $linkedSection.hasClass( 'active' ) ) {
189						$( '.w-tabs-section-header', $linkedSection ).trigger( 'click' );
190					}
191				}
192			}, 150 );
193		} );
194
195		// Support for external links to tabs.
196		$.each( self.tabs, ( index ) => {
197			if ( self.headers.length && self.headers[ index ].attr( 'href' ) != _undefined ) {
198				var tabHref = self.headers[ index ].attr( 'href' ),
199					tabHeader = self.headers[ index ];
200				$( `a[href="${tabHref}"]`, self.$container ).on( 'click', function( e ) {
201					e.preventDefault();
202					if ( $( this ).hasClass( 'w-tabs-section-header', 'w-tabs-item' ) ) {
203						return;
204					}
205					if ( ! $( tabHeader ).parent( '.w-tabs-section' ).hasClass( 'active' ) ) {
206						tabHeader.trigger( 'click' );
207					}
208				} );
209			}
210		} );
211
212		self.$container.addClass( 'initialized' );
213
214		// Gets the height of the header after animation.
215		self.headerHeight = 0;
216		$us.header.on( 'transitionEnd', ( header ) => {
217			self.headerHeight = header.getCurrentHeight( /* adminBar */true );
218		} );
219
220		if ( $us.usbPreview() ) {
221
222			const usbContentChange = () => {
223				if ( ! self.isTrendy() || self.curLayout == 'accordion' ) {
224					return;
225				}
226				self.measure();
227				self.setBarPosition( self.active[0] || 0 ); // set bar position for certain element index and current layout
228			};
229
230			self.$container.on( 'usb.contentChange', $ush.debounce( usbContentChange, 1 ) );
231		}
232	};
233
234	$us.WTabs.prototype = {
235
236		/**
237		 * Determines if trendy style (Material style)
238		 *
239		 * @return {boolean} True if trendy, False otherwise
240		 */
241		isTrendy: function() {
242			return this.$container.hasClass( 'style_trendy' );
243		},
244
245		hashchange: function() {
246			if ( window.location.hash ) {
247				var hash = window.location.hash.substr( 1 ),
248					$linkedSection = $( `.w-tabs-section[id="${hash}"]`, this.$sectionsWrapper );
249				if ( $linkedSection.length && ! $linkedSection.hasClass( 'active' ) ) {
250					$( '.w-tabs-section-header', $linkedSection ).click();
251				}
252			}
253		},
254
255		switchLayout: function( to ) {
256			this.cleanUpLayout( this.curLayout );
257			this.prepareLayout( to );
258			this.curLayout = to;
259		},
260
261		/**
262		 * Clean up layout's special inline styles and/or dom elements.
263		 *
264		 * @param from
265		 */
266		cleanUpLayout: function( from ) {
267			this.$sections.resetInlineCSS( 'display' );
268
269			if ( from === 'accordion' ) {
270				this.$container.removeClass( 'accordion' );
271				this.$contents.resetInlineCSS( 'height', 'padding-top', 'padding-bottom', 'display', 'opacity' );
272			}
273
274			if ( this.isTrendy() && [ 'hor', 'ver' ].includes( from ) ) {
275				this.$tabsBar.remove();
276			}
277		},
278
279		/**
280		 * Apply layout's special inline styles and/or dom elements.
281		 *
282		 * @param to
283		 */
284		prepareLayout: function( to ) {
285			if ( to !== 'accordion' && this.active[0] === _undefined ) {
286				this.active[0] = this.activeOnInit[0];
287				if ( this.active[ 0 ] !== _undefined ) {
288					this.tabs[ this.active[0] ]
289						.addClass( 'active' );
290					this.sections[ this.active[0] ]
291						.addClass( 'active' );
292				}
293			}
294
295			if ( to === 'accordion' ) {
296				this.$container.addClass( 'accordion' );
297				this.$contents.hide();
298				if ( this.curLayout !== 'accordion' && this.active[0] !== _undefined && this.active[0] !== this.definedActive[0] ) {
299					this.headers[ this.active[0] ]
300						.removeClass( 'active' );
301					this.tabs[ this.active[0] ]
302						.removeClass( 'active' );
303					this.sections[ this.active[0] ]
304						.removeClass( 'active' );
305					this.active[0] = this.definedActive[0];
306
307				}
308				for ( var i = 0; i < this.active.length; i ++ ) {
309					if ( this.contents[ this.active[ i ] ] !== _undefined ) {
310						this.tabs[ this.active[ i ] ]
311							.attr( 'aria-expanded', 'true' )
312							.addClass( 'active' );
313						this.sections[ this.active[ i ] ]
314							.addClass( 'active' );
315						this.contents[ this.active[ i ] ]
316							.show();
317					}
318				}
319
320			} else if ( to === 'ver' ) {
321				this.$contents.hide();
322				this.contents[ this.active[0] ]
323					.show();
324			}
325
326			if ( this.isTrendy() && 'hor|ver'.indexOf( this.curLayout ) > - 1 ) {
327				this.$tabsBar = $( '<div class="w-tabs-list-bar"></div>' )
328					.appendTo( this.$tabsList );
329			}
330		},
331
332		/**
333		 * Measure needed sizes.
334		 */
335		measure: function() {
336			if ( this.basicLayout === 'ver' ) {
337				// Get the specified minimum width or determine automatically
338				if ( this.isAccordionAtWidth ) {
339					this.minWidth = this.accordionAtWidth;
340				} else {
341					var // Measuring minimum tabs width.
342						minTabWidth = this.$tabsList.outerWidth( true ),
343						// Static value fo min content width
344						minContentWidth = 300,
345						// Measuring minimum tabs width for percent-based sizes.
346						navWidth = this.$container.usMod( 'navwidth' );
347
348					if ( navWidth !== 'auto' ) {
349						minTabWidth = Math.max( minTabWidth, minContentWidth * parseInt( navWidth ) / ( 100 - parseInt( navWidth ) ) );
350					}
351					this.minWidth = Math.max( 480, minContentWidth + minTabWidth + 1 )
352				}
353
354				if ( this.isTrendy() ) {
355					this.tabHeights = [];
356					this.tabTops = [];
357					for ( var index = 0; index < this.tabs.length; index ++ ) {
358						this.tabHeights.push( this.tabs[ index ].outerHeight( true ) );
359						this.tabTops.push(
360							index
361								? ( this.tabTops[ index - 1 ] + this.tabHeights[ index - 1 ] )
362								: 0
363						);
364					}
365				}
366
367			} else {
368				if ( this.basicLayout === 'hor' ) {
369					this.$container.addClass( 'measure' );
370					// Get the specified minimum width or determine automatically
371					if ( this.isAccordionAtWidth ) {
372						this.minWidth = this.accordionAtWidth;
373					} else {
374						this.minWidth = 0;
375						for ( var index = 0; index < this.tabs.length; index ++ ) {
376							this.minWidth += this.tabs[ index ].outerWidth( true );
377						}
378					}
379					this.$container.removeClass( 'measure' );
380				}
381
382				if ( this.isTrendy() ) {
383					this.tabWidths = [];
384					this.tabLefts = [];
385					for ( var index = 0; index < this.tabs.length; index ++ ) {
386						this.tabWidths.push( this.tabs[ index ].outerWidth( true ) );
387						this.tabLefts.push( index
388							? ( this.tabLefts[ index - 1 ] + this.tabWidths[ index - 1 ] )
389							: this.tabs[ index ].position().left
390						);
391					}
392					// Offset correction for RTL version with Trendy enabled
393					if ( this.isRtl ) {
394						var
395							// Get the width of the first tab
396							firstTabWidth = this.tabWidths[0],
397							// Get X offsets
398							offset = ( 'none' == this.align )
399								? this.$tabsList.outerWidth( true )
400								: this.tabWidths // Get the total width of all tabs
401									.reduce( ( a, b ) => { return a + b }, /* default */0 );
402						// Calculate position based on offset
403						this.tabLefts = this.tabLefts
404							.map( ( left ) => Math.abs( left - offset + firstTabWidth ) );
405					}
406				}
407			}
408		},
409
410		/**
411		 * Set bar position for certain element index and current layout
412		 *
413		 * @param {number} index The index element
414		 * @param {boolean} animated Animating an element when updating css
415		 */
416		setBarPosition: function( index, animated ) {
417			if (
418				index === _undefined
419				|| ! this.isTrendy()
420				|| 'hor|ver'.indexOf( this.curLayout ) == - 1
421			) {
422				return;
423			}
424			// Add a bar to the document if it does not exist
425			if ( ! this.$tabsBar.length ) {
426				this.$tabsBar = $( '<div class="w-tabs-list-bar"></div>' )
427					.appendTo( this.$tabsList );
428			}
429			// Get bar position for certain element index and current layout
430			var css = {};
431			if ( this.curLayout === 'hor' ) {
432				css = { width: this.tabWidths[ index ] };
433				css[ this.isRtl ? 'right' : 'left' ] = this.tabLefts[ index ];
434			} else if ( this.curLayout === 'ver' ) {
435				css = {
436					top: this.tabTops[ index ],
437					height: this.tabHeights[ index ]
438				};
439			}
440			// Set css properties for a bar element
441			if ( ! animated ) {
442				this.$tabsBar.css( css );
443			} else {
444				this.$tabsBar
445					.performCSSTransition( css, this.options.duration, null, this.options.easing );
446			}
447		},
448
449		/**
450		 * Open tab section.
451		 *
452		 * @param index int
453		 */
454		openSection: function( index ) {
455			if ( this.sections[ index ] === _undefined ) {
456				return;
457			}
458
459			if ( this.curLayout === 'hor' ) {
460				this.$sections
461					.removeClass( 'active' )
462					.css( 'display', 'none' );
463				this.sections[ index ]
464					.stop( true, true )
465					.fadeIn( this.options.duration, function() {
466						$( this ).addClass( 'active' );
467					} );
468			} else if ( this.curLayout === 'accordion' ) {
469				if ( this.contents[ this.active[0] ] !== _undefined ) {
470					this.contents[ this.active[0] ]
471						.css( 'display', 'block' )
472						.stop( true, false )
473						.slideUp( this.options.duration );
474				}
475				this.contents[ index ]
476					.css( 'display', 'none' )
477					.stop( true, false )
478					.slideDown( this.options.duration, function() {
479						this._events.contentChanged.call( this );
480
481						// Scrolling to the opened section
482						if ( this.hasScrolling && this.curLayout === 'accordion' && this.headerClicked == true ) {
483							var top = this.headers[ index ].offset().top;
484
485							if ( ! jQuery.isMobile ) {
486								top -= $us.$canvas.offset().top || 0;
487							}
488							// If there is a sticky section in front of the current section,
489							// then take into account the position this section.
490							var $prevStickySection = this.$container
491								.closest( '.l-section' )
492								.prevAll( '.l-section.type_sticky' );
493
494							if ( $prevStickySection.length ) {
495								top -= parseInt( $prevStickySection.outerHeight( true ) );
496							}
497
498							// Animate options
499							var animateOptions = {
500								duration: $us.canvasOptions.scrollDuration,
501								easing: $us.getAnimationName( 'easeInOutExpo' ),
502								start: function() {
503									this.isScrolling = true;
504								}.bind( this ),
505								always: function() {
506									this.isScrolling = false;
507								}.bind( this ),
508								/**
509								 * Get and applying new values during animation.
510								 *
511								 * @param number now
512								 * @param object fx
513								 */
514								step: function( now, fx ) {
515									var newTop = top;
516									// Since the header at the moment of scrolling the scroll can change the height,
517									// we will correct the position of the element.
518									if (
519										$us.header.isHorizontal()
520										&& $us.header.stickyEnabled()
521									) {
522										newTop -= this.headerHeight;
523									}
524									if ( fx.end !== newTop ) {
525										$us.$htmlBody
526											.stop( true, false )
527											.animate( { scrollTop: newTop }, $.extend( animateOptions, {
528												easing: $us.getAnimationName( 'easeOutExpo' )
529											} ) );
530									}
531								}.bind( this )
532							};
533							$us.$htmlBody
534								.stop( true, false )
535								.animate( { scrollTop: top }, animateOptions );
536							this.headerClicked = false;
537						}
538					}.bind( this ) );
539				this.$sections
540					.removeClass( 'active' );
541				this.sections[ index ]
542					.addClass( 'active' );
543			} else if ( this.curLayout === 'ver' ) {
544				if ( this.contents[ this.active[0] ] !== _undefined ) {
545					this.contents[ this.active[0] ]
546						.css( 'display', 'none' );
547				}
548				this.contents[ index ]
549					.css( 'display', 'none' )
550					.stop( true, true )
551					.fadeIn( this.options.duration, this._events.contentChanged );
552				this.$sections
553					.removeClass( 'active' );
554				this.sections[ index ]
555					.addClass( 'active' );
556			}
557
558			this._events.contentChanged();
559			this.$tabs
560				.attr( 'aria-expanded', 'false' )
561				.removeClass( 'active' );
562			this.tabs[ index ]
563				.attr( 'aria-expanded', 'true' )
564				.addClass( 'active' );
565			this.active[ 0 ] = index;
566
567			// Set bar position for certain element index and current layout
568			this.setBarPosition( index, /* animated */true );
569		},
570
571		/**
572		 * Toggle some togglable accordion section.
573		 *
574		 * @param index
575		 */
576		toggleSection: function( index ) {
577			// (!) Can only be used within accordion state
578			var indexPos = $.inArray( index, this.active );
579			if ( indexPos != - 1 ) {
580				this.contents[ index ]
581					.css( 'display', 'block' )
582					.slideUp( this.options.duration, this._events.contentChanged );
583				this.tabs[ index ]
584					.attr( 'aria-expanded', 'true' )
585					.removeClass( 'active' );
586				this.sections[ index ]
587					.removeClass( 'active' );
588				this.active.splice( indexPos, 1 );
589			} else {
590				this.contents[ index ]
591					.css( 'display', 'none' )
592					.slideDown( this.options.duration, this._events.contentChanged );
593				this.tabs[ index ]
594					.attr( 'aria-expanded', 'false' )
595					.addClass( 'active' );
596				this.sections[ index ]
597					.addClass( 'active' );
598				this.active.push( index );
599			}
600		},
601
602		/**
603		 * Resize-driven logics
604		 */
605		resize: function() {
606			this.width = this.isAccordionAtWidth
607				? $us.$window.outerWidth()
608				: this.$container.width();
609
610			// Skip changing Tabs into Accordion inside header menu on Mobiles
611			if (
612				this.curLayout !== 'accordion'
613				&& ! this.width
614				&& this.$container.closest( '.w-nav' ).length
615				&& ! jQuery.isMobile
616			) {
617				return;
618			}
619
620			var nextLayout = ( this.width <= this.minWidth )
621				? 'accordion'
622				: this.basicLayout;
623			if ( nextLayout !== this.curLayout ) {
624				this.switchLayout( nextLayout );
625			}
626			if ( this.curLayout !== 'accordion' ) {
627				this.measure();
628			}
629
630			this._events.contentChanged();
631			// Set bar position for certain element index and current layout
632			this.setBarPosition( this.active[ 0 ] );
633		}
634	};
635
636	$.fn.wTabs = function( options ) {
637		return this.each( function() {
638			$( this ).data( 'wTabs', new $us.WTabs( this, options ) );
639		} );
640	};
641
642	$( () => $( '.w-tabs' ).wTabs() );
643
644	// Init in Grid Layout context
645	$us.$document.on( 'usPostList.itemsLoaded usGrid.itemsLoaded', ( _, $items ) => {
646		$( '.w-tabs', $items ).wTabs();
647	} );
648
649}( jQuery );
650
651/* RevSlider support for our tabs */
652jQuery( function( $ ) {
653	$( '.w-tabs .rev_slider' ).each( function() {
654		var $slider = $( this );
655		$slider.bind( "revolution.slide.onloaded", function( e ) {
656			$us.$canvas.on( 'contentChange', function() {
657				$slider.revredraw();
658			} );
659		} );
660	} );
661} );

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.