X-Git-Url: http://git.roojs.org/?a=blobdiff_plain;f=Roo%2Fbootstrap%2Fmenu%2FMenu.js;h=07bd3d2177ad108d7971a05bd0a5d698ef7e0b4a;hb=f94d8932982ce42a87b91d281ce0cdd4fcabd1e5;hp=80b84470791fdbc3e36fd3104244391b3e538e65;hpb=dcd1156a8a9822590f7d1356b2e188beb9bf9ee8;p=roojs1 diff --git a/Roo/bootstrap/menu/Menu.js b/Roo/bootstrap/menu/Menu.js index 80b8447079..07bd3d2177 100644 --- a/Roo/bootstrap/menu/Menu.js +++ b/Roo/bootstrap/menu/Menu.js @@ -1,228 +1,477 @@ -/* - * - LGPL - * - * menu - * - */ -Roo.bootstrap.menu = Roo.bootstrap.menu || {}; - + /** * @class Roo.bootstrap.menu.Menu * @extends Roo.bootstrap.Component - * Bootstrap Menu class - container for Menu - * @cfg {String} html Text of the menu - * @cfg {String} weight (default | primary | success | info | warning | danger | inverse) - * @cfg {String} icon Font awesome icon - * @cfg {String} pos Menu align to (top | bottom) default bottom - * + * @licence LGPL + * @children Roo.bootstrap.menu.Item Roo.bootstrap.menu.Separator + * @parent none + * Bootstrap Menu class - container for MenuItems - normally has to be added to a object that supports the menu property * + * @cfg {String} type (dropdown|treeview|submenu) type of menu + * @cfg {bool} hidden if the menu should be hidden when rendered. + * @cfg {bool} stopEvent (true|false) Stop event after trigger press (default true) + * @cfg {bool} isLink (true|false) the menu has link disable auto expand and collaspe (default false) +* @cfg {bool} hideTrigger (true|false) default false - hide the carret for trigger. +* @cfg {String} align default tl-bl? == below - how the menu should be aligned. + * @constructor * Create a new Menu - * @param {Object} config The config object + * @param {Object} config The config objectQ */ Roo.bootstrap.menu.Menu = function(config){ + + if (config.type == 'treeview') { + // normally menu's are drawn attached to the document to handle layering etc.. + // however treeview (used by the docs menu is drawn into the parent element) + this.container_method = 'getChildContainer'; + } + Roo.bootstrap.menu.Menu.superclass.constructor.call(this, config); + if (this.registerMenu && this.type != 'treeview') { + Roo.bootstrap.menu.Manager.register(this); + } + this.addEvents({ /** * @event beforeshow - * Fires before this menu is displayed - * @param {Roo.bootstrap.menu.Menu} this + * Fires before this menu is displayed (return false to block) + * @param {Roo.menu.Menu} this */ beforeshow : true, /** * @event beforehide - * Fires before this menu is hidden - * @param {Roo.bootstrap.menu.Menu} this + * Fires before this menu is hidden (return false to block) + * @param {Roo.menu.Menu} this */ beforehide : true, /** * @event show * Fires after this menu is displayed - * @param {Roo.bootstrap.menu.Menu} this + * @param {Roo.menu.Menu} this */ show : true, /** * @event hide * Fires after this menu is hidden - * @param {Roo.bootstrap.menu.Menu} this + * @param {Roo.menu.Menu} this */ hide : true, /** * @event click * Fires when this menu is clicked (or when the enter key is pressed while it is active) - * @param {Roo.bootstrap.menu.Menu} this + * @param {Roo.menu.Menu} this + * @param {Roo.menu.Item} menuItem The menu item that was clicked * @param {Roo.EventObject} e */ - click : true + click : true, + /** + * @event mouseover + * Fires when the mouse is hovering over this menu + * @param {Roo.menu.Menu} this + * @param {Roo.EventObject} e + * @param {Roo.menu.Item} menuItem The menu item that was clicked + */ + mouseover : true, + /** + * @event mouseout + * Fires when the mouse exits this menu + * @param {Roo.menu.Menu} this + * @param {Roo.EventObject} e + * @param {Roo.menu.Item} menuItem The menu item that was clicked + */ + mouseout : true, + /** + * @event itemclick + * Fires when a menu item contained in this menu is clicked + * @param {Roo.menu.BaseItem} baseItem The BaseItem that was clicked + * @param {Roo.EventObject} e + */ + itemclick: true }); - + this.menuitems = new Roo.util.MixedCollection(false, function(o) { return o.el.id; }); }; Roo.extend(Roo.bootstrap.menu.Menu, Roo.bootstrap.Component, { - submenu : false, - html : '', - weight : 'default', - icon : false, - pos : 'bottom', + /// html : false, + + triggerEl : false, // is this set by component builder? -- it should really be fetched from parent()??? + type: false, + /** + * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it. + */ + registerMenu : true, + menuItems :false, // stores the menu items.. - getChildContainer : function() { - if(this.isSubMenu){ - return this.el; - } + hidden:true, - return this.el.select('ul.dropdown-menu', true).first(); + parentMenu : false, + + stopEvent : true, + + isLink : false, + + container_method : 'getDocumentBody', // so the menu is rendered on the body and zIndex works. + + hideTrigger : false, + + align : 'tl-bl?', + + + getChildContainer : function() { + return this.el; }, - getAutoCreate : function() - { - var text = [ - { - tag : 'span', - cls : 'roo-menu-text', - html : this.html - } - ]; - - if(this.icon){ - text.unshift({ - tag : 'i', - cls : 'fa ' + this.icon - }) - } - - + getAutoCreate : function(){ + + //if (['right'].indexOf(this.align)!==-1) { + // cfg.cn[1].cls += ' pull-right' + //} + var cfg = { - tag : 'div', - cls : 'btn-group', - cn : [ - { - tag : 'button', - cls : 'roo-dropdown-button btn btn-' + this.weight, - cn : text - }, - { - tag : 'button', - cls : 'roo-dropdown-toggle btn btn-' + this.weight, - cn : [ - { - tag : 'span', - cls : 'caret' - } - ] - }, - { - tag : 'ul', - cls : 'roo-dropdown-menu' - } - ] + tag : 'ul', + cls : 'dropdown-menu shadow' , + style : 'z-index:1000' }; - - if(this.pos == 'top'){ - cfg.cls += ' dropup'; + + if (this.type === 'submenu') { + cfg.cls = 'submenu active'; } + if (this.type === 'treeview') { + cfg.cls = 'treeview-menu'; + } + + return cfg; + }, + initEvents : function() { - if(this.isSubMenu){ - cfg = { - tag : 'ul', - cls : 'roo-dropdown-menu' + // Roo.log("ADD event"); + // Roo.log(this.triggerEl.dom); + if (this.triggerEl) { + + this.triggerEl.on('click', this.onTriggerClick, this); + + this.triggerEl.on(Roo.isTouch ? 'touchstart' : 'mouseup', this.onTriggerPress, this); + + if (!this.hideTrigger) { + if (this.triggerEl.hasClass('nav-item') && this.triggerEl.select('.nav-link',true).length) { + // dropdown toggle on the 'a' in BS4? + this.triggerEl.select('.nav-link',true).first().addClass('dropdown-toggle'); + } else { + this.triggerEl.addClass('dropdown-toggle'); + } } } - - return cfg; + + if (Roo.isTouch) { + this.el.on('touchstart' , this.onTouch, this); + } + this.el.on('click' , this.onClick, this); + + this.el.on("mouseover", this.onMouseOver, this); + this.el.on("mouseout", this.onMouseOut, this); + }, - onRender : function(ct, position) + findTargetItem : function(e) { - this.isSubMenu = ct.hasClass('roo-dropdown-submenu'); + var t = e.getTarget(".dropdown-menu-item", this.el, true); + if(!t){ + return false; + } + //Roo.log(t); Roo.log(t.id); + if(t && t.id){ + //Roo.log(this.menuitems); + return this.menuitems.get(t.id); + + //return this.items.get(t.menuItemId); + } - Roo.bootstrap.menu.Menu.superclass.onRender.call(this, ct, position); + return false; }, - initEvents : function() + onTouch : function(e) { + Roo.log("menu.onTouch"); + //e.stopEvent(); this make the user popdown broken + this.onClick(e); + }, + + onClick : function(e) + { + Roo.log("menu.onClick"); - this.list().setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'none'; - - if(this.isSubMenu){ + var t = this.findTargetItem(e); + if(!t || t.isContainer){ + return; + } + Roo.log(e); + /* + if (Roo.isTouch && e.type == 'touchstart' && t.menu && !t.disabled) { + if(t == this.activeItem && t.shouldDeactivate(e)){ + this.activeItem.deactivate(); + delete this.activeItem; + return; + } + if(t.canActivate){ + this.setActiveItem(t, true); + } return; + + } + */ + + Roo.log('pass click event'); - this.hidden = true; + t.onClick(e); - this.triggerEl = this.el.select('button.roo-dropdown-toggle', true).first(); - this.triggerEl.on('click', this.onTriggerPress, this); + this.fireEvent("click", this, t, e); - this.buttonEl = this.el.select('button.roo-dropdown-button', true).first(); - this.buttonEl.on('click', this.onClick, this); + var _this = this; + + if(!t.href.length || t.href == '#'){ + (function() { _this.hide(); }).defer(100); + } }, - list : function() - { - if(this.isSubMenu){ - return this.el; - } + onMouseOver : function(e){ + var t = this.findTargetItem(e); + //Roo.log(t); + //if(t){ + // if(t.canActivate && !t.disabled){ + // this.setActiveItem(t, true); + // } + //} + + this.fireEvent("mouseover", this, e, t); + }, + isVisible : function(){ + return !this.hidden; + }, + onMouseOut : function(e){ + var t = this.findTargetItem(e); - return this.el.select('ul.roo-dropdown-menu', true).first(); + //if(t ){ + // if(t == this.activeItem && t.shouldDeactivate(e)){ + // this.activeItem.deactivate(); + // delete this.activeItem; + // } + //} + this.fireEvent("mouseout", this, e, t); }, - onClick : function(e) + + /** + * Displays this menu relative to another element + * @param {String/HTMLElement/Roo.Element} element The element to align to + * @param {String} position (optional) The {@link Roo.Element#alignTo} anchor position to use in aligning to + * the element (defaults to this.defaultAlign) + * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined) + */ + show : function(el, pos, parentMenu) { - this.fireEvent("click", this, e); + if (false === this.fireEvent("beforeshow", this)) { + Roo.log("show canceled"); + return; + } + this.parentMenu = parentMenu; + if(!this.el){ + this.render(); + } + this.el.addClass('show'); // show otherwise we do not know how big we are.. + + var xy = this.el.getAlignToXY(el, pos); + + // bl-tl << left align below + // tl-bl << left align + + if(this.el.getWidth() + xy[0] >= Roo.lib.Dom.getViewWidth()){ + // if it goes to far to the right.. -> align left. + xy = this.el.getAlignToXY(el, this.align.replace('/l/g', 'r')) + } + if(xy[0] < 0){ + // was left align - go right? + xy = this.el.getAlignToXY(el, this.align.replace('/r/g', 'l')) + } + + // goes down the bottom + if(this.el.getHeight() + xy[1] >= Roo.lib.Dom.getViewHeight() || + xy[1] < 0 ){ + var a = this.align.replace('?', '').split('-'); + xy = this.el.getAlignToXY(el, a[1] + '-' + a[0] + '?') + + } + + this.showAt( xy , parentMenu, false); }, - - onTriggerPress : function(e) - { - if (this.isVisible()) { - this.hide(); - } else { - this.show(); + /** + * Displays this menu at a specific xy position + * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based) + * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined) + */ + showAt : function(xy, parentMenu, /* private: */_e){ + this.parentMenu = parentMenu; + if(!this.el){ + this.render(); + } + if(_e !== false){ + this.fireEvent("beforeshow", this); + //xy = this.el.adjustForConstraints(xy); } + + //this.el.show(); + this.hideMenuItems(); + this.hidden = false; + if (this.triggerEl) { + this.triggerEl.addClass('open'); + } + + this.el.addClass('show'); + + + + // reassign x when hitting right + + // reassign y when hitting bottom + + // but the list may align on trigger left or trigger top... should it be a properity? + + if(this.el.getStyle('top') != 'auto' && this.el.getStyle('top').slice(-1) != "%"){ + this.el.setXY(xy); + } + + this.focus(); + this.fireEvent("show", this); }, - isVisible : function(){ - return !this.hidden; + focus : function(){ + return; + if(!this.hidden){ + this.doFocus.defer(50, this); + } + }, + + doFocus : function(){ + if(!this.hidden){ + this.focusEl.focus(); + } + }, + + /** + * Hides this menu and optionally all parent menus + * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false) + */ + hide : function(deep) + { + if (false === this.fireEvent("beforehide", this)) { + Roo.log("hide canceled"); + return; + } + this.hideMenuItems(); + if(this.el && this.isVisible()){ + + if(this.activeItem){ + this.activeItem.deactivate(); + this.activeItem = null; + } + if (this.triggerEl) { + this.triggerEl.removeClass('open'); + } + + this.el.removeClass('show'); + this.hidden = true; + this.fireEvent("hide", this); + } + if(deep === true && this.parentMenu){ + this.parentMenu.hide(true); + } }, - show : function() + onTriggerClick : function(e) { - Roo.log('show??'); - this.fireEvent("beforeshow", this); + Roo.log('trigger click'); - this.hidden = false; -// this.el.addClass('open'); - Roo.log(this.list()); - this.list().show(); + var target = e.getTarget(); - Roo.get(document).on("mouseup", this.onMouseUp, this); + Roo.log(target.nodeName.toLowerCase()); - this.fireEvent("show", this); + if(target.nodeName.toLowerCase() === 'i'){ + e.preventDefault(); + } }, - hide : function() + onTriggerPress : function(e) { - this.fireEvent("beforehide", this); + Roo.log('trigger press'); + //Roo.log(e.getTarget()); + // Roo.log(this.triggerEl.dom); + + // trigger only occurs on normal menu's -- if it's a treeview or dropdown... do not hide/show.. + var pel = Roo.get(e.getTarget()); + if (pel.findParent('.dropdown-menu') || pel.findParent('.treeview-menu') ) { + Roo.log('is treeview or dropdown?'); + return; + } - this.hidden = true; - this.el.removeClass('open'); + if(e.getTarget().nodeName.toLowerCase() !== 'i' && this.isLink){ + return; + } - Roo.get(document).un("mouseup", this.onMouseUp); + if (this.isVisible()) { + Roo.log('hide'); + this.hide(); + } else { + Roo.log('show'); + + this.show(this.triggerEl, this.align, false); + } + + if(this.stopEvent || e.getTarget().nodeName.toLowerCase() === 'i'){ + e.stopEvent(); + } - this.fireEvent("hide", this); }, + - onMouseUp : function() + hideMenuItems : function() { - this.hide(); - } + Roo.log("hide Menu Items"); + if (!this.el) { + return; + } + + this.el.select('.open',true).each(function(aa) { + + aa.removeClass('open'); + + }); + }, + addxtypeChild : function (tree, cntr) { + var comp= Roo.bootstrap.menu.Menu.superclass.addxtypeChild.call(this, tree, cntr); + + this.menuitems.add(comp); + return comp; + + }, + getEl : function() + { + Roo.log(this.el); + return this.el; + }, + clear : function() + { + this.getEl().dom.innerHTML = ''; + this.menuitems.clear(); + } });