PageSourceSearch

https://ratracerebellion.com/wp-content/themes/yourweblayout/js/jquery.smartmenus.js?ver=6.9.9

js ratracerebellion.com collected 2026-09-25 21:10:43 UTC 41,734 bytes, 1,137 lines download raw bytes

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