3 * @class Roo.bootstrap.menu.Menu
4 * @extends Roo.bootstrap.Component
6 * @children Roo.bootstrap.menu.Item
8 * Bootstrap Menu class - container for MenuItems - normally has to be added to a object that supports the menu property
10 * @cfg {String} type (dropdown|treeview|submenu) type of menu
11 * @cfg {bool} hidden if the menu should be hidden when rendered.
12 * @cfg {bool} stopEvent (true|false) Stop event after trigger press (default true)
13 * @cfg {bool} isLink (true|false) the menu has link disable auto expand and collaspe (default false)
14 * @cfg {bool} hideTrigger (true|false) default false - hide the carret for trigger.
15 * @cfg {String} align default tl-bl? == below - how the menu should be aligned.
19 * @param {Object} config The config objectQ
23 Roo.bootstrap.menu.Menu = function(config){
25 if (config.type == 'treeview') {
26 // normally menu's are drawn attached to the document to handle layering etc..
27 // however treeview (used by the docs menu is drawn into the parent element)
28 this.container_method = 'getChildContainer';
31 Roo.bootstrap.menu.Menu.superclass.constructor.call(this, config);
32 if (this.registerMenu && this.type != 'treeview') {
33 Roo.bootstrap.menu.Manager.register(this);
40 * Fires before this menu is displayed (return false to block)
41 * @param {Roo.menu.Menu} this
46 * Fires before this menu is hidden (return false to block)
47 * @param {Roo.menu.Menu} this
52 * Fires after this menu is displayed
53 * @param {Roo.menu.Menu} this
58 * Fires after this menu is hidden
59 * @param {Roo.menu.Menu} this
64 * Fires when this menu is clicked (or when the enter key is pressed while it is active)
65 * @param {Roo.menu.Menu} this
66 * @param {Roo.menu.Item} menuItem The menu item that was clicked
67 * @param {Roo.EventObject} e
72 * Fires when the mouse is hovering over this menu
73 * @param {Roo.menu.Menu} this
74 * @param {Roo.EventObject} e
75 * @param {Roo.menu.Item} menuItem The menu item that was clicked
80 * Fires when the mouse exits this menu
81 * @param {Roo.menu.Menu} this
82 * @param {Roo.EventObject} e
83 * @param {Roo.menu.Item} menuItem The menu item that was clicked
88 * Fires when a menu item contained in this menu is clicked
89 * @param {Roo.menu.BaseItem} baseItem The BaseItem that was clicked
90 * @param {Roo.EventObject} e
94 this.menuitems = new Roo.util.MixedCollection(false, function(o) { return o.el.id; });
97 Roo.extend(Roo.bootstrap.menu.Menu, Roo.bootstrap.Component, {
101 triggerEl : false, // is this set by component builder? -- it should really be fetched from parent()???
104 * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it.
108 menuItems :false, // stores the menu items..
118 container_method : 'getDocumentBody', // so the menu is rendered on the body and zIndex works.
125 getChildContainer : function() {
129 getAutoCreate : function(){
131 //if (['right'].indexOf(this.align)!==-1) {
132 // cfg.cn[1].cls += ' pull-right'
137 cls : 'dropdown-menu shadow' ,
138 style : 'z-index:1000'
142 if (this.type === 'submenu') {
143 cfg.cls = 'submenu active';
145 if (this.type === 'treeview') {
146 cfg.cls = 'treeview-menu';
151 initEvents : function() {
153 // Roo.log("ADD event");
154 // Roo.log(this.triggerEl.dom);
155 if (this.triggerEl) {
157 this.triggerEl.on('click', this.onTriggerClick, this);
159 this.triggerEl.on(Roo.isTouch ? 'touchstart' : 'mouseup', this.onTriggerPress, this);
161 if (!this.hideTrigger) {
162 if (this.triggerEl.hasClass('nav-item') && this.triggerEl.select('.nav-link',true).length) {
163 // dropdown toggle on the 'a' in BS4?
164 this.triggerEl.select('.nav-link',true).first().addClass('dropdown-toggle');
166 this.triggerEl.addClass('dropdown-toggle');
172 this.el.on('touchstart' , this.onTouch, this);
174 this.el.on('click' , this.onClick, this);
176 this.el.on("mouseover", this.onMouseOver, this);
177 this.el.on("mouseout", this.onMouseOut, this);
181 findTargetItem : function(e)
183 var t = e.getTarget(".dropdown-menu-item", this.el, true);
187 //Roo.log(t); Roo.log(t.id);
189 //Roo.log(this.menuitems);
190 return this.menuitems.get(t.id);
192 //return this.items.get(t.menuItemId);
198 onTouch : function(e)
200 Roo.log("menu.onTouch");
201 //e.stopEvent(); this make the user popdown broken
205 onClick : function(e)
207 Roo.log("menu.onClick");
209 var t = this.findTargetItem(e);
210 if(!t || t.isContainer){
215 if (Roo.isTouch && e.type == 'touchstart' && t.menu && !t.disabled) {
216 if(t == this.activeItem && t.shouldDeactivate(e)){
217 this.activeItem.deactivate();
218 delete this.activeItem;
222 this.setActiveItem(t, true);
230 Roo.log('pass click event');
234 this.fireEvent("click", this, t, e);
238 if(!t.href.length || t.href == '#'){
239 (function() { _this.hide(); }).defer(100);
244 onMouseOver : function(e){
245 var t = this.findTargetItem(e);
248 // if(t.canActivate && !t.disabled){
249 // this.setActiveItem(t, true);
253 this.fireEvent("mouseover", this, e, t);
255 isVisible : function(){
258 onMouseOut : function(e){
259 var t = this.findTargetItem(e);
262 // if(t == this.activeItem && t.shouldDeactivate(e)){
263 // this.activeItem.deactivate();
264 // delete this.activeItem;
267 this.fireEvent("mouseout", this, e, t);
272 * Displays this menu relative to another element
273 * @param {String/HTMLElement/Roo.Element} element The element to align to
274 * @param {String} position (optional) The {@link Roo.Element#alignTo} anchor position to use in aligning to
275 * the element (defaults to this.defaultAlign)
276 * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
278 show : function(el, pos, parentMenu)
280 if (false === this.fireEvent("beforeshow", this)) {
281 Roo.log("show canceled");
284 this.parentMenu = parentMenu;
288 this.el.addClass('show'); // show otherwise we do not know how big we are..
290 var xy = this.el.getAlignToXY(el, pos);
292 // bl-tl << left align below
293 // tl-bl << left align
295 if(this.el.getWidth() + xy[0] >= Roo.lib.Dom.getViewWidth()){
296 // if it goes to far to the right.. -> align left.
297 xy = this.el.getAlignToXY(el, this.align.replace('/l/g', 'r'))
300 // was left align - go right?
301 xy = this.el.getAlignToXY(el, this.align.replace('/r/g', 'l'))
304 // goes down the bottom
305 if(this.el.getHeight() + xy[1] >= Roo.lib.Dom.getViewHeight() ||
307 var a = this.align.replace('?', '').split('-');
308 xy = this.el.getAlignToXY(el, a[1] + '-' + a[0] + '?')
312 this.showAt( xy , parentMenu, false);
315 * Displays this menu at a specific xy position
316 * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
317 * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
319 showAt : function(xy, parentMenu, /* private: */_e){
320 this.parentMenu = parentMenu;
325 this.fireEvent("beforeshow", this);
326 //xy = this.el.adjustForConstraints(xy);
330 this.hideMenuItems();
332 if (this.triggerEl) {
333 this.triggerEl.addClass('open');
336 this.el.addClass('show');
340 // reassign x when hitting right
342 // reassign y when hitting bottom
344 // but the list may align on trigger left or trigger top... should it be a properity?
346 if(this.el.getStyle('top') != 'auto' && this.el.getStyle('top').slice(-1) != "%"){
351 this.fireEvent("show", this);
357 this.doFocus.defer(50, this);
361 doFocus : function(){
363 this.focusEl.focus();
368 * Hides this menu and optionally all parent menus
369 * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
371 hide : function(deep)
373 if (false === this.fireEvent("beforehide", this)) {
374 Roo.log("hide canceled");
377 this.hideMenuItems();
378 if(this.el && this.isVisible()){
381 this.activeItem.deactivate();
382 this.activeItem = null;
384 if (this.triggerEl) {
385 this.triggerEl.removeClass('open');
388 this.el.removeClass('show');
390 this.fireEvent("hide", this);
392 if(deep === true && this.parentMenu){
393 this.parentMenu.hide(true);
397 onTriggerClick : function(e)
399 Roo.log('trigger click');
401 var target = e.getTarget();
403 Roo.log(target.nodeName.toLowerCase());
405 if(target.nodeName.toLowerCase() === 'i'){
411 onTriggerPress : function(e)
413 Roo.log('trigger press');
414 //Roo.log(e.getTarget());
415 // Roo.log(this.triggerEl.dom);
417 // trigger only occurs on normal menu's -- if it's a treeview or dropdown... do not hide/show..
418 var pel = Roo.get(e.getTarget());
419 if (pel.findParent('.dropdown-menu') || pel.findParent('.treeview-menu') ) {
420 Roo.log('is treeview or dropdown?');
424 if(e.getTarget().nodeName.toLowerCase() !== 'i' && this.isLink){
428 if (this.isVisible()) {
434 this.show(this.triggerEl, this.align, false);
437 if(this.stopEvent || e.getTarget().nodeName.toLowerCase() === 'i'){
444 hideMenuItems : function()
446 Roo.log("hide Menu Items");
451 this.el.select('.open',true).each(function(aa) {
453 aa.removeClass('open');
457 addxtypeChild : function (tree, cntr) {
458 var comp= Roo.bootstrap.menu.Menu.superclass.addxtypeChild.call(this, tree, cntr);
460 this.menuitems.add(comp);
472 this.getEl().dom.innerHTML = '';
473 this.menuitems.clear();