/* * * (c) Copyright Ascensio System SIA 2010-2019 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ /** * Menu.js * * A menu object. This is the container to which you may add {@link Common.UI.MenuItem menu items}. * * Created by Alexander Yuzhin on 1/28/14 * Copyright (c) 2018 Ascensio System SIA. All rights reserved. * */ /** * Default template * * * * A useful classes of menu position * * - `'pull-right'` using for layout menu by right side of a parent * * * Example usage: * * new Common.UI.Menu({ * items: [ * { caption: 'item 1', value: 1 }, * { caption: 'item 1', value: 2 }, * { caption: '--' }, * { caption: 'item 1', value: 3 }, * ] * }) * * @property {Object} itemTemplate * * Default template for items * * * @property {Array} items * * Arrow of the {Common.UI.MenuItem} menu items * * * @property {Boolean/Number} restoreHeight * * Adjust to the browser height and restore to restoreHeight when it's Number * * */ if (Common === undefined) var Common = {}; define([ 'common/main/lib/extend/Bootstrap', 'common/main/lib/component/BaseView', 'common/main/lib/component/MenuItem', 'common/main/lib/component/Scroller' ], function () { 'use strict'; Common.UI.Menu = (function(){ var manager = (function(){ var active = [], menus = {}; return { register: function(menu) { menus[menu.id] = menu; menu .on('show:after', function(m) { active.push(m); }) .on('hide:after', function(m) { var index = active.indexOf(m); if (index > -1) active.splice(index, 1); }); }, unregister: function(menu) { var index = active.indexOf(menu); delete menus[menu.id]; if (index > -1) active.splice(index, 1); menu.off('show:after').off('hide:after'); }, hideAll: function() { Common.NotificationCenter.trigger('menumanager:hideall'); if (active && active.length > 0) { _.each(active, function(menu) { if (menu) menu.hide(); }); return true; } return false; } } })(); return _.extend(Common.UI.BaseView.extend({ options : { cls : '', style : '', itemTemplate: null, items : [], menuAlign : 'tl-bl', menuAlignEl : null, offset : [0, 0], cyclic : true, search : false, scrollAlwaysVisible: true }, template: _.template([ '' ].join('')), initialize : function(options) { Common.UI.BaseView.prototype.initialize.call(this, options); var me = this; this.id = this.options.id || Common.UI.getId(); this.itemTemplate = this.options.itemTemplate || Common.UI.MenuItem.prototype.template; this.rendered = false; this.items = []; this.offset = [0, 0]; this.menuAlign = this.options.menuAlign; this.menuAlignEl = this.options.menuAlignEl; this.scrollAlwaysVisible = this.options.scrollAlwaysVisible; this.search = this.options.search; this.outerMenu = this.options.outerMenu; if (this.options.restoreHeight) { this.options.restoreHeight = (typeof (this.options.restoreHeight) == "number") ? this.options.restoreHeight : (this.options.maxHeight ? this.options.maxHeight : 100000); !this.options.maxHeight && (this.options.maxHeight = this.options.restoreHeight); } if (!this.options.cyclic) this.options.cls += ' no-cyclic'; _.each(this.options.items, function(item) { if (item instanceof Common.UI.MenuItem) { me.items.push(item) } else { me.items.push( new Common.UI.MenuItem(_.extend({ tagName : 'li', template: me.itemTemplate }, item)) ); } }); if (this.options.el) this.render(); manager.register(this); }, remove: function() { manager.unregister(this); Common.UI.BaseView.prototype.remove.call(this); }, render: function(parentEl) { var me = this; this.trigger('render:before', this); this.cmpEl = me.$el || $(this.el); if (parentEl) { this.setElement(parentEl, false); if (!me.rendered) { this.cmpEl = $(this.template({ options : me.options })); parentEl.append(this.cmpEl); } } else { if (!me.rendered) { this.cmpEl = this.template({ options : me.options }); this.$el.append(this.cmpEl); } } var rootEl = this.cmpEl.parent(), menuRoot = (rootEl.attr('role') === 'menu') ? rootEl : rootEl.find('[role=menu]'); this.menuRoot = menuRoot; if (menuRoot) { if (!me.rendered) { _.each(me.items || [], function(item) { menuRoot.append(item.render().el); item.on('click', _.bind(me.onItemClick, me)); item.on('toggle', _.bind(me.onItemToggle, me)); }); } if (this.options.maxHeight) { menuRoot.css({'max-height': me.options.maxHeight}); this.scroller = new Common.UI.Scroller({ el: me.$el.find('.dropdown-menu '), minScrollbarLength: 30, suppressScrollX: true, alwaysVisibleY: this.scrollAlwaysVisible }); } menuRoot.css({ position : 'fixed', right : 'auto', left : -1000, top : -1000 }); this.parentEl = menuRoot.parent(); this.parentEl.on('show.bs.dropdown', _.bind(me.onBeforeShowMenu, me)); this.parentEl.on('shown.bs.dropdown', _.bind(me.onAfterShowMenu, me)); this.parentEl.on('hide.bs.dropdown', _.bind(me.onBeforeHideMenu, me)); this.parentEl.on('hidden.bs.dropdown', _.bind(me.onAfterHideMenu, me)); this.parentEl.on('keydown.after.bs.dropdown', _.bind(me.onAfterKeydownMenu, me)); this.parentEl.on('keydown.before.bs.dropdown', _.bind(me.onBeforeKeydownMenu, me)); this.options.innerMenus && this.on('keydown:before', _.bind(me.onBeforeKeyDown, me)); menuRoot.hover( function(e) { me.isOver = true;}, function(e) { me.isOver = false; } ); } this.rendered = true; this.trigger('render:after', this); return this; }, isVisible: function() { return this.rendered && (this.cmpEl.is(':visible')); }, show: function() { if (this.rendered && this.parentEl && !this.parentEl.hasClass('open')) { this.cmpEl.dropdown('toggle'); } }, hide: function() { if (this.rendered && this.parentEl) { if ( this.parentEl.hasClass('open') ) this.cmpEl.dropdown('toggle'); else if (this.parentEl.hasClass('over')) this.parentEl.removeClass('over'); } }, insertItem: function(index, item) { var me = this, el = this.cmpEl; if (!(item instanceof Common.UI.MenuItem)) { item = new Common.UI.MenuItem(_.extend({ tagName : 'li', template: me.itemTemplate }, item)); } if (index < 0 || index >= me.items.length) me.items.push(item); else me.items.splice(index, 0, item); if (this.rendered) { var menuRoot = this.menuRoot; if (menuRoot) { if (index < 0) { menuRoot.append(item.render().el); } else if (index === 0) { menuRoot.prepend(item.render().el); } else { menuRoot.children('li:nth-child(' + (index+1) + ')').before(item.render().el); } item.on('click', _.bind(me.onItemClick, me)); item.on('toggle', _.bind(me.onItemToggle, me)); } } }, addItem: function(item) { this.insertItem(-1, item); }, removeItem: function(item) { var me = this, index = me.items.indexOf(item); if (index > -1) { me.items.splice(index, 1); item.off('click').off('toggle'); item.remove(); } }, removeItems: function(from, len) { if (from > this.items.length-1) return; if (from+len>this.items.length) len = this.items.length - from; for (var i=from; i li:first'), itemHeight = (item.length) ? item.outerHeight() : 1; this.wheelSpeed = Math.min((Math.floor(menuRoot.height()/itemHeight) * itemHeight)/10, 20); } this.scroller.update({alwaysVisibleY: this.scrollAlwaysVisible, wheelSpeed: this.wheelSpeed}); var $selected = menuRoot.find('> li .checked'); if ($selected.length) { var itemTop = $selected.position().top, itemHeight = $selected.outerHeight(), listHeight = menuRoot.outerHeight(); if (itemTop < 0 || itemTop + itemHeight > listHeight) { var height = menuRoot.scrollTop() + itemTop + (itemHeight - listHeight)/2; height = (Math.floor(height/itemHeight) * itemHeight); menuRoot.scrollTop(height); } setTimeout(function(){$selected.focus();}, 1); } } this._search = {}; }, onBeforeHideMenu: function(e) { this.trigger('hide:before', this, e); if (Common.UI.Scroller.isMouseCapture()) e.preventDefault(); }, onAfterHideMenu: function(e, isFromInputControl) { this.trigger('hide:after', this, e, isFromInputControl); Common.NotificationCenter.trigger('menu:hide', this, isFromInputControl); }, onAfterKeydownMenu: function(e) { this.trigger('keydown:before', this, e); if (e.isDefaultPrevented()) return; if (e.keyCode == Common.UI.Keys.RETURN) { var li = $(e.target).closest('li'); if (li.length<=0) li = $(e.target).parent().find('li .dataview'); if (li.length>0) li.click(); if (!li.hasClass('dropdown-submenu')) Common.UI.Menu.Manager.hideAll(); if ( $(e.currentTarget).closest('li').hasClass('dropdown-submenu')) { e.stopPropagation(); return false; } } else if (e.keyCode == Common.UI.Keys.UP || e.keyCode == Common.UI.Keys.DOWN) { this.fromKeyDown = true; } else if (e.keyCode == Common.UI.Keys.ESC) { // Common.NotificationCenter.trigger('menu:afterkeydown', e); // return false; } else if (this.search && e.keyCode > 64 && e.keyCode < 91 && e.key){ var me = this; clearTimeout(this._search.timer); this._search.timer = setTimeout(function () { me._search = {}; }, 1000); (!this._search.text) && (this._search.text = ''); (!this._search.char) && (this._search.char = e.key); (this._search.char !== e.key) && (this._search.full = true); this._search.text += e.key; if (this._search.index===undefined) { var $items = this.menuRoot.find('> li').find('> a'); this._search.index = $items.index($items.filter(':focus')); } this.selectCandidate(); } }, onBeforeKeydownMenu: function(e) { if (e.isDefaultPrevented() || !(this.outerMenu && this.outerMenu.menu)) return; if (e.keyCode == Common.UI.Keys.UP || e.keyCode == Common.UI.Keys.DOWN) { var $items = this.menuRoot.find('> li').find('> a'), index = $items.index($items.filter(':focus')); if (e.keyCode==Common.UI.Keys.UP && index==0 || e.keyCode == Common.UI.Keys.DOWN && index==$items.length-1) { this.outerMenu.menu.focusOuter(e, this.outerMenu.index); e.preventDefault(); e.stopPropagation(); } } }, selectCandidate: function() { var index = this._search.index || 0, re = new RegExp('^' + ((this._search.full) ? this._search.text : this._search.char), 'i'), itemCandidate, idxCandidate; for (var i=0; iindex) { itemCandidate = item; idxCandidate = i; break; } } } if (itemCandidate) { this._search.index = idxCandidate; var item = itemCandidate.cmpEl.find('a'); if (this.scroller) { this.scroller.update({alwaysVisibleY: this.scrollAlwaysVisible, wheelSpeed: this.wheelSpeed}); var itemTop = item.position().top, itemHeight = item.outerHeight(), listHeight = this.menuRoot.outerHeight(); if (itemTop < 0 || itemTop + itemHeight > listHeight) { var height = this.menuRoot.scrollTop() + itemTop; height = (Math.floor(height/itemHeight) * itemHeight); this.menuRoot.scrollTop(height); } } item.focus(); } }, onBeforeKeyDown: function(menu, e) { if (e.keyCode == Common.UI.Keys.RETURN) { var li = $(e.target).closest('li'); if (li.length>0) { e.preventDefault(); e.stopPropagation(); li.click(); } Common.UI.Menu.Manager.hideAll(); } else if (e.namespace!=="after.bs.dropdown" && (e.keyCode == Common.UI.Keys.DOWN || e.keyCode == Common.UI.Keys.UP)) { if ( this.menuRoot.length<1 || $(e.target).closest('ul[role=menu]').get(0) !== this.menuRoot.get(0)) return; var innerMenu = this.findInnerMenu(e.keyCode); if (innerMenu && innerMenu.focusInner) { e.preventDefault(); e.stopPropagation(); _.delay(function() { innerMenu.focusInner(e); }, 10); } } }, setInnerMenu: function(menus) { if (this.options.innerMenus || !menus) return; this.options.innerMenus = menus; this.rendered && this.on('keydown:before', _.bind(this.onBeforeKeyDown, this)); }, findInnerMenu: function(direction, index, findOuter) { if (!this.options.innerMenus) return; var $allItems = $('> li', this.menuRoot), $liItems = $('> li:not(.divider):not(.disabled):visible', this.menuRoot), length = $liItems.length; if (!length) return; var step = 0; while (step a').filter(':focus').parent()); var checkedIndex = (direction == Common.UI.Keys.DOWN) ? (focusedIndex0 ? focusedIndex-1 : length-1), checkedItem = $liItems.eq(checkedIndex); index = $allItems.index(checkedItem); for (var i=0; i a').length>0) return findOuter ? checkedItem : undefined; step++; } }, focusInner: function(e) { if (e.keyCode == Common.UI.Keys.UP) this.items[this.items.length-1].cmpEl.find('> a').focus(); else this.items[0].cmpEl.find('> a').focus(); }, focusOuter: function(e, index) { var innerMenu = this.findInnerMenu(e.keyCode, index, true); if (innerMenu && innerMenu.focusInner) { _.delay(function() { innerMenu.focusInner(e); }, 10); } else if (innerMenu) { innerMenu.find('> a').focus(); } else { var $items = $('> li:not(.divider):not(.disabled):visible', this.menuRoot).find('> a'), length = $items.length; length && $items.eq(e.keyCode == Common.UI.Keys.UP ? (index<0 ? length-1 : index) : (index>=length-1 ? 0 : index+1)).focus(); } }, onItemClick: function(item, e) { if (!item.menu) this.isOver = false; if (item.options.stopPropagation) { e.stopPropagation(); var me = this; _.delay(function(){ me.$el.parent().parent().find('[data-toggle=dropdown]').focus(); }, 10); return; } this.trigger('item:click', this, item, e); }, onItemToggle: function(item, state, e) { this.trigger('item:toggle', this, item, state, e); }, setOffset: function(offsetX, offsetY) { this.offset[0] = _.isUndefined(offsetX) ? this.offset[0] : offsetX; this.offset[1] = _.isUndefined(offsetY) ? this.offset[1] : offsetY; this.alignPosition(); }, getOffset: function() { return this.offset; }, alignPosition: function(fixedAlign, fixedOffset) { var menuRoot = this.menuRoot, menuParent = this.menuAlignEl || menuRoot.parent(), m = this.menuAlign.match(/^([a-z]+)-([a-z]+)/), offset = menuParent.offset(), docW = Common.Utils.innerWidth() - 10, docH = Common.Utils.innerHeight() - 10, // Yep, it's magic number menuW = menuRoot.outerWidth(), menuH = menuRoot.outerHeight(), parentW = menuParent.outerWidth(), parentH = menuParent.outerHeight(); var posMenu = { 'tl': [0, 0], 'bl': [0, menuH], 'tr': [menuW, 0], 'br': [menuW, menuH] }; var posParent = { 'tl': [0, 0], 'tr': [parentW, 0], 'bl': [0, parentH], 'br': [parentW, parentH] }; var left = offset.left - posMenu[m[1]][0] + posParent[m[2]][0] + this.offset[0]; var top = offset.top - posMenu[m[1]][1] + posParent[m[2]][1] + this.offset[1]; if (left + menuW > docW) if (menuParent.is('li.dropdown-submenu')) { left = offset.left - menuW + 2; } else { left = docW - menuW; } if (left < 0) left = 0; if (this.options.restoreHeightAndTop) { // can change top position, if top<0 - then change menu height var cg = Common.Utils.croppedGeometry(); docH = cg.height - 10; menuRoot.css('max-height', 'none'); menuH = menuRoot.outerHeight(); if (top + menuH > docH + cg.top) { top = docH - menuH; } if (top < cg.top) top = cg.top; if (top + menuH > docH + cg.top) { menuRoot.css('max-height', (docH - top) + 'px'); (!this.scroller) && (this.scroller = new Common.UI.Scroller({ el: this.$el.find('> .dropdown-menu '), minScrollbarLength: 30, suppressScrollX: true, alwaysVisibleY: this.scrollAlwaysVisible })); this.wheelSpeed = undefined; } this.scroller && this.scroller.update({alwaysVisibleY: this.scrollAlwaysVisible}); } else if (this.options.restoreHeight) { if (typeof (this.options.restoreHeight) == "number") { if (top + menuH > docH) { menuRoot.css('max-height', (docH - top) + 'px'); (!this.scroller) && (this.scroller = new Common.UI.Scroller({ el: this.$el.find('.dropdown-menu '), minScrollbarLength: 30, suppressScrollX: true, alwaysVisibleY: this.scrollAlwaysVisible })); this.wheelSpeed = undefined; } else if ( top + menuH < docH && menuRoot.height() < this.options.restoreHeight) { menuRoot.css('max-height', (Math.min(docH - top, this.options.restoreHeight)) + 'px'); this.wheelSpeed = undefined; } } } else { var cg = Common.Utils.croppedGeometry(); docH = cg.height - 10; if (top + menuH > docH + cg.top) { if (fixedAlign && typeof fixedAlign == 'string') { // how to align if menu height > window height m = fixedAlign.match(/^([a-z]+)-([a-z]+)/); top = offset.top - posMenu[m[1]][1] + posParent[m[2]][1] + this.offset[1] + (fixedOffset || 0); } else top = docH - menuH; } if (top < cg.top) top = cg.top; } if (this.options.additionalAlign) this.options.additionalAlign.call(this, menuRoot, left, top); else { var _css = {left: left, top: top}; if (!(menuH < docH)) _css['margin-top'] = 0; menuRoot.css(_css); } }, clearAll: function() { _.each(this.items, function(item){ if (item.setChecked) item.setChecked(false, true); }); } }), { Manager: (function() { return manager; })() }) })(); Common.UI.MenuSimple = Common.UI.BaseView.extend({ options : { cls : '', style : '', itemTemplate: null, items : [], menuAlign : 'tl-bl', menuAlignEl : null, offset : [0, 0], cyclic : true, search : false, scrollAlwaysVisible: true }, template: _.template([ '' ].join('')), initialize : function(options) { Common.UI.BaseView.prototype.initialize.call(this, options); var me = this; this.id = this.options.id || Common.UI.getId(); this.itemTemplate = this.options.itemTemplate || _.template([ ' style="<%= style %>" <% } %>', '<% if(typeof canFocused !== "undefined") { %> tabindex="-1" type="menuitem" <% } %>', '<% if(typeof stopPropagation !== "undefined") { %> data-stopPropagation="true" <% } %>', 'class="<% if (checked) { %> checked <% } %>" >', '<% if (typeof iconCls !== "undefined") { %>', '', '<% } %>', '<%= caption %>', '' ].join('')); this.rendered = false; this.items = this.options.items || []; this.offset = [0, 0]; this.menuAlign = this.options.menuAlign; this.menuAlignEl = this.options.menuAlignEl; this.scrollAlwaysVisible = this.options.scrollAlwaysVisible; this.search = this.options.search; if (this.options.restoreHeight) { this.options.restoreHeight = (typeof (this.options.restoreHeight) == "number") ? this.options.restoreHeight : (this.options.maxHeight ? this.options.maxHeight : 100000); !this.options.maxHeight && (this.options.maxHeight = this.options.restoreHeight); } if (!this.options.cyclic) this.options.cls += ' no-cyclic'; if (this.options.el) this.render(); Common.UI.Menu.Manager.register(this); }, remove: function() { Common.UI.Menu.Manager.unregister(this); Common.UI.BaseView.prototype.remove.call(this); }, render: function(parentEl) { var me = this; this.trigger('render:before', this); this.cmpEl = me.$el || $(this.el); parentEl && this.setElement(parentEl, false); if (!me.rendered) { this.cmpEl = $(this.template({ items: me.items, itemTemplate: me.itemTemplate, options : me.options })); parentEl ? parentEl.append(this.cmpEl) : this.$el.append(this.cmpEl); } var rootEl = this.cmpEl.parent(), menuRoot = (rootEl.attr('role') === 'menu') ? rootEl : rootEl.find('[role=menu]'); this.menuRoot = menuRoot; if (menuRoot) { if (!me.rendered) { menuRoot.on( "click", "li", _.bind(me.onItemClick, me)); menuRoot.on( "mousedown", "li", _.bind(me.onItemMouseDown, me)); } if (this.options.maxHeight) { menuRoot.css({'max-height': me.options.maxHeight}); this.scroller = new Common.UI.Scroller({ el: me.$el.find('.dropdown-menu '), minScrollbarLength: 30, suppressScrollX: true, alwaysVisibleY: this.scrollAlwaysVisible }); } menuRoot.css({ position : 'fixed', right : 'auto', left : -1000, top : -1000 }); this.parentEl = menuRoot.parent(); this.parentEl.on('show.bs.dropdown', _.bind(me.onBeforeShowMenu, me)); this.parentEl.on('shown.bs.dropdown', _.bind(me.onAfterShowMenu, me)); this.parentEl.on('hide.bs.dropdown', _.bind(me.onBeforeHideMenu, me)); this.parentEl.on('hidden.bs.dropdown', _.bind(me.onAfterHideMenu, me)); this.parentEl.on('keydown.after.bs.dropdown', _.bind(me.onAfterKeydownMenu, me)); menuRoot.hover( function(e) { me.isOver = true;}, function(e) { me.isOver = false; } ); } this.rendered = true; this.trigger('render:after', this); return this; }, resetItems: function(items) { this.items = items || []; this.$items = null; var template = _.template([ '<% _.each(items, function(item) { %>', '<% if (!item.id) item.id = Common.UI.getId(); %>', '<% item.checked = item.checked || false; %>', '
  • <%= itemTemplate(item) %>
  • ', '<% }) %>' ].join('')); this.cmpEl && this.cmpEl.html(template({ items: this.items, itemTemplate: this.itemTemplate, options : this.options })); }, isVisible: function() { return this.rendered && (this.cmpEl.is(':visible')); }, show: function() { if (this.rendered && this.parentEl && !this.parentEl.hasClass('open')) { this.cmpEl.dropdown('toggle'); } }, hide: function() { if (this.rendered && this.parentEl) { if ( this.parentEl.hasClass('open') ) this.cmpEl.dropdown('toggle'); else if (this.parentEl.hasClass('over')) this.parentEl.removeClass('over'); } }, onItemClick: function(e) { if (e.which != 1 && e.which !== undefined) return false; var index = $(e.currentTarget).closest('li').index(), item = (index>=0) ? this.items[index] : null; if (!item) return; if (item.disabled) return false; if (item.checkable && !item.checked) this.setChecked(index, !item.checked); this.isOver = false; if (item.stopPropagation) { e.stopPropagation(); var me = this; _.delay(function(){ me.$el.parent().parent().find('[data-toggle=dropdown]').focus(); }, 10); return; } this.trigger('item:click', this, item, e); }, onItemMouseDown: function(e) { if (e.which != 1) { e.preventDefault(); e.stopPropagation(); return false; } e.stopPropagation(); }, setChecked: function(index, check, suppressEvent) { this.toggle(index, check, suppressEvent); }, toggle: function(index, toggle, suppressEvent) { var state = !!toggle; var item = this.items[index]; this.clearAll(); if (item && item.checkable) { item.checked = state; if (this.rendered) { var itemEl = item.el || this.cmpEl.find('#'+item.id); if (itemEl) { itemEl.toggleClass('checked', item.checked); if (!_.isEmpty(item.iconCls)) { itemEl.css('background-image', 'none'); } } } if (!suppressEvent) this.trigger('item:toggle', this, item, state); } }, setDisabled: function(disabled) { this.disabled = !!disabled; if (this.rendered) this.cmpEl.toggleClass('disabled', this.disabled); }, isDisabled: function() { return this.disabled; }, onBeforeShowMenu: function(e) { Common.NotificationCenter.trigger('menu:show'); this.trigger('show:before', this, e); (e && e.target===e.currentTarget) && this.alignPosition(); }, onAfterShowMenu: function(e) { this.trigger('show:after', this, e); if (this.scroller && e && e.target===e.currentTarget) { this.scroller.update({alwaysVisibleY: this.scrollAlwaysVisible}); var menuRoot = this.menuRoot, $selected = menuRoot.find('> li .checked'); if ($selected.length) { var itemTop = $selected.position().top, itemHeight = $selected.outerHeight(), listHeight = menuRoot.outerHeight(); if (itemTop < 0 || itemTop + itemHeight > listHeight) { var height = menuRoot.scrollTop() + itemTop + (itemHeight - listHeight)/2; height = (Math.floor(height/itemHeight) * itemHeight); menuRoot.scrollTop(height); } setTimeout(function(){$selected.focus();}, 1); } } this._search = {}; if (this.search && !this.$items) { var me = this; this.$items = this.menuRoot.find('> li').find('> a'); _.each(this.$items, function(item, index) { me.items[index].el = $(item); }); } }, onBeforeHideMenu: function(e) { this.trigger('hide:before', this, e); if (Common.UI.Scroller.isMouseCapture()) e.preventDefault(); }, onAfterHideMenu: function(e, isFromInputControl) { this.trigger('hide:after', this, e, isFromInputControl); Common.NotificationCenter.trigger('menu:hide', this, isFromInputControl); }, onAfterKeydownMenu: function(e) { if (e.keyCode == Common.UI.Keys.RETURN) { var li = $(e.target).closest('li'); if (li.length<=0) li = $(e.target).parent().find('li .dataview'); if (li.length>0) li.click(); if (!li.hasClass('dropdown-submenu')) Common.UI.Menu.Manager.hideAll(); if ( $(e.currentTarget).closest('li').hasClass('dropdown-submenu')) { e.stopPropagation(); return false; } } else if (e.keyCode == Common.UI.Keys.UP || e.keyCode == Common.UI.Keys.DOWN) { this.fromKeyDown = true; } else if (e.keyCode == Common.UI.Keys.ESC) { // Common.NotificationCenter.trigger('menu:afterkeydown', e); // return false; } else if (this.search && e.keyCode > 64 && e.keyCode < 91 && e.key){ var me = this; clearTimeout(this._search.timer); this._search.timer = setTimeout(function () { me._search = {}; }, 1000); (!this._search.text) && (this._search.text = ''); (!this._search.char) && (this._search.char = e.key); (this._search.char !== e.key) && (this._search.full = true); this._search.text += e.key; if (this._search.index===undefined) { this._search.index = this.$items.index(this.$items.filter(':focus')); } this.selectCandidate(); } }, selectCandidate: function() { var index = this._search.index || 0, re = new RegExp('^' + ((this._search.full) ? this._search.text : this._search.char), 'i'), itemCandidate, idxCandidate; for (var i=0; iindex) { itemCandidate = item; idxCandidate = i; break; } } } if (itemCandidate) { this._search.index = idxCandidate; var item = itemCandidate.el; if (this.scroller) { this.scroller.update({alwaysVisibleY: this.scrollAlwaysVisible}); var itemTop = item.position().top, itemHeight = item.outerHeight(), listHeight = this.menuRoot.outerHeight(); if (itemTop < 0 || itemTop + itemHeight > listHeight) { var height = this.menuRoot.scrollTop() + itemTop; height = (Math.floor(height/itemHeight) * itemHeight); this.menuRoot.scrollTop(height); } } item.focus(); } }, setOffset: function(offsetX, offsetY) { this.offset[0] = _.isUndefined(offsetX) ? this.offset[0] : offsetX; this.offset[1] = _.isUndefined(offsetY) ? this.offset[1] : offsetY; this.alignPosition(); }, getOffset: function() { return this.offset; }, alignPosition: function(fixedAlign, fixedOffset) { var menuRoot = this.menuRoot, menuParent = this.menuAlignEl || menuRoot.parent(), m = this.menuAlign.match(/^([a-z]+)-([a-z]+)/), offset = menuParent.offset(), docW = Common.Utils.innerWidth(), docH = Common.Utils.innerHeight() - 10, // Yep, it's magic number menuW = menuRoot.outerWidth(), menuH = menuRoot.outerHeight(), parentW = menuParent.outerWidth(), parentH = menuParent.outerHeight(); var posMenu = { 'tl': [0, 0], 'bl': [0, menuH], 'tr': [menuW, 0], 'br': [menuW, menuH] }; var posParent = { 'tl': [0, 0], 'tr': [parentW, 0], 'bl': [0, parentH], 'br': [parentW, parentH] }; var left = offset.left - posMenu[m[1]][0] + posParent[m[2]][0] + this.offset[0]; var top = offset.top - posMenu[m[1]][1] + posParent[m[2]][1] + this.offset[1]; if (left + menuW > docW) if (menuParent.is('li.dropdown-submenu')) { left = offset.left - menuW + 2; } else { left = docW - menuW; } if (this.options.restoreHeight) { if (typeof (this.options.restoreHeight) == "number") { if (top + menuH > docH) { menuRoot.css('max-height', (docH - top) + 'px'); (!this.scroller) && (this.scroller = new Common.UI.Scroller({ el: this.$el.find('.dropdown-menu '), minScrollbarLength: 30, suppressScrollX: true, alwaysVisibleY: this.scrollAlwaysVisible })); } else if ( top + menuH < docH && menuRoot.height() < this.options.restoreHeight) { menuRoot.css('max-height', (Math.min(docH - top, this.options.restoreHeight)) + 'px'); } } } else { if (top + menuH > docH) { if (fixedAlign && typeof fixedAlign == 'string') { // how to align if menu height > window height m = fixedAlign.match(/^([a-z]+)-([a-z]+)/); top = offset.top - posMenu[m[1]][1] + posParent[m[2]][1] + this.offset[1] + (fixedOffset || 0); } else top = docH - menuH; } if (top < 0) top = 0; } if (this.options.additionalAlign) this.options.additionalAlign.call(this, menuRoot, left, top); else menuRoot.css({left: Math.ceil(left), top: Math.ceil(top)}); }, clearAll: function() { this.cmpEl && this.cmpEl.find('li > a.checked').removeClass('checked'); _.each(this.items, function(item){ item.checked = false; }); } }); });