PageSourceSearch

https://cosp.careerserviceunimi.it/assets/vendor/hs-megamenu/src/hs.megamenu.js

js careerserviceunimi.it collected 2026-10-05 11:03:04 UTC 18,802 bytes, 907 lines download raw bytes

1/*
2 * HS Mega Menu - jQuery Plugin
3 * @version: 1.0.0 (Sun, 26 Feb 2017)
4 * @requires: jQuery v1.6 or later
5 * @author: HtmlStream
6 * @event-namespace: .HSMegaMenu
7 * @browser-support: IE9+
8 * @license: 
9 *
10 * Copyright 2017 HtmlStream
11 *
12 */
13;(function($){
14	'use strict';
15
16
17	/**
18	 * Creates a mega menu.
19	 * 
20	 * @constructor 
21	 * @param {HTMLElement|jQuery} element - The element to create the mega menu for.
22	 * @param {Object} options - The options
23	 */
24	function MegaMenu(element, options) {
25
26		var self = this;
27
28		/**
29		 * Current element.
30		 *
31		 * @public
32		 */
33		this.$element = $(element);
34
35		/**
36		 * Current options set by the caller including defaults.
37		 *
38		 * @public 
39		 */
40		this.options = $.extend(true, {}, MegaMenu.defaults, options);
41
42
43		/**
44		 * Collection of menu elements.
45		 *
46		 * @private 
47		 */
48		this._items = $();
49
50
51		Object.defineProperties( this, {
52
53			/**
54			 * Contains composed selector of menu items.
55			 * 
56			 * @public
57			 */
58			itemsSelector: {
59				get: function() {
60					return self.options.classMap.hasSubMenu + ',' +
61								 self.options.classMap.hasMegaMenu;
62				}
63			},
64
65			/**
66			 * Contains chain of active items.
67			 * 
68			 * @private 
69			 */
70			_tempChain: {
71				value: null,
72				writable: true
73			},
74
75			/**
76			 * Contains current behavior state.
77			 * 
78			 * @public
79			 */
80			state: {
81				value: null,
82				writable: true
83			}
84
85		});
86
87		this.initialize();
88
89	};
90
91
92	/**
93	 * Default options of the mega menu.
94	 *
95	 * @public 
96	 */
97	MegaMenu.defaults = {
98		event: 'hover',
99		direction: 'horizontal',
100		breakpoint: 991,
101		animationIn: false,
102		animationOut: false,
103
104		rtl: false,
105		hideTimeOut: 300,
106
107		// For 'vertical' direction only
108		sideBarRatio: 1/4,
109		pageContainer: $('body'),
110
111		classMap: {
112			initialized: '.hs-menu-initialized',
113			mobileState: '.hs-mobile-state',
114
115			subMenu: '.hs-sub-menu',
116			hasSubMenu: '.hs-has-sub-menu',
117			hasSubMenuActive: '.hs-sub-menu-opened',
118
119			megaMenu: '.hs-mega-menu',
120			hasMegaMenu: '.hs-has-mega-menu',
121			hasMegaMenuActive: '.hs-mega-menu-opened'
122		},
123
124		mobileSpeed: 400,
125		mobileEasing: 'linear',
126
127		beforeOpen: function(){},
128		beforeClose: function(){},
129		afterOpen: function(){},
130		afterClose: function(){}
131	};
132
133
134	/**
135	 * Initialization of the plugin.
136	 *
137	 * @protected
138	 */
139	MegaMenu.prototype.initialize = function() {
140
141		var self = this,
142				$w = $(window);
143
144		if( this.options.rtl ) this.$element.addClass('hs-rtl');
145
146		this.$element
147				.addClass(this.options.classMap.initialized.slice(1))
148				.addClass('hs-menu-' + this.options.direction);
149
150
151
152		// Independent events
153		$w.on('resize.HSMegaMenu', function(e){
154
155			if( self.resizeTimeOutId ) clearTimeout( self.resizeTimeOutId );
156
157			self.resizeTimeOutId = setTimeout( function(){
158
159				if($w.width() <= self.options.breakpoint && self.state == 'desktop') self.initMobileBehavior();
160				else if( $w.width() > self.options.breakpoint && self.state == 'mobile' ) self.initDesktopBehavior();
161
162				self.refresh();
163
164			}, 50 );
165
166		});
167
168		$(document)
169			.on('click.HSMegaMenu', function(e){
170
171				var $parents = $(e.target).parents(self.itemsSelector);
172				self.closeAll( $parents.add($(e.target)) );
173
174			})
175			.on('keyup.HSMegaMenu', function(e){
176				if( e.keyCode && e.keyCode == 27 ) self.closeAll();
177			});
178
179		if($w.width() <= this.options.breakpoint) this.initMobileBehavior();
180		else if( $w.width() > this.options.breakpoint) this.initDesktopBehavior();
181
182		this.smartPositions();
183
184		return this;
185
186	};
187
188	MegaMenu.prototype.smartPositions = function() {
189
190		var self = this,
191				$submenus = this.$element.find( this.options.classMap.subMenu );
192
193		$submenus.each(function(i, el) {
194
195			MenuItem.smartPosition( $(el), self.options );
196
197		});
198
199	};
200
201	/**
202	 * Binding events to menu elements.
203	 * 
204	 * @protected
205	 */
206	MegaMenu.prototype.bindEvents = function() {
207
208		var self = this,
209				selector;
210
211		// Hover case
212		// if(this.options.event === 'hover' && !_isTouch()) {
213		if(this.options.event === 'hover') {
214			this.$element
215				.on(
216					'mouseenter.HSMegaMenu',
217					this.options.classMap.hasMegaMenu + ':not([data-event="click"]),' +
218					this.options.classMap.hasSubMenu + ':not([data-event="click"])',
219					function(e) {
220
221						var $this = $(this),
222								$chain = $this.parents( self.itemsSelector);
223
224						// Lazy initialization
225						if( !$this.data('HSMenuItem') ) {
226							self.initMenuItem( $this, self.getType( $this ) );
227						}
228
229						$chain = $chain.add($this);
230
231						self.closeAll( $chain );
232
233						$chain.each(function(i, el){
234
235							var HSMenuItem = $(el).data('HSMenuItem');
236
237							if(HSMenuItem.hideTimeOutId) clearTimeout(HSMenuItem.hideTimeOutId);
238							HSMenuItem.show();
239
240						});
241
242						self._items = self._items.not( $chain );
243						self._tempChain = $chain;
244
245						e.preventDefault();
246						e.stopPropagation();
247					}
248				)
249				.on(
250					'mouseleave.HSMegaMenu',
251					this.options.classMap.hasMegaMenu + ':not([data-event="click"]),' +
252					this.options.classMap.hasSubMenu + ':not([data-event="click"])',
253					function(e) {
254
255						var $this = $(this),
256								HSMenuItem = $this.data('HSMenuItem'),
257								$chain = $(e.relatedTarget).parents( self.itemsSelector );
258
259						HSMenuItem.hideTimeOutId = setTimeout( function(){
260							self.closeAll( $chain );
261						}, self.options.hideTimeOut );
262
263						self._items = self._items.add( self._tempChain );
264						self._tempChain = null;
265
266						e.preventDefault();
267						e.stopPropagation();
268					}
269				)
270				.on(
271					'click.HSMegaMenu',
272					this.options.classMap.hasMegaMenu + '[data-event="click"] > a, ' +
273					this.options.classMap.hasSubMenu + '[data-event="click"] > a',
274					function(e) {
275
276						var $this = $(this).parent('[data-event="click"]'),
277								HSMenuItem;
278
279						// Lazy initialization
280						if(!$this.data('HSMenuItem')) {
281							self.initMenuItem( $this, self.getType( $this ) );
282						}
283
284
285						self.closeAll( $this.add( 
286							$this.parents(self.itemsSelector) 
287						) );
288
289						HSMenuItem = $this
290							.data('HSMenuItem');
291
292						if(HSMenuItem.isOpened) {
293							HSMenuItem.hide();
294						}
295						else{
296							HSMenuItem.show();
297						}
298							
299
300						e.preventDefault();
301						e.stopPropagation();
302
303					}
304				);
305		}
306		// Click case
307		else {
308
309			this.$element
310				.on(
311					'click.HSMegaMenu',
312					(_isTouch() ?
313								 this.options.classMap.hasMegaMenu + ' > a, ' + 
314								 this.options.classMap.hasSubMenu + ' > a' :
315								 this.options.classMap.hasMegaMenu + ':not([data-event="hover"]) > a,' +
316								 this.options.classMap.hasSubMenu + ':not([data-event="hover"]) > a'),
317					function(e) {
318
319						var $this = $(this).parent(),
320								HSMenuItem,
321								$parents = $this.parents(self.itemsSelector);
322
323						// Lazy initialization
324						if(!$this.data('HSMenuItem')) {
325							self.initMenuItem( $this, self.getType( $this ) );
326						}
327
328						self.closeAll( $this.add(
329							$this.parents( self.itemsSelector )
330						) );
331
332						HSMenuItem = $this
333							.addClass('hs-event-prevented')
334							.data('HSMenuItem');
335
336						if(HSMenuItem.isOpened) {
337							HSMenuItem.hide();
338						}
339						else{
340							HSMenuItem.show();
341						}
342
343						e.preventDefault();
344						e.stopPropagation();
345					}
346				);
347
348				if(!_isTouch()) {
349					this.$element
350						.on(
351							'mouseenter.HSMegaMenu',
352							this.options.classMap.hasMegaMenu + '[data-event="hover"],' +
353							this.options.classMap.hasSubMenu + '[data-event="hover"]',
354							function(e) {
355
356								var $this = $(this),
357										$parents = $this.parents( self.itemsSelector);
358
359								// Lazy initialization
360								if( !$this.data('HSMenuItem') ) {
361									self.initMenuItem( $this, self.getType( $this ) );
362								}
363
364								self.closeAll( $this.add($parents) );
365
366								$parents.add($this).each(function(i, el){
367
368									var HSMenuItem = $(el).data('HSMenuItem');
369
370									if(HSMenuItem.hideTimeOutId) clearTimeout(HSMenuItem.hideTimeOutId);
371									HSMenuItem.show();
372
373								});
374
375								e.preventDefault();
376								e.stopPropagation();
377							}
378						)
379						.on(
380							'mouseleave.HSMegaMenu',
381							this.options.classMap.hasMegaMenu + '[data-event="hover"],' +
382							this.options.classMap.hasSubMenu + '[data-event="hover"]',
383							function(e) {
384
385								var $this = $(this),
386										HSMenuItem = $this.data('HSMenuItem');
387
388								HSMenuItem.hideTimeOutId = setTimeout( function(){
389
390									self.closeAll(
391										$(e.relatedTarget).parents(self.itemsSelector)
392									);
393
394								}, self.options.hideTimeOut );
395
396								e.preventDefault();
397								e.stopPropagation();
398							}
399						)
400				}
401		}
402
403	};
404
405	/**
406	 * Initialization of certain menu item.
407	 *
408	 * @protected 
409	 */
410	MegaMenu.prototype.initMenuItem = function(element, type) {
411
412		var self = this,
413				Item = new MenuItem(
414					element,
415					element.children(
416						self.options.classMap[type === 'mega-menu' ? 'megaMenu' : 'subMenu']
417					),
418					$.extend(true, {type: type}, self.options, element.data()),
419					self.$element
420				);
421
422		element.data( 'HSMenuItem', Item );
423		this._items = this._items.add( element );
424
425	};
426
427	/**
428	 * Destroys of desktop behavior, then makes initialization of mobile behavior.
429	 * 
430	 * @protected 
431	 */
432	MegaMenu.prototype.initMobileBehavior = function() {
433
434		var self = this;
435
436		this.state = 'mobile';
437
438		this.$element
439				.off('.HSMegaMenu')
440				.addClass( this.options.classMap.mobileState.slice(1) )
441				.on('click.HSMegaMenu', self.options.classMap.hasSubMenu + ' > a, ' + self.options.classMap.hasMegaMenu + ' > a', function(e){
442
443					var $this = $(this).parent(),
444							MenuItemInstance;
445
446					// Lazy initialization
447					if( !$this.data('HSMenuItem') ) {
448						self.initMenuItem( $this, self.getType( $this ) );
449					}
450
451					self.closeAll( $this.parents( self.itemsSelector ).add($this) );
452
453					MenuItemInstance = $this
454							 .data('HSMenuItem');
455
456					console.log(MenuItemInstance.isOpened);
457
458					if(MenuItemInstance.isOpened) {
459						MenuItemInstance.mobileHide();
460					}
461					else {
462						MenuItemInstance.mobileShow();
463					}
464
465					e.preventDefault();
466					e.stopPropagation();
467
468				})
469				.find( this.itemsSelector )
470				.not(
471					this.options.classMap.hasSubMenuActive  + ',' + 
472					this.options.classMap.hasMegaMenuActive
473				)
474				.children(
475					this.options.classMap.subMenu + ',' +
476					this.options.classMap.megaMenu
477				)
478				.hide();
479
480	};
481
482	/**
483	 * Destroys of mobile behavior, then makes initialization of desktop behavior.
484	 * 
485	 * @protected
486	 */
487	MegaMenu.prototype.initDesktopBehavior = function() {
488
489		this.state = 'desktop';
490
491		this.$element
492				.removeClass(this.options.classMap.mobileState.slice(1))
493				.off('.HSMegaMenu')
494				.find( this.itemsSelector )
495				.not(
496					this.options.classMap.hasSubMenuActive  + ',' + 
497					this.options.classMap.hasMegaMenuActive
498				)
499				.children(
500					this.options.classMap.subMenu + ',' +
501					this.options.classMap.megaMenu
502				)
503				.hide();
504
505		this.bindEvents();
506
507	};
508
509	/**
510	 * Hides all of opened submenus/megamenus.
511	 *
512	 * @param {jQuery} except - collection of elements, which shouldn't be closed.
513	 * @return {jQuery}
514	 * @public
515	 */
516	MegaMenu.prototype.closeAll = function(except) {
517
518		var self = this;
519
520		return this._items.not(except && except.length ? except : $()).each(function(i, el){
521
522			$(el)
523				.removeClass('hs-event-prevented')
524				.data('HSMenuItem')[self.state == 'mobile' ? 'mobileHide' : 'hide']();
525
526		});
527
528	};
529
530	/**
531	 * Returns type of sub menu based on specified menu item.
532	 * 
533	 * @param {jQuery} item
534	 * @return {String|null}
535	 * @public
536	 */
537	MegaMenu.prototype.getType = function( item ) {
538
539		if(!item || !item.length) return null;
540
541		return item.hasClass(this.options.classMap.hasSubMenu.slice(1)) ? 'sub-menu' : 
542					 (item.hasClass(this.options.classMap.hasMegaMenu.slice(1)) ? 'mega-menu' : null);
543
544	};
545
546	/**
547	 * Returns current menu state.
548	 *
549	 * @return {String}
550	 * @public
551	 */
552	MegaMenu.prototype.getState = function() {
553		return this.state;
554	};
555
556	/**
557	 * Updates bounds of all menu items.
558	 * 
559	 * @return {jQuery}
560	 * @public
561	 */
562	MegaMenu.prototype.refresh = function() {
563
564		return this._items.add( this._tempChain ).each(function(i, el){
565			$(el).data('HSMenuItem')._updateMenuBounds();
566		});
567
568	};
569
570
571	/**
572	 * Creates a mega-menu element.
573	 * 
574	 * @param {jQuery} element 
575	 * @param {jQuery} menu
576	 * @param {Object} options
577	 * @param {jQuery} container
578	 * @constructor 
579	 */
580	function MenuItem(element, menu, options, container) {
581
582		var self = this;
583
584		/**
585		 * Current menu item element.
586		 * 
587		 * @public
588		 */
589		this.$element = element;
590
591		/**
592		 * Current mega menu element.
593		 * 
594		 * @public
595		 */
596		this.menu = menu;
597
598		/**
599		 * Item options.
600		 * 
601		 * @public
602		 */
603		this.options = options;
604
605
606		/**
607		 * MegaMenu container.
608		 * 
609		 * @public 
610		 */
611		this.$container = container;
612
613		Object.defineProperties(this, {
614
615			/**
616			 * Contains css class of menu item element.
617			 *
618			 * @public 
619			 */
620			itemClass: {
621				get: function() {
622					return self.options.type === 'mega-menu' ? 
623								 self.options.classMap.hasMegaMenu : 
624								 self.options.classMap.hasSubMenu;
625				}
626			},
627
628			/**
629			 * Contains css active-class of menu item element.
630			 *
631			 * @public 
632			 */
633			activeItemClass: {
634				get: function() {
635					return self.options.type === 'mega-menu' ? 
636								 self.options.classMap.hasMegaMenuActive :
637								 self.options.classMap.hasSubMenuActive;
638				}
639			},
640
641			/**
642			 * Contains css class of menu element.
643			 *
644			 * @public 
645			 */
646			menuClass: {
647				get: function() {
648					return self.options.type === 'mega-menu' ? 
649								 self.options.classMap.megaMenu :
650								 self.options.classMap.subMenu;
651				}
652			},
653
654			isOpened: {
655				get: function() {
656					return this.$element.hasClass(this.activeItemClass.slice(1));
657				}
658			}
659
660		});
661
662
663		this.menu.addClass('animated').on('click.HSMegaMenu', function(e){
664			self._updateMenuBounds();
665		});
666
667		if( this.$element.data('max-width') ) this.menu.css('max-width', this.$element.data('max-width') );
668		if( this.$element.data('position') ) this.menu.addClass( 'hs-position-' + this.$element.data('position') );
669
670
671		if( this.options.animationOut ) {
672
673			this.menu.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(e) {
674
675				if(self.menu.hasClass(self.options.animationOut)) {
676					self.$element.removeClass(self.activeItemClass.slice(1));
677					self.options.afterClose.call(self, self.$element, self.menu);
678				}
679
680				if(self.menu.hasClass(self.options.animationIn)) {
681					self.options.afterOpen.call(self, self.$element, self.menu);
682				}
683
684				e.stopPropagation();
685				e.preventDefault();
686			});
687		}
688		
689	}
690
691	/**
692	 * Shows the mega-menu item.
693	 * 
694	 * @public 
695	 * @return {MenuItem}
696	 */
697	MenuItem.prototype.show = function() {
698		if( !this.menu.length ) return this;
699
700		this.$element.addClass(this.activeItemClass.slice(1));
701
702		if(this.options.direction == 'horizontal') this.smartPosition( this.menu, this.options );
703		
704		this._updateMenuBounds();
705
706		if(this.options.animationOut) {
707			this.menu.removeClass(this.options.animationOut);
708		}
709		else {
710			this.options.afterOpen.call(this, this.$element, this.menu);
711		}
712
713		if(this.options.animationIn) this.menu.addClass(this.options.animationIn);
714
715		return this;
716	}
717
718	/**
719	 * Hides the mega-menu item.
720	 * 
721	 * @public 
722	 * @return {MenuItem}
723	 */
724	MenuItem.prototype.hide = function() {
725
726		var self = this;
727
728		if( !this.menu.length ) return this;
729
730		if(!this.options.animationOut) {
731			this.$element.removeClass(this.activeItemClass.slice(1));
732		}
733
734		if(this.options.animationIn) this.menu.removeClass(this.options.animationIn);
735		if(this.options.animationOut) {
736			this.menu
737					.addClass(this.options.animationOut);
738		}
739		else{
740			this.options.afterClose.call(this, this.$element, this.menu);
741		}
742
743		return this;
744	}
745
746	/**
747	 * Shows the mega-menu item.
748	 * 
749	 * @public
750	 * @return {MenuItem}
751	 */
752	MenuItem.prototype.mobileShow = function() {
753		var self = this;
754
755		if( !this.menu.length ) return this;
756
757
758
759		this.menu
760				.removeClass( this.options.animationIn )
761				.removeClass( this.options.animationOut )
762				.stop()
763				.slideDown({
764					duration: self.options.mobileSpeed,
765					easing: self.options.mobileEasing,
766					complete: function() {
767						self.options.afterOpen.call(self, self.$element, self.menu);
768					}
769				});
770
771		this.$element.addClass(this.activeItemClass.slice(1));
772
773		return this;
774	};
775
776	/**
777	 * Hides the mega-menu item.
778	 * 
779	 * @public
780	 * @return {MenuItem}
781	 */
782	MenuItem.prototype.mobileHide = function() {
783		var self = this;
784
785		if( !this.menu.length ) return this;
786		
787		this.menu.stop().slideUp({
788			duration: self.options.mobileSpeed,
789			easing: self.options.mobileEasing,
790			complete: function() {
791				self.options.afterClose.call(self, self.$element, self.menu);
792			}
793		});
794
795		this.$element.removeClass(this.activeItemClass.slice(1));
796
797		return this;
798	};
799
800	/**
801	 * Check, if element is in viewport.
802	 * 
803	 * @param {jQuery} element
804	 * @param {Object} options
805	 * @public
806	 */
807	MenuItem.prototype.smartPosition = function( element, options ) {
808
809		MenuItem.smartPosition(element, options);
810
811	};
812
813	/**
814	 * Check, if element is in viewport.
815	 * 
816	 * @param {jQuery} element
817	 * @param {Object} options
818	 * @static
819	 * @public
820	 */
821	MenuItem.smartPosition = function( element, options ) {
822		if(!element && !element.length) return;
823
824		var $w = $(window);
825
826		element.removeClass('hs-reversed');
827
828		if(!options.rtl) {
829
830			if( element.offset().left + element.outerWidth() > $w.width() ) {
831				element.addClass('hs-reversed');
832			}
833
834		}
835		else {
836
837			if(element.offset().left < 0) {
838				element.addClass('hs-reversed');
839			}
840
841		}
842	};
843
844	/**
845	 * Updates bounds of current opened menu.
846	 *
847	 * @private
848	 */
849	MenuItem.prototype._updateMenuBounds = function() {
850
851		var width = 'auto';
852
853		if( this.options.direction == 'vertical' && this.options.type == 'mega-menu' ) {
854
855			if( this.$container && this.$container.data('HSMegaMenu').get
855State() == 'desktop' ) {
856				if( !this.options.pageContainer.length ) this.options.pageContainer = $('body');
857				width = this.options.pageContainer.outerWidth() * (1 - this.options.sideBarRatio);
858			}
859			else {
860				width = 'auto';
861			}
862
863
864			this.menu.css({
865				'width': width,
866				'height': 'auto'
867			});
868
869			if( this.menu.outerHeight() > this.$container.outerHeight() ) {
870				return;
871			}
872
873			this.menu.css('height', '100%');
874		}
875
876	};
877
878	/**
879	 * The jQuery plugin for the MegaMenu.
880	 *
881	 * @public
882	 */
883	$.fn.HSMegaMenu = function(options) {
884
885		return this.each(function(i, el) {
886
887			var $this = $(this);
888
889			if(!$this.data('HSMegaMenu')) {
890				$this.data('HSMegaMenu', new MegaMenu($this, options));
891			}
892
893		});
894
895	};
896
897	/**
898	 * Helper function for detect touch events in the environment.
899	 * 
900	 * @return {Boolean}
901	 * @private 
902	 */
903	function _isTouch() {
904		return ('ontouchstart' in window);
905	}
906
907})(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.