From 8480d35beef57ed08139b58972bfb83a3b37422c Mon Sep 17 00:00:00 2001 From: Dimitri van Heesch Date: Sun, 1 May 2016 10:05:39 +0200 Subject: [PATCH] Applied responsive design to menu bar using smartmenus --- jquery/.gitignore | 4 + jquery/Makefile | 25 +- jquery/README | 8 +- jquery/jquery.smartmenus-1.0.0.js | 1214 +++++++++++++++++++++ jquery/jquery.ui-0.2.3.touch-punch.js | 180 +++ jquery/sass/_round-corners-last-item.scss | 23 + jquery/sass/_sm-dox.scss | 594 ++++++++++ jquery/sass/_sub-items-indentation.scss | 15 + jquery/sass/sm-dox.scss | 5 + jquery/sm-core-css.css | 10 + qtools/Doxyfile | 2 +- src/doxygen.cpp | 2 +- src/htmlgen.cpp | 107 +- src/index.cpp | 165 ++- src/memberdef.cpp | 4 - templates/html/doxygen.css | 2 +- templates/html/htmlbase.tpl | 15 +- templates/html/htmlclmembers.tpl | 4 - templates/html/htmlclmembersindex.tpl | 26 - templates/html/htmlflmembers.tpl | 4 - templates/html/htmljsmenudata.tpl | 52 + templates/html/htmljsmenuletterdata.tpl | 12 + templates/html/htmljsmenumembersdata.tpl | 58 + templates/html/htmljssearchdata.tpl | 2 +- templates/html/htmllayout.tpl | 6 + templates/html/htmlnavtree.tpl | 22 - templates/html/htmlnsmembers.tpl | 4 - templates/html/htmlnsmembersindex.tpl | 26 - templates/html/jquery.js | 21 +- templates/html/menu.js | 22 + templates/html/navtree.css | 7 +- templates/html/navtree.js | 20 +- templates/html/resize.js | 141 ++- templates/html/search.css | 10 +- templates/html/tabs.css | 61 +- 35 files changed, 2573 insertions(+), 300 deletions(-) create mode 100644 jquery/.gitignore create mode 100644 jquery/jquery.smartmenus-1.0.0.js create mode 100644 jquery/jquery.ui-0.2.3.touch-punch.js create mode 100644 jquery/sass/_round-corners-last-item.scss create mode 100644 jquery/sass/_sm-dox.scss create mode 100644 jquery/sass/_sub-items-indentation.scss create mode 100644 jquery/sass/sm-dox.scss create mode 100644 jquery/sm-core-css.css delete mode 100644 templates/html/htmlclmembersindex.tpl create mode 100644 templates/html/htmljsmenudata.tpl create mode 100644 templates/html/htmljsmenuletterdata.tpl create mode 100644 templates/html/htmljsmenumembersdata.tpl delete mode 100644 templates/html/htmlnavtree.tpl delete mode 100644 templates/html/htmlnsmembersindex.tpl create mode 100644 templates/html/menu.js diff --git a/jquery/.gitignore b/jquery/.gitignore new file mode 100644 index 00000000000..8436b241648 --- /dev/null +++ b/jquery/.gitignore @@ -0,0 +1,4 @@ +.sass-cache +*-min.js +doxmenu*.css +jquery.js diff --git a/jquery/Makefile b/jquery/Makefile index 996f472d146..02cdd345d87 100644 --- a/jquery/Makefile +++ b/jquery/Makefile @@ -3,6 +3,8 @@ JQUERY_UI_VERSION = 1.8.18 HASHCHANGE_VERSION = 1.3 SCROLL_VERSION = 1.4.2 POWERTIP_VERSION = 1.2.0 +TOUCHPUNCH_VERSION = 0.2.3 +SMARTMENUS_VERSION = 1.0.0 MINIFIER ?= /usr/local/bin/yuicompressor-2.4.7 SCRIPTS = jquery-$(JQUERY_VERSION).js \ @@ -12,23 +14,36 @@ SCRIPTS = jquery-$(JQUERY_VERSION).js \ jquery.ui-$(JQUERY_UI_VERSION).resizable.js \ jquery.ba-$(HASHCHANGE_VERSION)-hashchange.js \ jquery.scrollTo-$(SCROLL_VERSION).js \ - jquery.powertip-$(POWERTIP_VERSION).js -RESULTS = jquery.js + jquery.powertip-$(POWERTIP_VERSION).js \ + jquery.ui-$(TOUCHPUNCH_VERSION).touch-punch.js \ + jquery.smartmenus-$(SMARTMENUS_VERSION).js +RESULTS = jquery.js doxmenu-min.css SCRIPTS_MIN = $(SCRIPTS:%.js=%-min.js) all: $(RESULTS) install: $(RESULTS) - cp $(RESULTS) ../templates/html/ + cp jquery.js ../templates/html/jquery.js + cp doxmenu-min.css ../templates/html/tabs.css -jquery.js: scripts +jquery.js: $(SCRIPTS_MIN) cat $(SCRIPTS_MIN) > jquery.js +doxmenu-min.css: sm-core-css.css \ + sass/sm-dox.scss \ + sass/_round-corners-last-item.scss \ + sass/_sm-dox.scss \ + sass/_sub-items-indentation.scss + compass compile --css-dir . --force sass/sm-dox.scss + cat sm-core-css.css sm-dox.css > doxmenu.css + java -jar $(MINIFIER).jar doxmenu.css > doxmenu-min.css + rm -f sm-dox.css doxmenu.css + scripts: $(SCRIPTS_MIN) clean: - rm -f $(SCRIPTS_MIN) $(RESULTS) + rm -rf $(SCRIPTS_MIN) $(RESULTS) doxmenu.css .sass-cache jquery.js %-min.js: %.js java -jar $(MINIFIER).jar $^ > $@ diff --git a/jquery/README b/jquery/README index 5e385a50fa9..21590ff4b57 100644 --- a/jquery/README +++ b/jquery/README @@ -7,8 +7,10 @@ packages: - jquery.ui.widget - jquery.ui.mouse - jquery.ui.resizable -- jquery.hashchange: 1.3: http://benalman.com/projects/jquery-hashchange-plugin/ -- jquery.scrollTo: 1.4.2: https://github.com/flesler/jquery.scrollTo -- jquery.powertip: 1.2.0: http://stevenbenner.github.io/jquery-powertip/ +- jquery.hashchange: 1.3: http://benalman.com/projects/jquery-hashchange-plugin/ +- jquery.scrollTo: 1.4.2: https://github.com/flesler/jquery.scrollTo +- jquery.powertip: 1.2.0: http://stevenbenner.github.io/jquery-powertip/ +- jquery.touchpunch: 0.2.3: http://touchpunch.furf.com/ +- jquery.smartmenus: 1.0.0: http://www.smartmenus.org/ The Makefile will built the jquery.js files used by doxygen. diff --git a/jquery/jquery.smartmenus-1.0.0.js b/jquery/jquery.smartmenus-1.0.0.js new file mode 100644 index 00000000000..9c3a4944ba7 --- /dev/null +++ b/jquery/jquery.smartmenus-1.0.0.js @@ -0,0 +1,1214 @@ +/*! + * SmartMenus jQuery Plugin - v1.0.0 - January 27, 2016 + * http://www.smartmenus.org/ + * + * Copyright Vasil Dinkov, Vadikom Web Ltd. + * http://vadikom.com + * + * Licensed MIT + */ + +(function(factory) { + if (typeof define === 'function' && define.amd) { + // AMD + define(['jquery'], factory); + } else if (typeof module === 'object' && typeof module.exports === 'object') { + // CommonJS + module.exports = factory(require('jquery')); + } else { + // Global jQuery + factory(jQuery); + } +} (function($) { + + var menuTrees = [], + IE = !!window.createPopup, // detect it for the iframe shim + mouse = false, // optimize for touch by default - we will detect for mouse input + touchEvents = 'ontouchstart' in window, // we use this just to choose between toucn and pointer events, not for touch screen detection + mouseDetectionEnabled = false, + requestAnimationFrame = window.requestAnimationFrame || function(callback) { return setTimeout(callback, 1000 / 60); }, + cancelAnimationFrame = window.cancelAnimationFrame || function(id) { clearTimeout(id); }; + + // Handle detection for mouse input (i.e. desktop browsers, tablets with a mouse, etc.) + function initMouseDetection(disable) { + var eNS = '.smartmenus_mouse'; + if (!mouseDetectionEnabled && !disable) { + // if we get two consecutive mousemoves within 2 pixels from each other and within 300ms, we assume a real mouse/cursor is present + // 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) + var firstTime = true, + lastMove = null; + $(document).bind(getEventsNS([ + ['mousemove', function(e) { + var thisMove = { x: e.pageX, y: e.pageY, timeStamp: new Date().getTime() }; + if (lastMove) { + var deltaX = Math.abs(lastMove.x - thisMove.x), + deltaY = Math.abs(lastMove.y - thisMove.y); + if ((deltaX > 0 || deltaY > 0) && deltaX <= 2 && deltaY <= 2 && thisMove.timeStamp - lastMove.timeStamp <= 300) { + mouse = true; + // 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 + if (firstTime) { + var $a = $(e.target).closest('a'); + if ($a.is('a')) { + $.each(menuTrees, function() { + if ($.contains(this.$root[0], $a[0])) { + this.itemEnter({ currentTarget: $a[0] }); + return false; + } + }); + } + firstTime = false; + } + } + } + lastMove = thisMove; + }], + [touchEvents ? 'touchstart' : 'pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut', function(e) { + if (isTouchEvent(e.originalEvent)) { + mouse = false; + } + }] + ], eNS)); + mouseDetectionEnabled = true; + } else if (mouseDetectionEnabled && disable) { + $(document).unbind(eNS); + mouseDetectionEnabled = false; + } + } + + function isTouchEvent(e) { + return !/^(4|mouse)$/.test(e.pointerType); + } + + // returns a jQuery bind() ready object + function getEventsNS(defArr, eNS) { + if (!eNS) { + eNS = ''; + } + var obj = {}; + $.each(defArr, function(index, value) { + obj[value[0].split(' ').join(eNS + ' ') + eNS] = value[1]; + }); + return obj; + } + + $.SmartMenus = function(elm, options) { + this.$root = $(elm); + this.opts = options; + this.rootId = ''; // internal + this.accessIdPrefix = ''; + this.$subArrow = null; + this.activatedItems = []; // stores last activated A's for each level + this.visibleSubMenus = []; // stores visible sub menus UL's (might be in no particular order) + this.showTimeout = 0; + this.hideTimeout = 0; + this.scrollTimeout = 0; + this.clickActivated = false; + this.focusActivated = false; + this.zIndexInc = 0; + this.idInc = 0; + this.$firstLink = null; // we'll use these for some tests + this.$firstSub = null; // at runtime so we'll cache them + this.disabled = false; + this.$disableOverlay = null; + this.$touchScrollingSub = null; + this.cssTransforms3d = 'perspective' in elm.style || 'webkitPerspective' in elm.style; + this.wasCollapsible = false; + this.init(); + }; + + $.extend($.SmartMenus, { + hideAll: function() { + $.each(menuTrees, function() { + this.menuHideAll(); + }); + }, + destroy: function() { + while (menuTrees.length) { + menuTrees[0].destroy(); + } + initMouseDetection(true); + }, + prototype: { + init: function(refresh) { + var self = this; + + if (!refresh) { + menuTrees.push(this); + + this.rootId = (new Date().getTime() + Math.random() + '').replace(/\D/g, ''); + this.accessIdPrefix = 'sm-' + this.rootId + '-'; + + if (this.$root.hasClass('sm-rtl')) { + this.opts.rightToLeftSubMenus = true; + } + + // init root (main menu) + var eNS = '.smartmenus'; + this.$root + .data('smartmenus', this) + .attr('data-smartmenus-id', this.rootId) + .dataSM('level', 1) + .bind(getEventsNS([ + ['mouseover focusin', $.proxy(this.rootOver, this)], + ['mouseout focusout', $.proxy(this.rootOut, this)], + ['keydown', $.proxy(this.rootKeyDown, this)] + ], eNS)) + .delegate('a', getEventsNS([ + ['mouseenter', $.proxy(this.itemEnter, this)], + ['mouseleave', $.proxy(this.itemLeave, this)], + ['mousedown', $.proxy(this.itemDown, this)], + ['focus', $.proxy(this.itemFocus, this)], + ['blur', $.proxy(this.itemBlur, this)], + ['click', $.proxy(this.itemClick, this)] + ], eNS)); + + // hide menus on tap or click outside the root UL + eNS += this.rootId; + if (this.opts.hideOnClick) { + $(document).bind(getEventsNS([ + ['touchstart', $.proxy(this.docTouchStart, this)], + ['touchmove', $.proxy(this.docTouchMove, this)], + ['touchend', $.proxy(this.docTouchEnd, this)], + // for Opera Mobile < 11.5, webOS browser, etc. we'll check click too + ['click', $.proxy(this.docClick, this)] + ], eNS)); + } + // hide sub menus on resize + $(window).bind(getEventsNS([['resize orientationchange', $.proxy(this.winResize, this)]], eNS)); + + if (this.opts.subIndicators) { + this.$subArrow = $('').addClass('sub-arrow'); + if (this.opts.subIndicatorsText) { + this.$subArrow.html(this.opts.subIndicatorsText); + } + } + + // make sure mouse detection is enabled + initMouseDetection(); + } + + // init sub menus + this.$firstSub = this.$root.find('ul').each(function() { self.menuInit($(this)); }).eq(0); + + this.$firstLink = this.$root.find('a').eq(0); + + // find current item + if (this.opts.markCurrentItem) { + var reDefaultDoc = /(index|default)\.[^#\?\/]*/i, + reHash = /#.*/, + locHref = window.location.href.replace(reDefaultDoc, ''), + locHrefNoHash = locHref.replace(reHash, ''); + this.$root.find('a').each(function() { + var href = this.href.replace(reDefaultDoc, ''), + $this = $(this); + if (href == locHref || href == locHrefNoHash) { + $this.addClass('current'); + if (self.opts.markCurrentTree) { + $this.parentsUntil('[data-smartmenus-id]', 'ul').each(function() { + $(this).dataSM('parent-a').addClass('current'); + }); + } + } + }); + } + + // save initial state + this.wasCollapsible = this.isCollapsible(); + }, + destroy: function(refresh) { + if (!refresh) { + var eNS = '.smartmenus'; + this.$root + .removeData('smartmenus') + .removeAttr('data-smartmenus-id') + .removeDataSM('level') + .unbind(eNS) + .undelegate(eNS); + eNS += this.rootId; + $(document).unbind(eNS); + $(window).unbind(eNS); + if (this.opts.subIndicators) { + this.$subArrow = null; + } + } + this.menuHideAll(); + var self = this; + this.$root.find('ul').each(function() { + var $this = $(this); + if ($this.dataSM('scroll-arrows')) { + $this.dataSM('scroll-arrows').remove(); + } + if ($this.dataSM('shown-before')) { + if (self.opts.subMenusMinWidth || self.opts.subMenusMaxWidth) { + $this.css({ width: '', minWidth: '', maxWidth: '' }).removeClass('sm-nowrap'); + } + if ($this.dataSM('scroll-arrows')) { + $this.dataSM('scroll-arrows').remove(); + } + $this.css({ zIndex: '', top: '', left: '', marginLeft: '', marginTop: '', display: '' }); + } + if (($this.attr('id') || '').indexOf(self.accessIdPrefix) == 0) { + $this.removeAttr('id'); + } + }) + .removeDataSM('in-mega') + .removeDataSM('shown-before') + .removeDataSM('ie-shim') + .removeDataSM('scroll-arrows') + .removeDataSM('parent-a') + .removeDataSM('level') + .removeDataSM('beforefirstshowfired') + .removeAttr('role') + .removeAttr('aria-hidden') + .removeAttr('aria-labelledby') + .removeAttr('aria-expanded'); + this.$root.find('a.has-submenu').each(function() { + var $this = $(this); + if ($this.attr('id').indexOf(self.accessIdPrefix) == 0) { + $this.removeAttr('id'); + } + }) + .removeClass('has-submenu') + .removeDataSM('sub') + .removeAttr('aria-haspopup') + .removeAttr('aria-controls') + .removeAttr('aria-expanded') + .closest('li').removeDataSM('sub'); + if (this.opts.subIndicators) { + this.$root.find('span.sub-arrow').remove(); + } + if (this.opts.markCurrentItem) { + this.$root.find('a.current').removeClass('current'); + } + if (!refresh) { + this.$root = null; + this.$firstLink = null; + this.$firstSub = null; + if (this.$disableOverlay) { + this.$disableOverlay.remove(); + this.$disableOverlay = null; + } + menuTrees.splice($.inArray(this, menuTrees), 1); + } + }, + disable: function(noOverlay) { + if (!this.disabled) { + this.menuHideAll(); + // display overlay over the menu to prevent interaction + if (!noOverlay && !this.opts.isPopup && this.$root.is(':visible')) { + var pos = this.$root.offset(); + this.$disableOverlay = $('
').css({ + position: 'absolute', + top: pos.top, + left: pos.left, + width: this.$root.outerWidth(), + height: this.$root.outerHeight(), + zIndex: this.getStartZIndex(true), + opacity: 0 + }).appendTo(document.body); + } + this.disabled = true; + } + }, + docClick: function(e) { + if (this.$touchScrollingSub) { + this.$touchScrollingSub = null; + return; + } + // hide on any click outside the menu or on a menu link + if (this.visibleSubMenus.length && !$.contains(this.$root[0], e.target) || $(e.target).is('a')) { + this.menuHideAll(); + } + }, + docTouchEnd: function(e) { + if (!this.lastTouch) { + return; + } + 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))) { + if (this.hideTimeout) { + clearTimeout(this.hideTimeout); + this.hideTimeout = 0; + } + // hide with a delay to prevent triggering accidental unwanted click on some page element + var self = this; + this.hideTimeout = setTimeout(function() { self.menuHideAll(); }, 350); + } + this.lastTouch = null; + }, + docTouchMove: function(e) { + if (!this.lastTouch) { + return; + } + var touchPoint = e.originalEvent.touches[0]; + this.lastTouch.x2 = touchPoint.pageX; + this.lastTouch.y2 = touchPoint.pageY; + }, + docTouchStart: function(e) { + var touchPoint = e.originalEvent.touches[0]; + this.lastTouch = { x1: touchPoint.pageX, y1: touchPoint.pageY, target: touchPoint.target }; + }, + enable: function() { + if (this.disabled) { + if (this.$disableOverlay) { + this.$disableOverlay.remove(); + this.$disableOverlay = null; + } + this.disabled = false; + } + }, + getClosestMenu: function(elm) { + var $closestMenu = $(elm).closest('ul'); + while ($closestMenu.dataSM('in-mega')) { + $closestMenu = $closestMenu.parent().closest('ul'); + } + return $closestMenu[0] || null; + }, + getHeight: function($elm) { + return this.getOffset($elm, true); + }, + // returns precise width/height float values + getOffset: function($elm, height) { + var old; + if ($elm.css('display') == 'none') { + old = { position: $elm[0].style.position, visibility: $elm[0].style.visibility }; + $elm.css({ position: 'absolute', visibility: 'hidden' }).show(); + } + var box = $elm[0].getBoundingClientRect && $elm[0].getBoundingClientRect(), + val = box && (height ? box.height || box.bottom - box.top : box.width || box.right - box.left); + if (!val && val !== 0) { + val = height ? $elm[0].offsetHeight : $elm[0].offsetWidth; + } + if (old) { + $elm.hide().css(old); + } + return val; + }, + getStartZIndex: function(root) { + var zIndex = parseInt(this[root ? '$root' : '$firstSub'].css('z-index')); + if (!root && isNaN(zIndex)) { + zIndex = parseInt(this.$root.css('z-index')); + } + return !isNaN(zIndex) ? zIndex : 1; + }, + getTouchPoint: function(e) { + return e.touches && e.touches[0] || e.changedTouches && e.changedTouches[0] || e; + }, + getViewport: function(height) { + var name = height ? 'Height' : 'Width', + val = document.documentElement['client' + name], + val2 = window['inner' + name]; + if (val2) { + val = Math.min(val, val2); + } + return val; + }, + getViewportHeight: function() { + return this.getViewport(true); + }, + getViewportWidth: function() { + return this.getViewport(); + }, + getWidth: function($elm) { + return this.getOffset($elm); + }, + handleEvents: function() { + return !this.disabled && this.isCSSOn(); + }, + handleItemEvents: function($a) { + return this.handleEvents() && !this.isLinkInMegaMenu($a); + }, + isCollapsible: function() { + return this.$firstSub.css('position') == 'static'; + }, + isCSSOn: function() { + return this.$firstLink.css('display') == 'block'; + }, + isFixed: function() { + var isFixed = this.$root.css('position') == 'fixed'; + if (!isFixed) { + this.$root.parentsUntil('body').each(function() { + if ($(this).css('position') == 'fixed') { + isFixed = true; + return false; + } + }); + } + return isFixed; + }, + isLinkInMegaMenu: function($a) { + return $(this.getClosestMenu($a[0])).hasClass('mega-menu'); + }, + isTouchMode: function() { + return !mouse || this.opts.noMouseOver || this.isCollapsible(); + }, + itemActivate: function($a, focus) { + var $ul = $a.closest('ul'), + level = $ul.dataSM('level'); + // 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 + if (level > 1 && (!this.activatedItems[level - 2] || this.activatedItems[level - 2][0] != $ul.dataSM('parent-a')[0])) { + var self = this; + $($ul.parentsUntil('[data-smartmenus-id]', 'ul').get().reverse()).add($ul).each(function() { + self.itemActivate($(this).dataSM('parent-a')); + }); + } + // hide any visible deeper level sub menus + if (!this.isCollapsible() || focus) { + this.menuHideSubMenus(!this.activatedItems[level - 1] || this.activatedItems[level - 1][0] != $a[0] ? level - 1 : level); + } + // save new active item for this level + this.activatedItems[level - 1] = $a; + if (this.$root.triggerHandler('activate.smapi', $a[0]) === false) { + return; + } + // show the sub menu if this item has one + var $sub = $a.dataSM('sub'); + if ($sub && (this.isTouchMode() || (!this.opts.showOnClick || this.clickActivated))) { + this.menuShow($sub); + } + }, + itemBlur: function(e) { + var $a = $(e.currentTarget); + if (!this.handleItemEvents($a)) { + return; + } + this.$root.triggerHandler('blur.smapi', $a[0]); + }, + itemClick: function(e) { + var $a = $(e.currentTarget); + if (!this.handleItemEvents($a)) { + return; + } + if (this.$touchScrollingSub && this.$touchScrollingSub[0] == $a.closest('ul')[0]) { + this.$touchScrollingSub = null; + e.stopPropagation(); + return false; + } + if (this.$root.triggerHandler('click.smapi', $a[0]) === false) { + return false; + } + var subArrowClicked = $(e.target).is('span.sub-arrow'), + $sub = $a.dataSM('sub'), + firstLevelSub = $sub ? $sub.dataSM('level') == 2 : false; + // if the sub is not visible + if ($sub && !$sub.is(':visible')) { + if (this.opts.showOnClick && firstLevelSub) { + this.clickActivated = true; + } + // try to activate the item and show the sub + this.itemActivate($a); + // if "itemActivate" showed the sub, prevent the click so that the link is not loaded + // 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 + if ($sub.is(':visible')) { + this.focusActivated = true; + return false; + } + } else if (this.isCollapsible() && subArrowClicked) { + this.itemActivate($a); + this.menuHide($sub); + return false; + } + if (this.opts.showOnClick && firstLevelSub || $a.hasClass('disabled') || this.$root.triggerHandler('select.smapi', $a[0]) === false) { + return false; + } + }, + itemDown: function(e) { + var $a = $(e.currentTarget); + if (!this.handleItemEvents($a)) { + return; + } + $a.dataSM('mousedown', true); + }, + itemEnter: function(e) { + var $a = $(e.currentTarget); + if (!this.handleItemEvents($a)) { + return; + } + if (!this.isTouchMode()) { + if (this.showTimeout) { + clearTimeout(this.showTimeout); + this.showTimeout = 0; + } + var self = this; + this.showTimeout = setTimeout(function() { self.itemActivate($a); }, this.opts.showOnClick && $a.closest('ul').dataSM('level') == 1 ? 1 : this.opts.showTimeout); + } + this.$root.triggerHandler('mouseenter.smapi', $a[0]); + }, + itemFocus: function(e) { + var $a = $(e.currentTarget); + if (!this.handleItemEvents($a)) { + return; + } + // 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 + if (this.focusActivated && (!this.isTouchMode() || !$a.dataSM('mousedown')) && (!this.activatedItems.length || this.activatedItems[this.activatedItems.length - 1][0] != $a[0])) { + this.itemActivate($a, true); + } + this.$root.triggerHandler('focus.smapi', $a[0]); + }, + itemLeave: function(e) { + var $a = $(e.currentTarget); + if (!this.handleItemEvents($a)) { + return; + } + if (!this.isTouchMode()) { + $a[0].blur(); + if (this.showTimeout) { + clearTimeout(this.showTimeout); + this.showTimeout = 0; + } + } + $a.removeDataSM('mousedown'); + this.$root.triggerHandler('mouseleave.smapi', $a[0]); + }, + menuHide: function($sub) { + if (this.$root.triggerHandler('beforehide.smapi', $sub[0]) === false) { + return; + } + $sub.stop(true, true); + if ($sub.css('display') != 'none') { + var complete = function() { + // unset z-index + $sub.css('z-index', ''); + }; + // if sub is collapsible (mobile view) + if (this.isCollapsible()) { + if (this.opts.collapsibleHideFunction) { + this.opts.collapsibleHideFunction.call(this, $sub, complete); + } else { + $sub.hide(this.opts.collapsibleHideDuration, complete); + } + } else { + if (this.opts.hideFunction) { + this.opts.hideFunction.call(this, $sub, complete); + } else { + $sub.hide(this.opts.hideDuration, complete); + } + } + // remove IE iframe shim + if ($sub.dataSM('ie-shim')) { + $sub.dataSM('ie-shim').remove().css({ '-webkit-transform': '', transform: '' }); + } + // deactivate scrolling if it is activated for this sub + if ($sub.dataSM('scroll')) { + this.menuScrollStop($sub); + $sub.css({ 'touch-action': '', '-ms-touch-action': '', '-webkit-transform': '', transform: '' }) + .unbind('.smartmenus_scroll').removeDataSM('scroll').dataSM('scroll-arrows').hide(); + } + // unhighlight parent item + accessibility + $sub.dataSM('parent-a').removeClass('highlighted').attr('aria-expanded', 'false'); + $sub.attr({ + 'aria-expanded': 'false', + 'aria-hidden': 'true' + }); + var level = $sub.dataSM('level'); + this.activatedItems.splice(level - 1, 1); + this.visibleSubMenus.splice($.inArray($sub, this.visibleSubMenus), 1); + this.$root.triggerHandler('hide.smapi', $sub[0]); + } + }, + menuHideAll: function() { + if (this.showTimeout) { + clearTimeout(this.showTimeout); + this.showTimeout = 0; + } + // hide all subs + // if it's a popup, this.visibleSubMenus[0] is the root UL + var level = this.opts.isPopup ? 1 : 0; + for (var i = this.visibleSubMenus.length - 1; i >= level; i--) { + this.menuHide(this.visibleSubMenus[i]); + } + // hide root if it's popup + if (this.opts.isPopup) { + this.$root.stop(true, true); + if (this.$root.is(':visible')) { + if (this.opts.hideFunction) { + this.opts.hideFunction.call(this, this.$root); + } else { + this.$root.hide(this.opts.hideDuration); + } + // remove IE iframe shim + if (this.$root.dataSM('ie-shim')) { + this.$root.dataSM('ie-shim').remove(); + } + } + } + this.activatedItems = []; + this.visibleSubMenus = []; + this.clickActivated = false; + this.focusActivated = false; + // reset z-index increment + this.zIndexInc = 0; + this.$root.triggerHandler('hideAll.smapi'); + }, + menuHideSubMenus: function(level) { + for (var i = this.activatedItems.length - 1; i >= level; i--) { + var $sub = this.activatedItems[i].dataSM('sub'); + if ($sub) { + this.menuHide($sub); + } + } + }, + menuIframeShim: function($ul) { + // create iframe shim for the menu + if (IE && this.opts.overlapControlsInIE && !$ul.dataSM('ie-shim')) { + $ul.dataSM('ie-shim', $('