PageSourceSearch

https://sellersprite.jp/assets-frontend/vendor/hs-megamenu/src/hs.megamenu.js

js sellersprite.jp collected 2026-10-08 14:04:54 UTC 18,734 bytes, 906 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(window.innerWidth <= self.options.breakpoint && self.state == 'desktop') self.initMobileBehavior();
160				else if( window.innerWidth > 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(window.innerWidth <= this.options.breakpoint) this.initMobileBehavior();
180		else if( window.innerWidth > 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			this.$element
214				.on(
215					'mouseenter.HSMegaMenu',
216					this.options.classMap.hasMegaMenu + ':not([data-event="click"]),' +
217					this.options.classMap.hasSubMenu + ':not([data-event="click"])',
218					function(e) {
219
220						var $this = $(this),
221								$chain = $this.parents( self.itemsSelector);
222
223						// Lazy initialization
224						if( !$this.data('HSMenuItem') ) {
225							self.initMenuItem( $this, self.getType( $this ) );
226						}
227
228						$chain = $chain.add($this);
229
230						self.closeAll( $chain );
231
232						$chain.each(function(i, el){
233
234							var HSMenuItem = $(el).data('HSMenuItem');
235
236							if(HSMenuItem.hideTimeOutId) clearTimeout(HSMenuItem.hideTimeOutId);
237							HSMenuItem.show();
238
239						});
240
241						self._items = self._items.not( $chain );
242						self._tempChain = $chain;
243
244						e.preventDefault();
245						e.stopPropagation();
246					}
247				)
248				.on(
249					'mouseleave.HSMegaMenu',
250					this.options.classMap.hasMegaMenu + ':not([data-event="click"]),' +
251					this.options.classMap.hasSubMenu + ':not([data-event="click"])',
252					function(e) {
253
254						var $this = $(this),
255								HSMenuItem = $this.data('HSMenuItem'),
256								$chain = $(e.relatedTarget).parents( self.itemsSelector );
257
258						HSMenuItem.hideTimeOutId = setTimeout( function(){
259							self.closeAll( $chain );
260						}, self.options.hideTimeOut );
261
262						self._items = self._items.add( self._tempChain );
263						self._tempChain = null;
264
265						e.preventDefault();
266						e.stopPropagation();
267					}
268				)
269				.on(
270					'click.HSMegaMenu',
271					this.options.classMap.hasMegaMenu + '[data-event="click"] > a, ' +
272					this.options.classMap.hasSubMenu + '[data-event="click"] > a',
273					function(e) {
274
275						var $this = $(this).parent('[data-event="click"]'),
276								HSMenuItem;
277
278						// Lazy initialization
279						if(!$this.data('HSMenuItem')) {
280							self.initMenuItem( $this, self.getType( $this ) );
281						}
282
283
284						self.closeAll( $this.add(
285							$this.parents(self.itemsSelector)
286						) );
287
288						HSMenuItem = $this
289							.data('HSMenuItem');
290
291						if(HSMenuItem.isOpened) {
292							HSMenuItem.hide();
293						}
294						else{
295							HSMenuItem.show();
296						}
297
298
299						e.preventDefault();
300						e.stopPropagation();
301
302					}
303				);
304		}
305		// Click case
306		else {
307
308			this.$element
309				.on(
310					'click.HSMegaMenu',
311					(_isTouch() ?
312								 this.options.classMap.hasMegaMenu + ' > a, ' +
313								 this.options.classMap.hasSubMenu + ' > a' :
314								 this.options.classMap.hasMegaMenu + ':not([data-event="hover"]) > a,' +
315								 this.options.classMap.hasSubMenu + ':not([data-event="hover"]) > a'),
316					function(e) {
317
318						var $this = $(this).parent(),
319								HSMenuItem,
320								$parents = $this.parents(self.itemsSelector);
321
322						// Lazy initialization
323						if(!$this.data('HSMenuItem')) {
324							self.initMenuItem( $this, self.getType( $this ) );
325						}
326
327						self.closeAll( $this.add(
328							$this.parents( self.itemsSelector )
329						) );
330
331						HSMenuItem = $this
332							.addClass('hs-event-prevented')
333							.data('HSMenuItem');
334
335						if(HSMenuItem.isOpened) {
336							HSMenuItem.hide();
337						}
338						else{
339							HSMenuItem.show();
340						}
341
342						e.preventDefault();
343						e.stopPropagation();
344					}
345				);
346
347				if(!_isTouch()) {
348					this.$element
349						.on(
350							'mouseenter.HSMegaMenu',
351							this.options.classMap.hasMegaMenu + '[data-event="hover"],' +
352							this.options.classMap.hasSubMenu + '[data-event="hover"]',
353							function(e) {
354
355								var $this = $(this),
356										$parents = $this.parents( self.itemsSelector);
357
358								// Lazy initialization
359								if( !$this.data('HSMenuItem') ) {
360									self.initMenuItem( $this, self.getType( $this ) );
361								}
362
363								self.closeAll( $this.add($parents) );
364
365								$parents.add($this).each(function(i, el){
366
367									var HSMenuItem = $(el).data('HSMenuItem');
368
369									if(HSMenuItem.hideTimeOutId) clearTimeout(HSMenuItem.hideTimeOutId);
370									HSMenuItem.show();
371
372								});
373
374								e.preventDefault();
375								e.stopPropagation();
376							}
377						)
378						.on(
379							'mouseleave.HSMegaMenu',
380							this.options.classMap.hasMegaMenu + '[data-event="hover"],' +
381							this.options.classMap.hasSubMenu + '[data-event="hover"]',
382							function(e) {
383
384								var $this = $(this),
385										HSMenuItem = $this.data('HSMenuItem');
386
387								HSMenuItem.hideTimeOutId = setTimeout( function(){
388
389									self.closeAll(
390										$(e.relatedTarget).parents(self.itemsSelector)
391									);
392
393								}, self.options.hideTimeOut );
394
395								e.preventDefault();
396								e.stopPropagation();
397							}
398						)
399				}
400		}
401
402	};
403
404	/**
405	 * Initialization of certain menu item.
406	 *
407	 * @protected
408	 */
409	MegaMenu.prototype.initMenuItem = function(element, type) {
410
411		var self = this,
412				Item = new MenuItem(
413					element,
414					element.children(
415						self.options.classMap[type === 'mega-menu' ? 'megaMenu' : 'subMenu']
416					),
417					$.extend(true, {type: type}, self.options, element.data()),
418					self.$element
419				);
420
421		element.data( 'HSMenuItem', Item );
422		this._items = this._items.add( element );
423
424	};
425
426	/**
427	 * Destroys of desktop behavior, then makes initialization of mobile behavior.
428	 *
429	 * @protected
430	 */
431	MegaMenu.prototype.initMobileBehavior = function() {
432
433		var self = this;
434
435		this.state = 'mobile';
436
437		this.$element
438				.off('.HSMegaMenu')
439				.addClass( this.options.classMap.mobileState.slice(1) )
440				.on('click.HSMegaMenu', self.options.classMap.hasSubMenu + ' > a, ' + self.options.classMap.hasMegaMenu + ' > a', function(e){
441
442					var $this = $(this).parent(),
443							MenuItemInstance;
444
445					// Lazy initialization
446					if( !$this.data('HSMenuItem') ) {
447						self.initMenuItem( $this, self.getType( $this ) );
448					}
449
450					self.closeAll( $this.parents( self.itemsSelector ).add($this) );
451
452					MenuItemInstance = $this
453							 .data('HSMenuItem');
454
455					console.log(MenuItemInstance.isOpened);
456
457					if(MenuItemInstance.isOpened) {
458						MenuItemInstance.mobileHide();
459					}
460					else {
461						MenuItemInstance.mobileShow();
462					}
463
464					e.preventDefault();
465					e.stopPropagation();
466
467				})
468				.find( this.itemsSelector )
469				.not(
470					this.options.classMap.hasSubMenuActive  + ',' +
471					this.options.classMap.hasMegaMenuActive
472				)
473				.children(
474					this.options.classMap.subMenu + ',' +
475					this.options.classMap.megaMenu
476				)
477				.hide();
478
479	};
480
481	/**
482	 * Destroys of mobile behavior, then makes initialization of desktop behavior.
483	 *
484	 * @protected
485	 */
486	MegaMenu.prototype.initDesktopBehavior = function() {
487
488		this.state = 'desktop';
489
490		this.$element
491				.removeClass(this.options.classMap.mobileState.slice(1))
492				.off('.HSMegaMenu')
493				.find( this.itemsSelector )
494				.not(
495					this.options.classMap.hasSubMenuActive  + ',' +
496					this.options.classMap.hasMegaMenuActive
497				)
498				.children(
499					this.options.classMap.subMenu + ',' +
500					this.options.classMap.megaMenu
501				)
502				.hide();
503
504		this.bindEvents();
505
506	};
507
508	/**
509	 * Hides all of opened submenus/megamenus.
510	 *
511	 * @param {jQuery} except - collection of elements, which shouldn't be closed.
512	 * @return {jQuery}
513	 * @public
514	 */
515	MegaMenu.prototype.closeAll = function(except) {
516
517		var self = this;
518
519		return this._items.not(except && except.length ? except : $()).each(function(i, el){
520
521			$(el)
522				.removeClass('hs-event-prevented')
523				.data('HSMenuItem')[self.state == 'mobile' ? 'mobileHide' : 'hide']();
524
525		});
526
527	};
528
529	/**
530	 * Returns type of sub menu based on specified menu item.
531	 *
532	 * @param {jQuery} item
533	 * @return {String|null}
534	 * @public
535	 */
536	MegaMenu.prototype.getType = function( item ) {
537
538		if(!item || !item.length) return null;
539
540		return item.hasClass(this.options.classMap.hasSubMenu.slice(1)) ? 'sub-menu' :
541					 (item.hasClass(this.options.classMap.hasMegaMenu.slice(1)) ? 'mega-menu' : null);
542
543	};
544
545	/**
546	 * Returns current menu state.
547	 *
548	 * @return {String}
549	 * @public
550	 */
551	MegaMenu.prototype.getState = function() {
552		return this.state;
553	};
554
555	/**
556	 * Updates bounds of all menu items.
557	 *
558	 * @return {jQuery}
559	 * @public
560	 */
561	MegaMenu.prototype.refresh = function() {
562
563		return this._items.add( this._tempChain ).each(function(i, el){
564			$(el).data('HSMenuItem')._updateMenuBounds();
565		});
566
567	};
568
569
570	/**
571	 * Creates a mega-menu element.
572	 *
573	 * @param {jQuery} element
574	 * @param {jQuery} menu
575	 * @param {Object} options
576	 * @param {jQuery} container
577	 * @constructor
578	 */
579	function MenuItem(element, menu, options, container) {
580
581		var self = this;
582
583		/**
584		 * Current menu item element.
585		 *
586		 * @public
587		 */
588		this.$element = element;
589
590		/**
591		 * Current mega menu element.
592		 *
593		 * @public
594		 */
595		this.menu = menu;
596
597		/**
598		 * Item options.
599		 *
600		 * @public
601		 */
602		this.options = options;
603
604
605		/**
606		 * MegaMenu container.
607		 *
608		 * @public
609		 */
610		this.$container = container;
611
612		Object.defineProperties(this, {
613
614			/**
615			 * Contains css class of menu item element.
616			 *
617			 * @public
618			 */
619			itemClass: {
620				get: function() {
621					return self.options.type === 'mega-menu' ?
622								 self.options.classMap.hasMegaMenu :
623								 self.options.classMap.hasSubMenu;
624				}
625			},
626
627			/**
628			 * Contains css active-class of menu item element.
629			 *
630			 * @public
631			 */
632			activeItemClass: {
633				get: function() {
634					return self.options.type === 'mega-menu' ?
635								 self.options.classMap.hasMegaMenuActive :
636								 self.options.classMap.hasSubMenuActive;
637				}
638			},
639
640			/**
641			 * Contains css class of menu element.
642			 *
643			 * @public
644			 */
645			menuClass: {
646				get: function() {
647					return self.options.type === 'mega-menu' ?
648								 self.options.classMap.megaMenu :
649								 self.options.classMap.subMenu;
650				}
651			},
652
653			isOpened: {
654				get: function() {
655					return this.$element.hasClass(this.activeItemClass.slice(1));
656				}
657			}
658
659		});
660
661
662		this.menu.addClass('animated').on('click.HSMegaMenu', function(e){
663			self._updateMenuBounds();
664		});
665
666		if( this.$element.data('max-width') ) this.menu.css('max-width', this.$element.data('max-width') );
667		if( this.$element.data('position') ) this.menu.addClass( 'hs-position-' + this.$element.data('position') );
668
669
670		if( this.options.animationOut ) {
671
672			this.menu.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(e) {
673
674				if(self.menu.hasClass(self.options.animationOut)) {
675					self.$element.removeClass(self.activeItemClass.slice(1));
676					self.options.afterClose.call(self, self.$element, self.menu);
677				}
678
679				if(self.menu.hasClass(self.options.animationIn)) {
680					self.options.afterOpen.call(self, self.$element, self.menu);
681				}
682
683				e.stopPropagation();
684				e.preventDefault();
685			});
686		}
687
688	}
689
690	/**
691	 * Shows the mega-menu item.
692	 *
693	 * @public
694	 * @return {MenuItem}
695	 */
696	MenuItem.prototype.show = function() {
697		if( !this.menu.length ) return this;
698
699		this.$element.addClass(this.activeItemClass.slice(1));
700
701		if(this.options.direction == 'horizontal') this.smartPosition( this.menu, this.options );
702
703		this._updateMenuBounds();
704
705		if(this.options.animationOut) {
706			this.menu.removeClass(this.options.animationOut);
707		}
708		else {
709			this.options.afterOpen.call(this, this.$element, this.menu);
710		}
711
712		if(this.options.animationIn) this.menu.addClass(this.options.animationIn);
713
714		return this;
715	}
716
717	/**
718	 * Hides the mega-menu item.
719	 *
720	 * @public
721	 * @return {MenuItem}
722	 */
723	MenuItem.prototype.hide = function() {
724
725		var self = this;
726
727		if( !this.menu.length ) return this;
728
729		if(!this.options.animationOut) {
730			this.$element.removeClass(this.activeItemClass.slice(1));
731		}
732
733		if(this.options.animationIn) this.menu.removeClass(this.options.animationIn);
734		if(this.options.animationOut) {
735			this.menu
736					.addClass(this.options.animationOut);
737		}
738		else{
739			this.options.afterClose.call(this, this.$element, this.menu);
740		}
741
742		return this;
743	}
744
745	/**
746	 * Shows the mega-menu item.
747	 *
748	 * @public
749	 * @return {MenuItem}
750	 */
751	MenuItem.prototype.mobileShow = function() {
752		var self = this;
753
754		if( !this.menu.length ) return this;
755
756
757
758		this.menu
759				.removeClass( this.options.animationIn )
760				.removeClass( this.options.animationOut )
761				.stop()
762				.slideDown({
763					duration: self.options.mobileSpeed,
764					easing: self.options.mobileEasing,
765					complete: function() {
766						self.options.afterOpen.call(self, self.$element, self.menu);
767					}
768				});
769
770		this.$element.addClass(this.activeItemClass.slice(1));
771
772		return this;
773	};
774
775	/**
776	 * Hides the mega-menu item.
777	 *
778	 * @public
779	 * @return {MenuItem}
780	 */
781	MenuItem.prototype.mobileHide = function() {
782		var self = this;
783
784		if( !this.menu.length ) return this;
785
786		this.menu.stop().slideUp({
787			duration: self.options.mobileSpeed,
788			easing: self.options.mobileEasing,
789			complete: function() {
790				self.options.afterClose.call(self, self.$element, self.menu);
791			}
792		});
793
794		this.$element.removeClass(this.activeItemClass.slice(1));
795
796		return this;
797	};
798
799	/**
800	 * Check, if element is in viewport.
801	 *
802	 * @param {jQuery} element
803	 * @param {Object} options
804	 * @public
805	 */
806	MenuItem.prototype.smartPosition = function( element, options ) {
807
808		MenuItem.smartPosition(element, options);
809
810	};
811
812	/**
813	 * Check, if element is in viewport.
814	 *
815	 * @param {jQuery} element
816	 * @param {Object} options
817	 * @static
818	 * @public
819	 */
820	MenuItem.smartPosition = function( element, options ) {
821		if(!element && !element.length) return;
822
823		var $w = $(window);
824
825		element.removeClass('hs-reversed');
826
827		if(!options.rtl) {
828
829			if( element.offset().left + element.outerWidth() > window.innerWidth ) {
830				element.addClass('hs-reversed');
831			}
832
833		}
834		else {
835
836			if(element.offset().left < 0) {
837				element.addClass('hs-reversed');
838			}
839
840		}
841	};
842
843	/**
844	 * Updates bounds of current opened menu.
845	 *
846	 * @private
847	 */
848	MenuItem.prototype._updateMenuBounds = function() {
849
850		var width = 'auto';
851
852		if( this.options.direction == 'vertical' && this.options.type == 'mega-menu' ) {
853
854			if( this.$container && this.$container.data('HSMegaMenu').get
854State() == 'desktop' ) {
855				if( !this.options.pageContainer.length ) this.options.pageContainer = $('body');
856				width = this.options.pageContainer.outerWidth() * (1 - this.options.sideBarRatio);
857			}
858			else {
859				width = 'auto';
860			}
861
862
863			this.menu.css({
864				'width': width,
865				'height': 'auto'
866			});
867
868			if( this.menu.outerHeight() > this.$container.outerHeight() ) {
869				return;
870			}
871
872			this.menu.css('height', '100%');
873		}
874
875	};
876
877	/**
878	 * The jQuery plugin for the MegaMenu.
879	 *
880	 * @public
881	 */
882	$.fn.HSMegaMenu = function(options) {
883
884		return this.each(function(i, el) {
885
886			var $this = $(this);
887
888			if(!$this.data('HSMegaMenu')) {
889				$this.data('HSMegaMenu', new MegaMenu($this, options));
890			}
891
892		});
893
894	};
895
896	/**
897	 * Helper function for detect touch events in the environment.
898	 *
899	 * @return {Boolean}
900	 * @private
901	 */
902	function _isTouch() {
903		return ('ontouchstart' in window);
904	}
905
906})(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.