PageSourceSearch

https://www.achbooks.co.il/vault/assets/js/menu.js

js achbooks.co.il collected 2026-09-27 02:52:16 UTC 45,305 bytes, 1,210 lines download raw bytes

1
2(function(factory) {
3	if (typeof define === 'function' && define.amd) {
4		// AMD
5		define(['jquery'], factory);
6	} else if (typeof module === 'object' && typeof module.exports === 'object') {
7		// CommonJS
8		module.exports = factory(require('jquery'));
9	} else {
10		// Global jQuery
11		factory(jQuery);
12	}
13} (function($) {
14
15	var menuTrees = [],
16		mouse = false, // optimize for touch by default - we will detect for mouse input
17		touchEvents = 'ontouchstart' in window, // we use this just to choose between toucn and pointer events, not for touch screen detection
18		mouseDetectionEnabled = false,
19		requestAnimationFrame = window.requestAnimationFrame || function(callback) { return setTimeout(callback, 1000 / 60); },
20		cancelAnimationFrame = window.cancelAnimationFrame || function(id) { clearTimeout(id); },
21		canAnimate = !!$.fn.animate;
22
23	// Handle detection for mouse input (i.e. desktop browsers, tablets with a mouse, etc.)
24	function initMouseDetection(disable) {
25		var eNS = '.smartmenus_mouse';
26		if (!mouseDetectionEnabled && !disable) {
27			// if we get two consecutive mousemoves within 2 pixels from each other and within 300ms, we assume a real mouse/cursor is present
28			// in practice, this seems like impossible to trick unintentianally with a real mouse and a pretty safe detection on touch devices (even with older browsers that do not support touch events)
29			var firstTime = true,
30				lastMove = null,
31				events = {
32					'mousemove': function(e) {
33						var thisMove = { x: e.pageX, y: e.pageY, timeStamp: new Date().getTime() };
34						if (lastMove) {
35							var deltaX = Math.abs(lastMove.x - thisMove.x),
36								deltaY = Math.abs(lastMove.y - thisMove.y);
37		 					if ((deltaX > 0 || deltaY > 0) && deltaX <= 2 && deltaY <= 2 && thisMove.timeStamp - lastMove.timeStamp <= 300) {
38								mouse = true;
39								// if this is the first check after page load, check if we are not over some item by chance and call the mouseenter handler if yes
40								if (firstTime) {
41									var $a = $(e.target).closest('a');
42									if ($a.is('a')) {
43										$.each(menuTrees, function() {
44											if ($.contains(this.$root[0], $a[0])) {
45												this.itemEnter({ currentTarget: $a[0] });
46												return false;
47											}
48										});
49									}
50									firstTime = false;
51								}
52							}
53						}
54						lastMove = thisMove;
55					}
56				};
57			events[touchEvents ? 'touchstart' : 'pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut'] = function(e) {
58				if (isTouchEvent(e.originalEvent)) {
59					mouse = false;
60				}
61			};
62			$(document).on(getEventsNS(events, eNS));
63			mouseDetectionEnabled = true;
64		} else if (mouseDetectionEnabled && disable) {
65			$(document).off(eNS);
66			mouseDetectionEnabled = false;
67		}
68	}
69
70	function isTouchEvent(e) {
71		return !/^(4|mouse)$/.test(e.pointerType);
72	}
73
74	// returns a jQuery on() ready object
75	function getEventsNS(events, eNS) {
76		if (!eNS) {
77			eNS = '';
78		}
79		var eventsNS = {};
80		for (var i in events) {
81			eventsNS[i.split(' ').join(eNS + ' ') + eNS] = events[i];
82		}
83		return eventsNS;
84	}
85
86	$.SmartMenus = function(elm, options) {
87		this.$root = $(elm);
88		this.opts = options;
89		this.rootId = ''; // internal
90		this.accessIdPrefix = '';
91		this.$subArrow = null;
92		this.activatedItems = []; // stores last activated A's for each level
93		this.visibleSubMenus = []; // stores visible sub menus UL's (might be in no particular order)
94		this.showTimeout = 0;
95		this.hideTimeout = 0;
96		this.scrollTimeout = 0;
97		this.clickActivated = false;
98		this.focusActivated = false;
99		this.zIndexInc = 0;
100		this.idInc = 0;
101		this.$firstLink = null; // we'll use these for some tests
102		this.$firstSub = null; // at runtime so we'll cache them
103		this.disabled = false;
104		this.$disableOverlay = null;
105		this.$touchScrollingSub = null;
106		this.cssTransforms3d = 'perspective' in elm.style || 'webkitPerspective' in elm.style;
107		this.wasCollapsible = false;
108		this.init();
109	};
110
111	$.extend($.SmartMenus, {
112		hideAll: function() {
113			$.each(menuTrees, function() {
114				this.menuHideAll();
115			});
116		},
117		destroy: function() {
118			while (menuTrees.length) {
119				menuTrees[0].destroy();
120			}
121			initMouseDetection(true);
122		},
123		prototype: {
124			init: function(refresh) {
125				var self = this;
126
127				if (!refresh) {
128					menuTrees.push(this);
129
130					this.rootId = (new Date().getTime() + Math.random() + '').replace(/\D/g, '');
131					this.accessIdPrefix = 'sm-' + this.rootId + '-';
132
133					if (this.$root.hasClass('sm-rtl')) {
134						this.opts.rightToLeftSubMenus = true;
135					}
136
137					// init root (main menu)
138					var eNS = '.smartmenus';
139					this.$root
140						.data('smartmenus', this)
141						.attr('data-smartmenus-id', this.rootId)
142						.dataSM('level', 1)
143						.on(getEventsNS({
144							'mouseover focusin': $.proxy(this.rootOver, this),
145							'mouseout focusout': $.proxy(this.rootOut, this),
146							'keydown': $.proxy(this.rootKeyDown, this)
147						}, eNS))
148						.on(getEventsNS({
149							'mouseenter': $.proxy(this.itemEnter, this),
150							'mouseleave': $.proxy(this.itemLeave, this),
151							'mousedown': $.proxy(this.itemDown, this),
152							'focus': $.proxy(this.itemFocus, this),
153							'blur': $.proxy(this.itemBlur, this),
154							'click': $.proxy(this.itemClick, this)
155						}, eNS), 'a');
156
157					// hide menus on tap or click outside the root UL
158					eNS += this.rootId;
159					if (this.opts.hideOnClick) {
160						$(document).on(getEventsNS({
161							'touchstart': $.proxy(this.docTouchStart, this),
162							'touchmove': $.proxy(this.docTouchMove, this),
163							'touchend': $.proxy(this.docTouchEnd, this),
164							// for Opera Mobile < 11.5, webOS browser, etc. we'll check click too
165							'click': $.proxy(this.docClick, this)
166						}, eNS));
167					}
168					// hide sub menus on resize
169					$(window).on(getEventsNS({ 'resize orientationchange': $.proxy(this.winResize, this) }, eNS));
170
171					if (this.opts.subIndicators) {
172						this.$subArrow = $('<span/>').addClass('sub-arrow');
173						if (this.opts.subIndicatorsText) {
174							this.$subArrow.html(this.opts.subIndicatorsText);
175						}
176					}
177
178					// make sure mouse detection is enabled
179					initMouseDetection();
180				}
181
182				// init sub menus
183				this.$firstSub = this.$root.find('ul').each(function() { self.menuInit($(this)); }).eq(0);
184
185				this.$firstLink = this.$root.find('a').eq(0);
186
187				// find current item
188				if (this.opts.markCurrentItem) {
189					var reDefaultDoc = /(index|default)\.[^#\?\/]*/i,
190						reHash = /#.*/,
191						locHref = window.location.href.replace(reDefaultDoc, ''),
192						locHrefNoHash = locHref.replace(reHash, '');
193					this.$root.find('a:not(.mega-menu a)').each(function() {
194						var href = this.href.replace(reDefaultDoc, ''),
195							$this = $(this);
196						if (href == locHref || href == locHrefNoHash) {
197							$this.addClass('current');
198							if (self.opts.markCurrentTree) {
199								$this.parentsUntil('[data-smartmenus-id]', 'ul').each(function() {
200									$(this).dataSM('parent-a').addClass('current');
201								});
202							}
203						}
204					});
205				}
206
207				// save initial state
208				this.wasCollapsible = this.isCollapsible();
209			},
210			destroy: function(refresh) {
211				if (!refresh) {
212					var eNS = '.smartmenus';
213					this.$root
214						.removeData('smartmenus')
215						.removeAttr('data-smartmenus-id')
216						.removeDataSM('level')
217						.off(eNS);
218					eNS += this.rootId;
219					$(document).off(eNS);
220					$(window).off(eNS);
221					if (this.opts.subIndicators) {
222						this.$subArrow = null;
223					}
224				}
225				this.menuHideAll();
226				var self = this;
227				this.$root.find('ul').each(function() {
228						var $this = $(this);
229						if ($this.dataSM('scroll-arrows')) {
230							$this.dataSM('scroll-arrows').remove();
231						}
232						if ($this.dataSM('shown-before')) {
233							if (self.opts.subMenusMinWidth || self.opts.subMenusMaxWidth) {
234								$this.css({ width: '', minWidth: '', maxWidth: '' }).removeClass('sm-nowrap');
235							}
236							if ($this.dataSM('scroll-arrows')) {
237								$this.dataSM('scroll-arrows').remove();
238							}
239							$this.css({ zIndex: '', top: '', left: '', marginLeft: '', marginTop: '', display: '' });
240						}
241						if (($this.attr('id') || '').indexOf(self.accessIdPrefix) == 0) {
242							$this.removeAttr('id');
243						}
244					})
245					.removeDataSM('in-mega')
246					.removeDataSM('shown-before')
247					.removeDataSM('scroll-arrows')
248					.removeDataSM('parent-a')
249					.removeDataSM('level')
250					.removeDataSM('beforefirstshowfired')
251					.removeAttr('role')
252					.removeAttr('aria-hidden')
253					.removeAttr('aria-labelledby')
254					.removeAttr('aria-expanded');
255				this.$root.find('a.has-submenu').each(function() {
256						var $this = $(this);
257						if ($this.attr('id').indexOf(self.accessIdPrefix) == 0) {
258							$this.removeAttr('id');
259						}
260					})
261					.removeClass('has-submenu')
262					.removeDataSM('sub')
263					.removeAttr('aria-haspopup')
264					.removeAttr('aria-controls')
265					.removeAttr('aria-expanded')
266					.closest('li').removeDataSM('sub');
267				if (this.opts.subIndicators) {
268					this.$root.find('span.sub-arrow').remove();
269				}
270				if (this.opts.markCurrentItem) {
271					this.$root.find('a.current').removeClass('current');
272				}
273				if (!refresh) {
274					this.$root = null;
275					this.$firstLink = null;
276					this.$firstSub = null;
277					if (this.$disableOverlay) {
278						this.$disableOverlay.remove();
279						this.$disableOverlay = null;
280					}
281					menuTrees.splice($.inArray(this, menuTrees), 1);
282				}
283			},
284			disable: function(noOverlay) {
285				if (!this.disabled) {
286					this.menuHideAll();
287					// display overlay over the menu to prevent interaction
288					if (!noOverlay && !this.opts.isPopup && this.$root.is(':visible')) {
289						var pos = this.$root.offset();
290						this.$disableOverlay = $('<div class="sm-jquery-disable-overlay"/>').css({
291							position: 'absolute',
292							top: pos.top,
293							left: pos.left,
294							width: this.$root.outerWidth(),
295							height: this.$root.outerHeight(),
296							zIndex: this.getStartZIndex(true),
297							opacity: 0
298						}).appendTo(document.body);
299					}
300					this.disabled = true;
301				}
302			},
303			docClick: function(e) {
304				if (this.$touchScrollingSub) {
305					this.$touchScrollingSub = null;
306					return;
307				}
308				// hide on any click outside the menu or on a menu link
309				if (this.visibleSubMenus.length && !$.contains(this.$root[0], e.target) || $(e.target).closest('a').length) {
310					this.menuHideAll();
311				}
312			},
313			docTouchEnd: function(e) {
314				if (!this.lastTouch) {
315					return;
316				}
317				if (this.visibleSubMenus.length && (this.lastTouch.x2 === undefined || this.lastTouch.x1 == this.lastTouch.x2) && (this.lastTouch.y2 === undefined || this.lastTouch.y1 == this.lastTouch.y2) && (!this.lastTouch.target || !$.contains(this.$root[0], this.lastTouch.target))) {
318					if (this.hideTimeout) {
319						clearTimeout(this.hideTimeout);
320						this.hideTimeout = 0;
321					}
322					// hide with a delay to prevent triggering accidental unwanted click on some page element
323					var self = this;
324					this.hideTimeout = setTimeout(function() { self.menuHideAll(); }, 350);
325				}
326				this.lastTouch = null;
327			},
328			docTouchMove: function(e) {
329				if (!this.lastTouch) {
330					return;
331				}
332				var touchPoint = e.originalEvent.touches[0];
333				this.lastTouch.x2 = touchPoint.pageX;
334				this.lastTouch.y2 = touchPoint.pageY;
335			},
336			docTouchStart: function(e) {
337				var touchPoint = e.originalEvent.touches[0];
338				this.lastTouch = { x1: touchPoint.pageX, y1: touchPoint.pageY, target: touchPoint.target };
339			},
340			enable: function() {
341				if (this.disabled) {
342					if (this.$disableOverlay) {
343						this.$disableOverlay.remove();
344						this.$disableOverlay = null;
345					}
346					this.disabled = false;
347				}
348			},
349			getClosestMenu: function(elm) {
350				var $closestMenu = $(elm).closest('ul');
351				while ($closestMenu.dataSM('in-mega')) {
352					$closestMenu = $closestMenu.parent().closest('ul');
353				}
354				return $closestMenu[0] || null;
355			},
356			getHeight: function($elm) {
357				return this.getOffset($elm, true);
358			},
359			// returns precise width/height float values
360			getOffset: function($elm, height) {
361				var old;
362				if ($elm.css('display') == 'none') {
363					old = { position: $elm[0].style.position, visibility: $elm[0].style.visibility };
364					$elm.css({ position: 'absolute', visibility: 'hidden' }).show();
365				}
366				var box = $elm[0].getBoundingClientRect && $elm[0].getBoundingClientRect(),
367					val = box && (height ? box.height || box.bottom - box.top : box.width || box.right - box.left);
368				if (!val && val !== 0) {
369					val = height ? $elm[0].offsetHeight : $elm[0].offsetWidth;
370				}
371				if (old) {
372					$elm.hide().css(old);
373				}
374				return val;
375			},
376			getStartZIndex: function(root) {
377				var zIndex = parseInt(this[root ? '$root' : '$firstSub'].css('z-index'));
378				if (!root && isNaN(zIndex)) {
379					zIndex = parseInt(this.$root.css('z-index'));
380				}
381				return !isNaN(zIndex) ? zIndex : 1;
382			},
383			getTouchPoint: function(e) {
384				return e.touches && e.touches[0] || e.changedTouches && e.changedTouches[0] || e;
385			},
386			getViewport: function(height) {
387				var name = height ? 'Height' : 'Width',
388					val = document.documentElement['client' + name],
389					val2 = window['inner' + name];
390				if (val2) {
391					val = Math.min(val, val2);
392				}
393				return val;
394			},
395			getViewportHeight: function() {
396				return this.getViewport(true);
397			},
398			getViewportWidth: function() {
399				return this.getViewport();
400			},
401			getWidth: function($elm) {
402				return this.getOffset($elm);
403			},
404			handleEvents: function() {
405				return !this.disabled && this.isCSSOn();
406			},
407			handleItemEvents: function($a) {
408				return this.handleEvents() && !this.isLinkInMegaMenu($a);
409			},
410			isCollapsible: function() {
411				return this.$firstSub.css('position') == 'static';
412			},
413			isCSSOn: function() {
414				return this.$firstLink.css('display') != 'inline';
415			},
416			isFixed: function() {
417				var isFixed = this.$root.css('position') == 'fixed';
418				if (!isFixed) {
419					this.$root.parentsUntil('body').each(function() {
420						if ($(this).css('position') == 'fixed') {
421							isFixed = true;
422							return false;
423						}
424					});
425				}
426				return isFixed;
427			},
428			isLinkInMegaMenu: function($a) {
429				return $(this.getClosestMenu($a[0])).hasClass('mega-menu');
430			},
431			isTouchMode: function() {
432				return !mouse || this.opts.noMouseOver || this.isCollapsible();
433			},
434			itemActivate: function($a, hideDeeperSubs) {
435				var $ul = $a.closest('ul'),
436					level = $ul.dataSM('level');
437				// if for some reason the parent item is not activated (e.g. this is an API call to activate the item), activate all parent items first
438				if (level > 1 && (!this.activatedItems[level - 2] || this.activatedItems[level - 2][0] != $ul.dataSM('parent-a')[0])) {
439					var self = this;
440					$($ul.parentsUntil('[data-smartmenus-id]', 'ul').get().reverse()).add($ul).each(function() {
441						self.itemActivate($(this).dataSM('parent-a'));
442					});
443				}
444				// hide any visible deeper level sub menus
445				if (!this.isCollapsible() || hideDeeperSubs) {
446					this.menuHideSubMenus(!this.activatedItems[level - 1] || this.activatedItems[level - 1][0] != $a[0] ? level - 1 : level);
447				}
448				// save new active item for this level
449				this.activatedItems[level - 1] = $a;
450				if (this.$root.triggerHandler('activate.smapi', $a[0]) === false) {
451					return;
452				}
453				// show the sub menu if this item has one
454				var $sub = $a.dataSM('sub');
455				if ($sub && (this.isTouchMode() || (!this.opts.showOnClick || this.clickActivated))) {
456					this.menuShow($sub);
457				}
458			},
459			itemBlur: function(e) {
460				var $a = $(e.currentTarget);
461				if (!this.handleItemEvents($a)) {
462					return;
463				}
464				this.$root.triggerHandler('blur.smapi', $a[0]);
465			},
466			itemClick: function(e) {
467				var $a = $(e.currentTarget);
468				if (!this.handleItemEvents($a)) {
469					return;
470				}
471				if (this.$touchScrollingSub && this.$touchScrollingSub[0] == $a.closest('ul')[0]) {
472					this.$touchScrollingSub = null;
473					e.stopPropagation();
474					return false;
475				}
476				if (this.$root.triggerHandler('click.smapi', $a[0]) === false) {
477					return false;
478				}
479				var $sub = $a.dataSM('sub'),
480					firstLevelSub = $sub ? $sub.dataSM('level') == 2 : false;
481				if ($sub) {
482					var subArrowClicked = $(e.target).is('.sub-arrow'),
483						collapsible = this.isCollapsible(),
484						behaviorToggle = /toggle$/.test(this.opts.collapsibleBehavior),
485						behaviorLink = /link$/.test(this.opts.collapsibleBehavior),
486						behaviorAccordion = /^accordion/.test(this.opts.collapsibleBehavior);
487					// if the sub is hidden, try to show it
488					if (!$sub.is(':visible')) {
489						if (!behaviorLink || !collapsible || subArrowClicked) {
490							if (this.opts.showOnClick && firstLevelSub) {
491								this.clickActivated = true;
492							}
493							// try to activate the item and show the sub
494							this.itemActivate($a, behaviorAccordion);
495							// if "itemActivate" showed the sub, prevent the click so that the link is not loaded
496							// if it couldn't show it, then the sub menus are disabled with an !important declaration (e.g. via mobile styles) so let the link get loaded
497							if ($sub.is(':visible')) {
498								this.focusActivated = true;
499								return false;
500							}
501						}
502					// if the sub is visible and we are in collapsible mode
503					} else if (collapsible && (behaviorToggle || subArrowClicked)) {
504						this.itemActivate($a, behaviorAccordion);
505						this.menuHide($sub);
506						if (behaviorToggle) {
507							this.focusActivated = false;
508						}
509						return false;
510					}
511				}
512				if (this.opts.showOnClick && firstLevelSub || $a.hasClass('disabled') || this.$root.triggerHandler('select.smapi', $a[0]) === false) {
513					return false;
514				}
515			},
516			itemDown: function(e) {
517				var $a = $(e.currentTarget);
518				if (!this.handleItemEvents($a)) {
519					return;
520				}
521				$a.dataSM('mousedown', true);
522			},
523			itemEnter: function(e) {
524				var $a = $(e.currentTarget);
525				if (!this.handleItemEvents($a)) {
526					return;
527				}
528				if (!this.isTouchMode()) {
529					if (this.showTimeout) {
530						clearTimeout(this.showTimeout);
531						this.showTimeout = 0;
532					}
533					var self = this;
534					this.showTimeout = setTimeout(function() { self.itemActivate($a); }, this.opts.showOnClick && $a.closest('ul').dataSM('level') == 1 ? 1 : this.opts.showTimeout);
535				}
536				this.$root.triggerHandler('mouseenter.smapi', $a[0]);
537			},
538			itemFocus: function(e) {
539				var $a = $(e.currentTarget);
540				if (!this.handleItemEvents($a)) {
541					return;
542				}
543				// fix (the mousedown check): in some browsers a tap/click produces consecutive focus + click events so we don't need to activate the item on focus
544				if (this.focusActivated && (!this.isTouchMode() || !$a.dataSM('mousedown')) && (!this.activatedItems.length || this.activatedItems[this.activatedItems.length - 1][0] != $a[0])) {
545					this.itemActivate($a, true);
546				}
547				this.$root.triggerHandler('focus.smapi', $a[0]);
548			},
549			itemLeave: function(e) {
550				var $a = $(e.currentTarget);
551				if (!this.handleItemEvents($a)) {
552					return;
553				}
554				if (!this.isTouchMode()) {
555					$a[0].blur();
556					if (this.showTimeout) {
557						clearTimeout(this.showTimeout);
558						this.showTimeout = 0;
559					}
560				}
561				$a.removeDataSM('mousedown');
562				this.$root.triggerHandler('mouseleave.smapi', $a[0]);
563			},
564			menuHide: function($sub) {
565				if (this.$root.triggerHandler('beforehide.smapi', $sub[0]) === false) {
566					return;
567				}
568				if (canAnimate) {
569					$sub.stop(true, true);
570				}
571				if ($sub.css('display') != 'none') {
572					var complete = function() {
573						// unset z-index
574						$sub.css('z-index', '');
575					};
576					// if sub is collapsible (mobile view)
577					if (this.isCollapsible()) {
578						if (canAnimate && this.opts.collapsibleHideFunction) {
579							this.opts.collapsibleHideFunction.call(this, $sub, complete);
580						} else {
581							$sub.hide(this.opts.collapsibleHideDuration, complete);
582						}
583					} else {
584						if (canAnimate && this.opts.hideFunction) {
585							this.opts.hideFunction.call(this, $sub, complete);
586						} else {
587							$sub.hide(this.opts.hideDuration, complete);
588						}
589					}
590					// deactivate scrolling if it is activated for this sub
591					if ($sub.dataSM('scroll')) {
592						this.menuScrollStop($sub);
593						$sub.css({ 'touch-action': '', '-ms-touch-action': '', '-webkit-transform': '', transform: '' })
594							.off('.smartmenus_scroll').removeDataSM('scroll').dataSM('scroll-arrows').hide();
595					}
596					// unhighlight parent item + accessibility
597					$sub.dataSM('parent-a').removeClass('highlighted').attr('aria-expanded', 'false');
598					$sub.attr({
599						'aria-expanded': 'false',
600						'aria-hidden': 'true'
601					});
602					var level = $sub.dataSM('level');
603					this.activatedItems.splice(level - 1, 1);
604					this.visibleSubMenus.splice($.inArray($sub, this.visibleSubMenus), 1);
605					this.$root.triggerHandler('hide.smapi', $sub[0]);
606				}
607			},
608			menuHideAll: function() {
609				if (this.showTimeout) {
610					clearTimeout(this.showTimeout);
611					this.showTimeout = 0;
612				}
613				// hide all subs
614				// if it's a popup, this.visibleSubMenus[0] is the root UL
615				var level = this.opts.isPopup ? 1 : 0;
616				for (var i = this.visibleSubMenus.length - 1; i >= level; i--) {
617					this.menuHide(this.visibleSubMenus[i]);
618				}
619				// hide root if it's popup
620				if (this.opts.isPopup) {
621					if (canAnimate) {
622						this.$root.stop(true, true);
623					}
624					if (this.$root.is(':visible')) {
625						if (canAnimate && this.opts.hideFunction) {
626							this.opts.hideFunction.call(this, this.$root);
627						} else {
628							this.$root.hide(this.opts.hideDuration);
629						}
630					}
631				}
632				this.activatedItems = [];
633				this.visibleSubMenus = [];
634				this.clickActivated = false;
635				this.focusActivated = false;
636				// reset z-index increment
637				this.zIndexInc = 0;
638				this.$root.triggerHandler('hideAll.smapi');
639			},
640			menuHideSubMenus: function(level) {
641				for (var i = this.activatedItems.length - 1; i >= level; i--) {
642					var $sub = this.activatedItems[i].dataSM('sub');
643					if ($sub) {
644						this.menuHide($sub);
645					}
646				}
647			},
648			menuInit: function($ul) {
649				if (!$ul.dataSM('in-mega')) {
650					// mark UL's in mega drop downs (if any) so we can neglect them
651					if ($ul.hasClass('mega-menu')) {
652						$ul.find('ul').dataSM('in-mega', true);
653					}
654					// get level (much faster than, for example, using parentsUntil)
655					var level = 2,
656						par = $ul[0];
657					while ((par = par.parentNode.parentNode) != this.$root[0]) {
658						level++;
659					}
660					// cache stuff for quick access
661					var $a = $ul.prevAll('a').eq(-1);
662					// if the link is nested (e.g. in a heading)
663					if (!$a.length) {
664						$a = $ul.prevAll().find('a').eq(-1);
665					}
666					$a.addClass('has-submenu').dataSM('sub', $ul);
667					$ul.dataSM('parent-a', $a)
668						.dataSM('level', level)
669						.parent().dataSM('sub', $ul);
670					// accessibility
671					var aId = $a.attr('id') || this.accessIdPrefix + (++this.idInc),
672						ulId = $ul.attr('id') || this.accessIdPrefix + (++this.idInc);
673					$a.attr({
674						id: aId,
675						'aria-haspopup': 'true',
676						'aria-controls': ulId,
677						'aria-expanded': 'false'
678					});
679					$ul.attr({
680						id: ulId,
681						'role': 'group',
682						'aria-hidden': 'true',
683						'aria-labelledby': aId,
684						'aria-expanded': 'false'
685					});
686					// add sub indicator to parent item
687					if (this.opts.subIndicators) {
688						$a[this.opts.subIndicatorsPos](this.$subArrow.clone());
689					}
690				}
691			},
692			menuPosition: function($sub) {
693				var $a = $sub.dataSM('parent-a'),
694					$li = $a.closest('li'),
695					$ul = $li.parent(),
696					level = $sub.dataSM('level'),
697					subW = this.getWidth($sub),
698					subH = this.getHeight($sub),
699					itemOffset = $a.offset(),
700					itemX = itemOffset.left,
701					itemY = itemOffset.top,
702					itemW = this.getWidth($a),
703					itemH = this.getHeight($a),
704					$win = $(window),
705					winX = $win.scrollLeft(),
706					winY = $win.scrollTop(),
707					winW = this.getViewportWidth(),
708					winH = this.getViewportHeight(),
709					horizontalParent = $ul.parent().is('[data-sm-horizontal-sub]') || level == 2 && !$ul.hasClass('sm-vertical'),
710					rightToLeft = this.opts.rightToLeftSubMenus && !$li.is('[data-sm-reverse]') || !this.opts.rightToLeftSubMenus && $li.is('[data-sm-reverse]'),
711					subOffsetX = level == 2 ? this.opts.mainMenuSubOffsetX : this.opts.subMenusSubOffsetX,
712					subOffsetY = level == 2 ? this.opts.mainMenuSubOffsetY : this.opts.subMenusSubOffsetY,
713					x, y;
714				if (horizontalParent) {
715					x = rightToLeft ? itemW - subW - subOffsetX : subOffsetX;
716					y = this.opts.bottomToTopSubMenus ? -subH - subOffsetY : itemH + subOffsetY;
717				} else {
718					x = rightToLeft ? subOffsetX - subW : itemW - subOffsetX;
719					y = this.opts.bottomToTopSubMenus ? itemH - subOffsetY - subH : subOffsetY;
720				}
721				if (this.opts.keepInViewport) {
722					var absX = itemX + x,
723						absY = itemY + y;
724					if (rightToLeft && absX < winX) {
725						x = horizontalParent ? winX - absX + x : itemW - subOffsetX;
726					} else if (!rightToLeft && absX + subW > winX + winW) {
727						x = horizontalParent ? winX + winW - subW - absX + x : subOffsetX - subW;
728					}
729					if (!horizontalParent) {
730						if (subH < winH && absY + subH > winY + winH) {
731							y += winY + winH - subH - absY;
732						} else if (subH >= winH || absY < winY) {
733							y += winY - absY;
734						}
735					}
736					// do we need scrolling?
737					// 0.49 used for better precision when dealing with float values
738					if (horizontalParent && (absY + subH > winY + winH + 0.49 || absY < winY) || !horizontalParent && subH > winH + 0.49) {
739						var self = this;
740						if (!$sub.dataSM('scroll-arrows')) {
741							$sub.dataSM('scroll-arrows', $([$('<span class="scroll-up"><span class="scroll-up-arrow"></span></span>')[0], $('<span class="scroll-down"><span class="scroll-down-arrow"></span></span>')[0]])
742								.on({
743									mouseenter: function() {
744										$sub.dataSM('scroll').up = $(this).hasClass('scroll-up');
745										self.menuScroll($sub);
746									},
747									mouseleave: function(e) {
748										self.menuScrollStop($sub);
749										self.menuScrollOut($sub, e);
750									},
751									'mousewheel DOMMouseScroll': function(e) { e.preventDefault(); }
752								})
753								.insertAfter($sub)
754							);
755						}
756						// bind scroll events and save scroll data for this sub
757						var eNS = '.smartmenus_scroll';
758						$sub.dataSM('scroll', {
759								y: this.cssTransforms3d ? 0 : y - itemH,
760								step: 1,
761								// cache stuff for faster recalcs later
762								itemH: itemH,
763								subH: subH,
764								arrowDownH: this.getHeight($sub.dataSM('scroll-arrows').eq(1))
765							})
766							.on(getEventsNS({
767								'mouseover': function(e) { self.menuScrollOver($sub, e); },
768								'mouseout': function(e) { self.menuScrollOut($sub, e); },
769								'mousewheel DOMMouseScroll': function(e) { self.menuScrollMousewheel($sub, e); }
770							}, eNS))
771							.dataSM('scroll-arrows').css({ top: 'auto', left: '0', marginLeft: x + (parseInt($sub.css('border-left-width')) || 0), width: subW - (parseInt($sub.css('border-left-width')) || 0) - (parseInt($sub.css('border-right-width')) || 0), zIndex: $sub.css('z-index') })
772								.eq(horizontalParent && this.opts.bottomToTopSubMenus ? 0 : 1).show();
773						// when a menu tree is fixed positioned we allow scrolling via touch too
774						// since there is no other way to access such long sub menus if no mouse is present
775						if (this.isFixed()) {
776							var events = {};
777							events[touchEvents ? 'touchstart touchmove touchend' : 'pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp'] = function(e) {
778								self.menuScrollTouch($sub, e);
779							};
780							$sub.css({ 'touch-action': 'none', '-ms-touch-action': 'none' }).on(getEventsNS(events, eNS));
781						}
782					}
783				}
784				$sub.css({ top: 'auto', left: '0', marginLeft: x, marginTop: y - itemH });
785			},
786			menuScroll: function($sub, once, step) {
787				var data = $sub.dataSM('scroll'),
788					$arrows = $sub.dataSM('scroll-arrows'),
789					end = data.up ? data.upEnd : data.downEnd,
790					diff;
791				if (!once && data.momentum) {
792					data.momentum *= 0.92;
793					diff = data.momentum;
794					if (diff < 0.5) {
795						this.menuScrollStop($sub);
796						return;
797					}
798				} else {
799					diff = step || (once || !this.opts.scrollAccelerate ? this.opts.scrollStep : Math.floor(data.step));
800				}
801				// hide any visible deeper level sub menus
802				var level = $sub.dataSM('level');
803				if (this.activatedItems[level - 1] && this.activatedItems[level - 1].dataSM('sub') && this.activatedItems[level - 1].dataSM('sub').is(':visible')) {
804					this.menuHideSubMenus(level - 1);
805				}
806				data.y = data.up && end <= data.y || !data.up && end >= data.y ? data.y : (Math.abs(end - data.y) > diff ? data.y + (data.up ? diff : -diff) : end);
807				$sub.css(this.cssTransforms3d ? { '-webkit-transform': 'translate3d(0, ' + data.y + 'px, 0)', transform: 'translate3d(0, ' + data.y + 'px, 0)' } : { marginTop: data.y });
808				// show opposite arrow if appropriate
809				if (mouse && (data.up && data.y > data.downEnd || !data.up && data.y < data.upEnd)) {
810					$arrows.eq(data.up ? 1 : 0).show();
811				}
812				// if we've reached the end
813				if (data.y == end) {
814					if (mouse) {
815						$arrows.eq(data.up ? 0 : 1).hide();
816					}
817					this.menuScrollStop($sub);
818				} else if (!once) {
819					if (this.opts.scrollAccelerate && data.step < this.opts.scrollStep) {
820						data.step += 0.2;
821					}
822					var self = this;
823					this.scrollTimeout = requestAnimationFrame(function() { self.menuScroll($sub); });
824				}
825			},
826			menuScrollMousewheel: function($sub, e) {
827				if (this.getClosestMenu(e.target) == $sub[0]) {
828					e = e.originalEvent;
829					var up = (e.wheelDelta || -e.detail) > 0;
830					if ($sub.dataSM('scroll-arrows').eq(up ? 0 : 1).is(':visible')) {
831						$sub.dataSM('scroll').up = up;
832						this.menuScroll($sub, true);
833					}
834				}
835				e.preventDefault();
836			},
837			menuScrollOut: function($sub, e) {
838				if (mouse) {
839					if (!/^scroll-(up|down)/.test((e.relatedTarget || '').className) && ($sub[0] != e.relatedTarget && !$.contains($sub[0], e.relatedTarget) || this.getClosestMenu(e.relatedTarget) != $sub[0])) {
840						$sub.dataSM('scroll-arrows').css('visibility', 'hidden');
841					}
842				}
843			},
844			menuScrollOver: function($sub, e) {
845				if (mouse) {
846					if (!/^scroll-(up|down)/.test(e.target.className) && this.getClosestMenu(e.target) == $sub[0]) {
847						this.menuScrollRefreshData($sub);
848						var data = $sub.dataSM('scroll'),
849							upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH;
850						$sub.dataSM('scroll-arrows').eq(0).css('margin-top', upEnd).end()
851							.eq(1).css('margin-top', upEnd + this.getViewportHeight() - data.arrowDownH).end()
852							.css('visibility', 'visible');
853					}
854				}
855			},
856			menuScrollRefreshData: function($sub) {
857				var data = $sub.dataSM('scroll'),
858					upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH;
859				if (this.cssTransforms3d) {
860					upEnd = -(parseFloat($sub.css('margin-top')) - upEnd);
861				}
862				$.extend(data, {
863					upEnd: upEnd,
864					downEnd: upEnd + this.getViewportHeight() - data.subH
865				});
866			},
867			menuScrollStop: function($sub) {
868				if (this.scrollTimeout) {
869					cancelAnimationFrame(this.scrollTimeout);
870					this.scrollTimeout = 0;
871					$sub.dataSM('scroll').step = 1;
872					return true;
873				}
874			},
875			menuScrollTouch: function($sub, e) {
876				e = e.originalEvent;
877				if (isTouchEvent(e)) {
878					var touchPoint = this.getTouchPoint(e);
879					// neglect event if we touched a visible deeper level sub menu
880					if (this.getClosestMenu(touchPoint.target) == $sub[0]) {
881						var data = $sub.dataSM('scroll');
882						if (/(start|down)$/i.test(e.type)) {
883							if (this.menuScrollStop($sub)) {
884								// if we were scrolling, just stop and don't activate any link on the first touch
885								e.preventDefault();
886								this.$touchScrollingSub = $sub;
887							} else {
888								this.$touchScrollingSub = null;
889							}
890							// update scroll data since the user might have zoomed, etc.
891							this.menuScrollRefreshData($sub);
892							// extend it with the touch properties
893							$.extend(data, {
894								touchStartY: touchPoint.pageY,
895								touchStartTime: e.timeStamp
896							});
897						} else if (/move$/i.test(e.type)) {
898							var prevY = data.touchY !== undefined ? data.touchY : data.touchStartY;
899							if (prevY !== undefined && prevY != touchPoint.pageY) {
900								this.$touchScrollingSub = $sub;
901								var up = prevY < touchPoint.pageY;
902								// changed direction? reset...
903								if (data.up !== undefined && data.up != up) {
904									$.extend(data, {
905										touchStartY: touchPoint.pageY,
906										touchStartTime: e.timeStamp
907									});
908								}
909								$.extend(data, {
910									up: up,
911									touchY: touchPoint.pageY
912								});
913								this.menuScroll($sub, true, Math.abs(touchPoint.pageY - prevY));
914							}
915							e.preventDefault();
916						} else { // touchend/pointerup
917							if (data.touchY !== undefined) {
918								if (data.momentum = Math.pow(Math.abs(touchPoint.pageY - data.touchStartY) / (e.timeStamp - data.touchStartTime), 2) * 15) {
919									this.menuScrollStop($sub);
920									this.menuScroll($sub);
921									e.preventDefault();
922								}
923								delete data.touchY;
924							}
925						}
926					}
927				}
928			},
929			menuShow: function($sub) {
930				if (!$sub.dataSM('beforefirstshowfired')) {
931					$sub.dataSM('beforefirstshowfired', true);
932					if (this.$root.triggerHandler('beforefirstshow.smapi', $sub[0]) === false) {
933						return;
934					}
935				}
936				if (this.$root.triggerHandler('beforeshow.smapi', $sub[0]) === false) {
937					return;
938				}
939				$sub.dataSM('shown-before', true);
940				if (canAnimate) {
941					$sub.stop(true, true);
942				}
943				if (!$sub.is(':visible')) {
944					// highlight parent item
945					var $a = $sub.dataSM('parent-a'),
946						collapsible = this.isCollapsible();
947					if (this.opts.keepHighlighted || collapsible) {
948						$a.addClass('highlighted');
949					}
950					if (collapsible) {
951						$sub.removeClass('sm-nowrap').css({ zIndex: '', width: 'auto', minWidth: '', maxWidth: '', top: '', left: '', marginLeft: '', marginTop: '' });
952					} else {
953						// set z-index
954						$sub.css('z-index', this.zIndexInc = (this.zIndexInc || this.get
954StartZIndex()) + 1);
955						// min/max-width fix - no way to rely purely on CSS as all UL's are nested
956						if (this.opts.subMenusMinWidth || this.opts.subMenusMaxWidth) {
957							$sub.css({ width: 'auto', minWidth: '', maxWidth: '' }).addClass('sm-nowrap');
958							if (this.opts.subMenusMinWidth) {
959							 	$sub.css('min-width', this.opts.subMenusMinWidth);
960							}
961							if (this.opts.subMenusMaxWidth) {
962							 	var noMaxWidth = this.getWidth($sub);
963							 	$sub.css('max-width', this.opts.subMenusMaxWidth);
964								if (noMaxWidth > this.getWidth($sub)) {
965									$sub.removeClass('sm-nowrap').css('width', this.opts.subMenusMaxWidth);
966								}
967							}
968						}
969						this.menuPosition($sub);
970					}
971					var complete = function() {
972						// fix: "overflow: hidden;" is not reset on animation complete in jQuery < 1.9.0 in Chrome when global "box-sizing: border-box;" is used
973						$sub.css('overflow', '');
974					};
975					// if sub is collapsible (mobile view)
976					if (collapsible) {
977						if (canAnimate && this.opts.collapsibleShowFunction) {
978							this.opts.collapsibleShowFunction.call(this, $sub, complete);
979						} else {
980							$sub.show(this.opts.collapsibleShowDuration, complete);
981						}
982					} else {
983						if (canAnimate && this.opts.showFunction) {
984							this.opts.showFunction.call(this, $sub, complete);
985						} else {
986							$sub.show(this.opts.showDuration, complete);
987						}
988					}
989					// accessibility
990					$a.attr('aria-expanded', 'true');
991					$sub.attr({
992						'aria-expanded': 'true',
993						'aria-hidden': 'false'
994					});
995					// store sub menu in visible array
996					this.visibleSubMenus.push($sub);
997					this.$root.triggerHandler('show.smapi', $sub[0]);
998				}
999			},
1000			popupHide: function(noHideTimeout) {
1001				if (this.hideTimeout) {
1002					clearTimeout(this.hideTimeout);
1003					this.hideTimeout = 0;
1004				}
1005				var self = this;
1006				this.hideTimeout = setTimeout(function() {
1007					self.menuHideAll();
1008				}, noHideTimeout ? 1 : this.opts.hideTimeout);
1009			},
1010			popupShow: function(left, top) {
1011				if (!this.opts.isPopup) {
1012					alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.');
1013					return;
1014				}
1015				if (this.hideTimeout) {
1016					clearTimeout(this.hideTimeout);
1017					this.hideTimeout = 0;
1018				}
1019				this.$root.dataSM('shown-before', true);
1020				if (canAnimate) {
1021					this.$root.stop(true, true);
1022				}
1023				if (!this.$root.is(':visible')) {
1024					this.$root.css({ left: left, top: top });
1025					// show menu
1026					var self = this,
1027						complete = function() {
1028							self.$root.css('overflow', '');
1029						};
1030					if (canAnimate && this.opts.showFunction) {
1031						this.opts.showFunction.call(this, this.$root, complete);
1032					} else {
1033						this.$root.show(this.opts.showDuration, complete);
1034					}
1035					this.visibleSubMenus[0] = this.$root;
1036				}
1037			},
1038			refresh: function() {
1039				this.destroy(true);
1040				this.init(true);
1041			},
1042			rootKeyDown: function(e) {
1043				if (!this.handleEvents()) {
1044					return;
1045				}
1046				switch (e.keyCode) {
1047					case 27: // reset on Esc
1048						var $activeTopItem = this.activatedItems[0];
1049						if ($activeTopItem) {
1050							this.menuHideAll();
1051							$activeTopItem[0].focus();
1052							var $sub = $activeTopItem.dataSM('sub');
1053							if ($sub) {
1054								this.menuHide($sub);
1055							}
1056						}
1057						break;
1058					case 32: // activate item's sub on Space
1059						var $target = $(e.target);
1060						if ($target.is('a') && this.handleItemEvents($target)) {
1061							var $sub = $target.dataSM('sub');
1062							if ($sub && !$sub.is(':visible')) {
1063								this.itemClick({ currentTarget: e.target });
1064								e.preventDefault();
1065							}
1066						}
1067						break;
1068				}
1069			},
1070			rootOut: function(e) {
1071				if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) {
1072					return;
1073				}
1074				if (this.hideTimeout) {
1075					clearTimeout(this.hideTimeout);
1076					this.hideTimeout = 0;
1077				}
1078				if (!this.opts.showOnClick || !this.opts.hideOnClick) {
1079					var self = this;
1080					this.hideTimeout = setTimeout(function() { self.menuHideAll(); }, this.opts.hideTimeout);
1081				}
1082			},
1083			rootOver: function(e) {
1084				if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) {
1085					return;
1086				}
1087				if (this.hideTimeout) {
1088					clearTimeout(this.hideTimeout);
1089					this.hideTimeout = 0;
1090				}
1091			},
1092			winResize: function(e) {
1093				if (!this.handleEvents()) {
1094					// we still need to resize the disable overlay if it's visible
1095					if (this.$disableOverlay) {
1096						var pos = this.$root.offset();
1097	 					this.$disableOverlay.css({
1098							top: pos.top,
1099							left: pos.left,
1100							width: this.$root.outerWidth(),
1101							height: this.$root.outerHeight()
1102						});
1103					}
1104					return;
1105				}
1106				// hide sub menus on resize - on mobile do it only on orientation change
1107				if (!('onorientationchange' in window) || e.type == 'orientationchange') {
1108					var collapsible = this.isCollapsible();
1109					// if it was collapsible before resize and still is, don't do it
1110					if (!(this.wasCollapsible && collapsible)) {
1111						if (this.activatedItems.length) {
1112							this.activatedItems[this.activatedItems.length - 1][0].blur();
1113						}
1114						this.menuHideAll();
1115					}
1116					this.wasCollapsible = collapsible;
1117				}
1118			}
1119		}
1120	});
1121
1122	$.fn.dataSM = function(key, val) {
1123		if (val) {
1124			return this.data(key + '_smartmenus', val);
1125		}
1126		return this.data(key + '_smartmenus');
1127	};
1128
1129	$.fn.removeDataSM = function(key) {
1130		return this.removeData(key + '_smartmenus');
1131	};
1132
1133	$.fn.smartmenus = function(options) {
1134		if (typeof options == 'string') {
1135			var args = arguments,
1136				method = options;
1137			Array.prototype.shift.call(args);
1138			return this.each(function() {
1139				var smartmenus = $(this).data('smartmenus');
1140				if (smartmenus && smartmenus[method]) {
1141					smartmenus[method].apply(smartmenus, args);
1142				}
1143			});
1144		}
1145		return this.each(function() {
1146			// [data-sm-options] attribute on the root UL
1147			var dataOpts = $(this).data('sm-options') || null;
1148			if (dataOpts) {
1149				try {
1150					dataOpts = eval('(' + dataOpts + ')');
1151				} catch(e) {
1152					dataOpts = null;
1153					alert('ERROR\n\nSmartMenus jQuery init:\nInvalid "data-sm-options" attribute value syntax.');
1154				};
1155			}
1156			new $.SmartMenus(this, $.extend({}, $.fn.smartmenus.defaults, options, dataOpts));
1157		});
1158	};
1159
1160	// default settings
1161	$.fn.smartmenus.defaults = {
1162		isPopup:		false,		// is this a popup menu (can be shown via the popupShow/popupHide methods) or a permanent menu bar
1163		mainMenuSubOffsetX:	0,		// pixels offset from default position
1164		mainMenuSubOffsetY:	0,		// pixels offset from default position
1165		subMenusSubOffsetX:	0,		// pixels offset from default position
1166		subMenusSubOffsetY:	0,		// pixels offset from default position
1167		subMenusMinWidth:	'10em',		// min-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored
1168		subMenusMaxWidth:	'20em',		// max-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored
1169		subIndicators: 		true,		// create sub menu indicators - creates a SPAN and inserts it in the A
1170		subIndicatorsPos: 	'append',	// position of the SPAN relative to the menu item content ('append', 'prepend')
1171		subIndicatorsText:	'',		// [optionally] add text in the SPAN (e.g. '+') (you may want to check the CSS for the sub indicators too)
1172		scrollStep: 		30,		// pixels step when scrolling long sub menus that do not fit in the viewport height
1173		scrollAccelerate:	true,		// accelerate scrolling or use a fixed step
1174		showTimeout:		250,		// timeout before showing the sub menus
1175		hideTimeout:		500,		// timeout before hiding the sub menus
1176		showDuration:		0,		// duration for show animation - set to 0 for no animation - matters only if showFunction:null
1177		showFunction:		null,		// custom function to use when showing a sub menu (the default is the jQuery 'show')
1178							// don't forget to call complete() at the end of whatever you do
1179							// e.g.: function($ul, complete) { $ul.fadeIn(250, complete); }
1180		hideDuration:		0,		// duration for hide animation - set to 0 for no animation - matters only if hideFunction:null
1181		hideFunction:		function($ul, complete) { $ul.fadeOut(200, complete); },	// custom function to use when hiding a sub menu (the default is the jQuery 'hide')
1182							// don't forget to call complete() at the end of whatever you do
1183							// e.g.: function($ul, complete) { $ul.fadeOut(250, complete); }
1184		collapsibleShowDuration:0,		// duration for show animation for collapsible sub menus - matters only if collapsibleShowFunction:null
1185		collapsibleShowFunction:function($ul, complete) { $ul.slideDown(200, complete); },	// custom function to use when showing a collapsible sub menu
1186							// (i.e. when mobile styles are used to make the sub menus collapsible)
1187		collapsibleHideDuration:0,		// duration for hide animation for collapsible sub menus - matters only if collapsibleHideFunction:null
1188		collapsibleHideFunction:function($ul, complete) { $ul.slideUp(200, complete); },	// custom function to use when hiding a collapsible sub menu
1189							// (i.e. when mobile styles are used to make the sub menus collapsible)
1190		showOnClick:		false,		// show the first-level sub menus onclick instead of onmouseover (i.e. mimic desktop app menus) (matters only for mouse input)
1191		hideOnClick:		true,		// hide the sub menus on click/tap anywhere on the page
1192		noMouseOver:		false,		// disable sub menus activation onmouseover (i.e. behave like in touch mode - use just mouse clicks) (matters only for mouse input)
1193		keepInViewport:		true,		// reposition the sub menus if needed to make sure they always appear inside the viewport
1194		keepHighlighted:	true,		// keep all ancestor items of the current sub menu highlighted (adds the 'highlighted' class to the A's)
1195		markCurrentItem:	false,		// automatically add the 'current' class to the A element of the item linking to the current URL
1196		markCurrentTree:	true,		// add the 'current' class also to the A elements of all ancestor items of the current item
1197		rightToLeftSubMenus:	false,		// right to left display of the sub menus (check the CSS for the sub indicators' position)
1198		bottomToTopSubMenus:	false,		// bottom to top display of the sub menus
1199		collapsibleBehavior:	'default'	// parent items behavior in collapsible (mobile) view ('default', 'toggle', 'link', 'accordion', 'accordion-toggle', 'accordion-link')
1200							// 'default' - first tap on parent item expands sub, second tap loads its link
1201							// 'toggle' - the whole parent item acts just as a toggle button for its sub menu (expands/collapses on each tap)
1202							// 'link' - the parent item acts as a regular item (first tap loads its link), the sub menu can be expanded only via the +/- button
1203							// 'accordion' - like 'default' but on expand also resets any visible sub menus from deeper levels or other branches
1204							// 'accordion-toggle' - like 'toggle' but on expand also resets any visible sub menus from deeper levels or other branches
1205							// 'accordion-link' - like 'link' but on expand also resets any visible sub menus from deeper levels or other branches
1206	};
1207
1208	return $;
1209}));
1210

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.