import defaults from '../defaults'; /** * @typedef {jQuery.Event} ContextMenuEvent * @augments jQuery.Event * @property {ContextMenuData} data */ export default class ContextMenuEventHandler { /** * @constructs ContextMenuEventHandler * @constructor * @property {?JQuery} $currentTrigger * @property {Object} hoveract */ constructor() { this.$currentTrigger = null; this.hoveract = {}; } /** * Helper to abort an event * * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ abortevent(e) { e.preventDefault(); e.stopImmediatePropagation(); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ contextmenu(e) { const $this = $(e.currentTarget); if (!e.data) { throw new Error('No data attached'); } // disable actual context-menu if we are using the right mouse button as the trigger if (e.data.trigger === 'right') { e.preventDefault(); e.stopImmediatePropagation(); } // abort native-triggered events unless we're triggering on right click if ((e.data.trigger !== 'right' && e.data.trigger !== 'demand') && e.originalEvent) { return; } // Let the current contextmenu decide if it should show or not based on its own trigger settings if (typeof e.mouseButton !== 'undefined') { if (!(e.data.trigger === 'left' && e.mouseButton === 0) && !(e.data.trigger === 'right' && e.mouseButton === 2)) { // Mouse click is not valid. return; } } // abort event if menu is visible for this trigger if ($this.hasClass('context-menu-active')) { return; } if (!$this.hasClass('context-menu-disabled')) { // theoretically need to fire a show event at <menu> // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this }); // e.data.$menu.trigger(evt); e.data.manager.handler.$currentTrigger = $this; if (e.data.build) { const built = e.data.build(e, $this); // abort if build() returned false if (built === false) { return; } // dynamically build menu on invocation e.data = $.extend(true, {}, defaults, e.data, built || {}); // abort if there are no items to display if (!e.data.items || $.isEmptyObject(e.data.items)) { // Note: jQuery captures and ignores errors from event handlers if (window.console) { (console.error || console.log).call(console, 'No items specified to show in contextMenu'); } throw new Error('No Items specified'); } // backreference for custom command type creation e.data.$trigger = e.data.manager.handler.$currentTrigger; e.data.manager.operations.create(e, e.data); } let showMenu = false; for (let item in e.data.items) { if (e.data.items.hasOwnProperty(item)) { let visible; if ($.isFunction(e.data.items[item].visible)) { visible = e.data.items[item].visible.call($this, e, item, e.data, e.data); } else if (typeof e.data.items[item] !== 'undefined' && e.data.items[item].visible) { visible = e.data.items[item].visible === true; } else { visible = true; } if (visible) { showMenu = true; } } } if (showMenu) { // show menu e.data.manager.operations.show.call($this, e, e.data, e.pageX, e.pageY); } } } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ click(e) { e.preventDefault(); e.stopImmediatePropagation(); $(this).trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY, originalEvent: e})); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ mousedown(e) { // register mouse down const $this = $(this); // hide any previous menus if (e.data.manager.handler.$currentTrigger && e.data.manager.handler.$currentTrigger.length && !e.data.manager.handler.$currentTrigger.is($this)) { e.data.manager.handler.$currentTrigger.data('contextMenu').$menu.trigger($.Event('contextmenu', {data: e.data, originalEvent: e})); } // activate on right click if (e.button === 2) { e.data.manager.handler.$currentTrigger = $this.data('contextMenuActive', true); } } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ mouseup(e) { // show menu const $this = $(this); if ($this.data('contextMenuActive') && e.data.manager.handler.$currentTrigger && e.data.manager.handler.$currentTrigger.length && e.data.manager.handler.$currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) { e.preventDefault(); e.stopImmediatePropagation(); e.data.manager.handler.$currentTrigger = $this; $this.trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY, originalEvent: e})); } $this.removeData('contextMenuActive'); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ mouseenter(e) { const $this = $(this); const $related = $(e.relatedTarget); const $document = $(document); // abort if we're coming from a menu if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { return; } // abort if a menu is shown if (e.data.manager.handler.$currentTrigger && e.data.manager.handler.$currentTrigger.length) { return; } e.data.manager.handler.hoveract.pageX = e.pageX; e.data.manager.handler.hoveract.pageY = e.pageY; e.data.manager.handler.hoveract.data = e.data; $document.on('mousemove.contextMenuShow', e.data.manager.handler.mousemove); e.data.manager.handler.hoveract.timer = setTimeout(function () { e.data.manager.handler.hoveract.timer = null; $document.off('mousemove.contextMenuShow'); e.data.manager.handler.$currentTrigger = $this; $this.trigger($.Event('contextmenu', { data: e.data.manager.handler.hoveract.data, pageX: e.data.manager.handler.hoveract.pageX, pageY: e.data.manager.handler.hoveract.pageY })); }, e.data.delay); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ mousemove(e) { e.data.manager.handler.hoveract.pageX = e.pageX; e.data.manager.handler.hoveract.pageY = e.pageY; } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ mouseleave(e) { // abort if we're leaving for a menu const $related = $(e.relatedTarget); if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { return; } try { clearTimeout(e.data.manager.handler.hoveract.timer); } catch (e) { } e.data.manager.handler.hoveract.timer = null; } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ layerClick(e) { let $this = $(this); let root = $this.data('contextMenuRoot'); if (root === null || typeof root === 'undefined') { throw new Error('No ContextMenuData found'); } let button = e.button; let x = e.pageX; let y = e.pageY; let target; let offset; e.preventDefault(); setTimeout(function () { let $window = $(window); let triggerAction = ((root.trigger === 'left' && button === 0) || (root.trigger === 'right' && button === 2)); // find the element that would've been clicked, wasn't the layer in the way if (document.elementFromPoint && root.$layer) { root.$layer.hide(); target = document.elementFromPoint(x - $window.scrollLeft(), y - $window.scrollTop()); // also need to try and focus this element if we're in a contenteditable area, // as the layer will prevent the browser mouse action we want if (target.isContentEditable) { const range = document.createRange(); const sel = window.getSelection(); range.selectNode(target); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); } $(target).trigger(e); root.$layer.show(); } if (root.hideOnSecondTrigger && triggerAction && root.$menu !== null && typeof root.$menu !== 'undefined') { root.$menu.trigger('contextmenu:hide', {data: root, originalEvent: e}); return; } if (root.reposition && triggerAction) { if (document.elementFromPoint) { if (root.$trigger.is(target)) { root.position.call(root.$trigger, e, root, x, y); return; } } else { offset = root.$trigger.offset(); const $window = $(window); // while this looks kinda awful, it's the best way to avoid // unnecessarily calculating any positions offset.top += $window.scrollTop(); if (offset.top <= e.pageY) { offset.left += $window.scrollLeft(); if (offset.left <= e.pageX) { offset.bottom = offset.top + root.$trigger.outerHeight(); if (offset.bottom >= e.pageY) { offset.right = offset.left + root.$trigger.outerWidth(); if (offset.right >= e.pageX) { // reposition root.position.call(root.$trigger, e, root, x, y); return; } } } } } } if (target && triggerAction) { root.$trigger.one('contextmenu:hidden', function () { $(target).contextMenu({x: x, y: y, button: button, originalEvent: e}); }); } if (root.$menu !== null && typeof root.$menu !== 'undefined') { root.$menu.trigger('contextmenu:hide', {data: root, originalEvent: e}); } }, 50); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e * @param {ContextMenuItem} currentMenuData */ keyStop(e, currentMenuData) { if (!currentMenuData.isInput) { e.preventDefault(); } e.stopPropagation(); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ key(e) { let rootMenuData = {}; // Only get the data from this.$currentTrigger if it exists if (e.data.manager.handler.$currentTrigger) { rootMenuData = e.data.manager.handler.$currentTrigger.data('contextMenu') || {}; } // If the trigger happen on a element that are above the contextmenu do this if (typeof rootMenuData.zIndex === 'undefined') { rootMenuData.zIndex = 0; } const getZIndexOfTriggerTarget = function (target) { if (target.style.zIndex !== '') { return target.style.zIndex; } else { if (target.offsetParent !== null && typeof target.offsetParent !== 'undefined') { return getZIndexOfTriggerTarget(target.offsetParent); } else if (target.parentElement !== null && typeof target.parentElement !== 'undefined') { return getZIndexOfTriggerTarget(target.parentElement); } } }; let targetZIndex = getZIndexOfTriggerTarget(e.target); // If targetZIndex is heigher then rootMenuData.zIndex dont progress any futher. // This is used to make sure that if you are using a dialog with a input / textarea / contenteditable div // and its above the contextmenu it wont steal keys events if (rootMenuData.$menu && parseInt(targetZIndex, 10) > parseInt(rootMenuData.$menu.css('zIndex'), 10)) { return; } switch (e.keyCode) { case 9: case 38: // up e.data.manager.handler.keyStop(e, rootMenuData); // if keyCode is [38 (up)] or [9 (tab) with shift] if (rootMenuData.isInput) { if (e.keyCode === 9 && e.shiftKey) { e.preventDefault(); if (rootMenuData.$selected) { rootMenuData.$selected.find('input, textarea, select').blur(); } if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') { rootMenuData.$menu.trigger('prevcommand', {data: rootMenuData, originalEvent: e}); } return; } else if (e.keyCode === 38 && rootMenuData.$selected.find('input, textarea, select').prop('type') === 'checkbox') { // checkboxes don't capture this key e.preventDefault(); return; } } else if (e.keyCode !== 9 || e.shiftKey) { if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') { rootMenuData.$menu.trigger('prevcommand', {data: rootMenuData, originalEvent: e}); } return; } break; // omitting break; // case 9: // tab - reached through omitted break; case 40: // down e.data.manager.handler.keyStop(e, rootMenuData); if (rootMenuData.isInput) { if (e.keyCode === 9) { e.preventDefault(); if (rootMenuData.$selected) { rootMenuData.$selected.find('input, textarea, select').blur(); } if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') { rootMenuData.$menu.trigger('nextcommand', {data: rootMenuData, originalEvent: e}); } return; } else if (e.keyCode === 40 && rootMenuData.$selected.find('input, textarea, select').prop('type') === 'checkbox') { // checkboxes don't capture this key e.preventDefault(); return; } } else { if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') { rootMenuData.$menu.trigger('nextcommand', {data: rootMenuData, originalEvent: e}); } return; } break; case 37: // left e.data.manager.handler.keyStop(e, rootMenuData); if (rootMenuData.isInput || !rootMenuData.$selected || !rootMenuData.$selected.length) { break; } if (!rootMenuData.$selected.parent().hasClass('context-menu-root')) { const $parent = rootMenuData.$selected.parent().parent(); rootMenuData.$selected.trigger('contextmenu:blur', {data: rootMenuData, originalEvent: e}); rootMenuData.$selected = $parent; return; } break; case 39: // right e.data.manager.handler.keyStop(e, rootMenuData); if (rootMenuData.isInput || !rootMenuData.$selected || !rootMenuData.$selected.length) { break; } const itemdata = rootMenuData.$selected.data('contextMenu') || {}; if (itemdata.$menu && rootMenuData.$selected.hasClass('context-menu-submenu')) { rootMenuData.$selected = null; itemdata.$selected = null; itemdata.$menu.trigger('nextcommand', {data: itemdata, originalEvent: e}); return; } break; case 35: // end case 36: // home if (rootMenuData.$selected && rootMenuData.$selected.find('input, textarea, select').length) { break; } else { ((rootMenuData.$selected && rootMenuData.$selected.parent()) || rootMenuData.$menu) .children(':not(.' + rootMenuData.classNames.disabled + ', .' + rootMenuData.classNames.notSelectable + ')')[e.keyCode === 36 ? 'first' : 'last']() .trigger('contextmenu:focus', {data: rootMenuData, originalEvent: e}); e.preventDefault(); break; } case 13: // enter e.data.manager.handler.keyStop(e, rootMenuData); if (rootMenuData.isInput) { if (rootMenuData.$selected && !rootMenuData.$selected.is('textarea, select')) { e.preventDefault(); return; } break; } if (typeof rootMenuData.$selected !== 'undefined' && rootMenuData.$selected !== null) { rootMenuData.$selected.trigger('mouseup', {data: rootMenuData, originalEvent: e}); } return; case 32: // space case 33: // page up case 34: // page down // prevent browser from scrolling down while menu is visible e.data.manager.handler.keyStop(e, rootMenuData); return; case 27: // esc e.data.manager.handler.keyStop(e, rootMenuData); if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') { rootMenuData.$menu.trigger('contextmenu:hide', {data: rootMenuData, originalEvent: e}); } return; default: // 0-9, a-z const k = (String.fromCharCode(e.keyCode)).toUpperCase(); if (rootMenuData.accesskeys && rootMenuData.accesskeys[k]) { // according to the specs accesskeys must be invoked immediately rootMenuData.accesskeys[k].$node.trigger(rootMenuData.accesskeys[k].$menu ? 'contextmenu:focus' : 'mouseup', {data: rootMenuData, originalEvent: e}); return; } break; } // pass event to selected item, // stop propagation to avoid endless recursion e.stopPropagation(); if (typeof rootMenuData.$selected !== 'undefined' && rootMenuData.$selected !== null) { rootMenuData.$selected.trigger(e); } } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ prevItem(e) { e.stopPropagation(); let currentMenuData = $(this).data('contextMenu') || {}; const rootMenuData = $(this).data('contextMenuRoot') || {}; // obtain currently selected menu if (currentMenuData.$selected) { const $s = currentMenuData.$selected; currentMenuData = currentMenuData.$selected.parent().data('contextMenu') || {}; currentMenuData.$selected = $s; } const $children = currentMenuData.$menu.children(); let $prev = !currentMenuData.$selected || !currentMenuData.$selected.prev().length ? $children.last() : currentMenuData.$selected.prev(); const $round = $prev; // skip disabled or hidden elements while ($prev.hasClass(rootMenuData.classNames.disabled) || $prev.hasClass(rootMenuData.classNames.notSelectable) || $prev.is(':hidden')) { if ($prev.prev().length) { $prev = $prev.prev(); } else { $prev = $children.last(); } if ($prev.is($round)) { // break endless loop return; } } // leave current if (currentMenuData.$selected) { rootMenuData.manager.handler.itemMouseleave.call(currentMenuData.$selected.get(0), e); } // activate next rootMenuData.manager.handler.itemMouseenter.call($prev.get(0), e); // focus input const $input = $prev.find('input, textarea, select'); if ($input.length) { $input.focus(); } } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ nextItem(e) { e.stopPropagation(); let currentMenuData = $(this).data('contextMenu') || {}; let rootMenuData = $(this).data('contextMenuRoot') || {}; // obtain currently selected menu if (currentMenuData.$selected) { const $s = currentMenuData.$selected; currentMenuData = currentMenuData.$selected.parent().data('contextMenu') || {}; currentMenuData.$selected = $s; } const $children = currentMenuData.$menu.children(); let $next = !currentMenuData.$selected || !currentMenuData.$selected.next().length ? $children.first() : currentMenuData.$selected.next(); const $round = $next; // skip disabled while ($next.hasClass(rootMenuData.classNames.disabled) || $next.hasClass(rootMenuData.classNames.notSelectable) || $next.is(':hidden')) { if ($next.next().length) { $next = $next.next(); } else { $next = $children.first(); } if ($next.is($round)) { // break endless loop return; } } // leave current if (currentMenuData.$selected) { rootMenuData.manager.handler.itemMouseleave.call(currentMenuData.$selected.get(0), e); } // activate next rootMenuData.manager.handler.itemMouseenter.call($next.get(0), e); // focus input const $input = $next.find('input, textarea, select'); if ($input.length) { $input.focus(); } } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ focusInput(e) { let $this = $(this).closest('.context-menu-item'); let data = $this.data(); let currentMenuData = data.contextMenu; let rootMenuData = data.contextMenuRoot; rootMenuData.$selected = currentMenuData.$selected = $this; rootMenuData.isInput = currentMenuData.isInput = true; } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ blurInput(e) { let $this = $(this).closest('.context-menu-item'); let data = $this.data(); let currentMenuData = data.contextMenu; let rootMenuData = data.contextMenuRoot; rootMenuData.isInput = currentMenuData.isInput = false; } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ menuMouseenter(e) { let root = $(this).data().contextMenuRoot; root.hovering = true; } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ menuMouseleave(e) { let root = $(this).data().contextMenuRoot; if (root.$layer && root.$layer.is(e.relatedTarget)) { root.hovering = false; } } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ itemMouseenter(e) { let $this = $(this); let data = $this.data(); let currentMenuData = data.contextMenu; let rootMenuData = data.contextMenuRoot; rootMenuData.hovering = true; // abort if we're re-entering if (e && rootMenuData.$layer && rootMenuData.$layer.is(e.relatedTarget)) { e.preventDefault(); e.stopImmediatePropagation(); } // make sure only one item is selected let targetMenu = (currentMenuData.$menu ? currentMenuData : rootMenuData); targetMenu.$menu .children('.' + rootMenuData.classNames.hover).trigger('contextmenu:blur', {data: targetMenu, originalEvent: e}) .children('.hover').trigger('contextmenu:blur', {data: targetMenu, originalEvent: e}); if ($this.hasClass(rootMenuData.classNames.disabled) || $this.hasClass(rootMenuData.classNames.notSelectable)) { currentMenuData.$selected = null; return; } $this.trigger('contextmenu:focus', {data: currentMenuData, originalEvent: e}); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ itemMouseleave(e) { let $this = $(this); let data = $this.data(); let currentMenuData = data.contextMenu; let rootMenuData = data.contextMenuRoot; if (rootMenuData !== currentMenuData && rootMenuData.$layer && rootMenuData.$layer.is(e.relatedTarget)) { if (typeof rootMenuData.$selected !== 'undefined' && rootMenuData.$selected !== null) { rootMenuData.$selected.trigger('contextmenu:blur', {data: rootMenuData, originalEvent: e}); } e.preventDefault(); e.stopImmediatePropagation(); rootMenuData.$selected = currentMenuData.$selected = currentMenuData.$node; return; } if (currentMenuData && currentMenuData.$menu && currentMenuData.$menu.hasClass(rootMenuData.classNames.visible)) { return; } $this.trigger('contextmenu:blur'); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ itemClick(e) { let $this = $(this); let data = $this.data(); let currentMenuData = data.contextMenu; let rootMenuData = data.contextMenuRoot; let key = data.contextMenuKey; let callback; // abort if the key is unknown or disabled or is a menu if (!currentMenuData.items[key] || $this.is('.' + rootMenuData.classNames.disabled + ', .context-menu-separator, .' + rootMenuData.classNames.notSelectable) || ($this.is('.context-menu-submenu') && rootMenuData.selectableSubMenu === false)) { return; } e.preventDefault(); e.stopImmediatePropagation(); if ($.isFunction(currentMenuData.callbacks[key]) && Object.prototype.hasOwnProperty.call(currentMenuData.callbacks, key)) { // item-specific callback callback = currentMenuData.callbacks[key]; } else if ($.isFunction(rootMenuData.callback)) { // default callback callback = rootMenuData.callback; } else { // no callback, no action return; } // hide menu if callback doesn't stop that if (callback.call(rootMenuData.$trigger, e, key, currentMenuData, rootMenuData) !== false) { rootMenuData.$menu.trigger('contextmenu:hide'); } else if (rootMenuData.$menu.parent().length) { rootMenuData.manager.operations.update.call(rootMenuData.$trigger, e, rootMenuData); } } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ inputClick(e) { e.stopImmediatePropagation(); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e * @param {Object} data */ hideMenu(e, data) { console.log(e); console.log(e.originalEvent); const root = $(this).data('contextMenuRoot'); root.manager.operations.hide.call(root.$trigger, e, root, data && data.force); } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ focusItem(e) { e.stopPropagation(); const $this = $(this); const data = $this.data(); const currentMenuData = data.contextMenu; const rootMenuData = data.contextMenuRoot; if ($this.hasClass(rootMenuData.classNames.disabled) || $this.hasClass(rootMenuData.classNames.notSelectable)) { return; } $this .addClass([rootMenuData.classNames.hover, rootMenuData.classNames.visible].join(' ')) // select other items and included items .parent().find('.context-menu-item').not($this) .removeClass(rootMenuData.classNames.visible) .filter('.' + rootMenuData.classNames.hover) .trigger('contextmenu:blur'); // remember selected currentMenuData.$selected = rootMenuData.$selected = $this; if (currentMenuData.$node && currentMenuData.$node.hasClass('context-menu-submenu')) { currentMenuData.$node.addClass(rootMenuData.classNames.hover); } // position sub-menu - do after show so dumb $.ui.position can keep up if (currentMenuData.$node) { rootMenuData.positionSubmenu.call(currentMenuData.$node, e, currentMenuData.$menu); } } /** * @method * @memberOf ContextMenuEventHandler * @instance * * @param {ContextMenuEvent|JQuery.Event} e */ blurItem(e) { e.stopPropagation(); const $this = $(this); const data = $this.data(); const currentMenuData = data.contextMenu; const rootMenuData = data.contextMenuRoot; if (rootMenuData.autoHide) { // for tablets and touch screens this needs to remain $this.removeClass(rootMenuData.classNames.visible); } $this.removeClass(rootMenuData.classNames.hover); currentMenuData.$selected = null; } };