a1e7f54446261f1e751c7c527a2a44d13fc5996a
[roojs1] / Roo / bootstrap / Menu.js
1 /*
2  * - LGPL
3  *
4  * menu
5  * 
6  */
7
8 /**
9  * @class Roo.bootstrap.Menu
10  * @extends Roo.bootstrap.Component
11  * Bootstrap Menu class - container for MenuItems
12  * @cfg {String} type (dropdown|treeview|submenu) type of menu
13  * @cfg {bool} hidden  if the menu should be hidden when rendered.
14  * @cfg {bool} stopEvent (true|false)  Stop event after trigger press (default true)
15  * @cfg {bool} isLink (true|false)  the menu has link disable auto expand and collaspe (default false)
16  * @cfg {bool} hideTrigger (true|false)  default false - hide the carret for trigger.
17  * 
18  * @constructor
19  * Create a new Menu
20  * @param {Object} config The config object
21  */
22
23
24 Roo.bootstrap.Menu = function(config){
25     Roo.bootstrap.Menu.superclass.constructor.call(this, config);
26     if (this.registerMenu && this.type != 'treeview')  {
27         Roo.bootstrap.MenuMgr.register(this);
28     }
29     
30     
31     this.addEvents({
32         /**
33          * @event beforeshow
34          * Fires before this menu is displayed (return false to block)
35          * @param {Roo.menu.Menu} this
36          */
37         beforeshow : true,
38         /**
39          * @event beforehide
40          * Fires before this menu is hidden (return false to block)
41          * @param {Roo.menu.Menu} this
42          */
43         beforehide : true,
44         /**
45          * @event show
46          * Fires after this menu is displayed
47          * @param {Roo.menu.Menu} this
48          */
49         show : true,
50         /**
51          * @event hide
52          * Fires after this menu is hidden
53          * @param {Roo.menu.Menu} this
54          */
55         hide : true,
56         /**
57          * @event click
58          * Fires when this menu is clicked (or when the enter key is pressed while it is active)
59          * @param {Roo.menu.Menu} this
60          * @param {Roo.menu.Item} menuItem The menu item that was clicked
61          * @param {Roo.EventObject} e
62          */
63         click : true,
64         /**
65          * @event mouseover
66          * Fires when the mouse is hovering over this menu
67          * @param {Roo.menu.Menu} this
68          * @param {Roo.EventObject} e
69          * @param {Roo.menu.Item} menuItem The menu item that was clicked
70          */
71         mouseover : true,
72         /**
73          * @event mouseout
74          * Fires when the mouse exits this menu
75          * @param {Roo.menu.Menu} this
76          * @param {Roo.EventObject} e
77          * @param {Roo.menu.Item} menuItem The menu item that was clicked
78          */
79         mouseout : true,
80         /**
81          * @event itemclick
82          * Fires when a menu item contained in this menu is clicked
83          * @param {Roo.menu.BaseItem} baseItem The BaseItem that was clicked
84          * @param {Roo.EventObject} e
85          */
86         itemclick: true
87     });
88     this.menuitems = new Roo.util.MixedCollection(false, function(o) { return o.el.id; });
89 };
90
91 Roo.extend(Roo.bootstrap.Menu, Roo.bootstrap.Component,  {
92     
93    /// html : false,
94     //align : '',
95     triggerEl : false,  // is this set by component builder? -- it should really be fetched from parent()???
96     type: false,
97     /**
98      * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it.
99      */
100     registerMenu : true,
101     
102     menuItems :false, // stores the menu items..
103     
104     hidden:true,
105         
106     parentMenu : false,
107     
108     stopEvent : true,
109     
110     isLink : false,
111     
112     container_method : 'getDocumentBody', // so the menu is rendered on the body and zIndex works.
113     
114     hideTrigger : false,
115     
116     
117     getChildContainer : function() {
118         return this.el;  
119     },
120     
121     getAutoCreate : function(){
122          
123         //if (['right'].indexOf(this.align)!==-1) {
124         //    cfg.cn[1].cls += ' pull-right'
125         //}
126         
127         
128         var cfg = {
129             tag : 'ul',
130             cls : 'dropdown-menu shadow' ,
131             style : 'z-index:1000'
132             
133         };
134         
135         if (this.type === 'submenu') {
136             cfg.cls = 'submenu active';
137         }
138         if (this.type === 'treeview') {
139             cfg.cls = 'treeview-menu';
140         }
141         
142         return cfg;
143     },
144     initEvents : function() {
145         
146        // Roo.log("ADD event");
147        // Roo.log(this.triggerEl.dom);
148         
149         this.triggerEl.on('click', this.onTriggerClick, this);
150         
151         this.triggerEl.on(Roo.isTouch ? 'touchstart' : 'mouseup', this.onTriggerPress, this);
152         
153         if (!this.hideTrigger) {
154             if (this.triggerEl.hasClass('nav-item') && this.triggerEl.select('.nav-link',true).length) {
155                 // dropdown toggle on the 'a' in BS4?
156                 this.triggerEl.select('.nav-link',true).first().addClass('dropdown-toggle');
157             } else {
158                 this.triggerEl.addClass('dropdown-toggle');
159             }
160         }
161         if (Roo.isTouch) {
162             this.el.on('touchstart'  , this.onTouch, this);
163         }
164         this.el.on('click' , this.onClick, this);
165
166         this.el.on("mouseover", this.onMouseOver, this);
167         this.el.on("mouseout", this.onMouseOut, this);
168         
169     },
170     
171     findTargetItem : function(e)
172     {
173         var t = e.getTarget(".dropdown-menu-item", this.el,  true);
174         if(!t){
175             return false;
176         }
177         //Roo.log(t);         Roo.log(t.id);
178         if(t && t.id){
179             //Roo.log(this.menuitems);
180             return this.menuitems.get(t.id);
181             
182             //return this.items.get(t.menuItemId);
183         }
184         
185         return false;
186     },
187     
188     onTouch : function(e) 
189     {
190         Roo.log("menu.onTouch");
191         //e.stopEvent(); this make the user popdown broken
192         this.onClick(e);
193     },
194     
195     onClick : function(e)
196     {
197         Roo.log("menu.onClick");
198         
199         var t = this.findTargetItem(e);
200         if(!t || t.isContainer){
201             return;
202         }
203         Roo.log(e);
204         /*
205         if (Roo.isTouch && e.type == 'touchstart' && t.menu  && !t.disabled) {
206             if(t == this.activeItem && t.shouldDeactivate(e)){
207                 this.activeItem.deactivate();
208                 delete this.activeItem;
209                 return;
210             }
211             if(t.canActivate){
212                 this.setActiveItem(t, true);
213             }
214             return;
215             
216             
217         }
218         */
219        
220         Roo.log('pass click event');
221         
222         t.onClick(e);
223         
224         this.fireEvent("click", this, t, e);
225         
226         var _this = this;
227         
228         if(!t.href.length || t.href == '#'){
229             (function() { _this.hide(); }).defer(100);
230         }
231         
232     },
233     
234     onMouseOver : function(e){
235         var t  = this.findTargetItem(e);
236         //Roo.log(t);
237         //if(t){
238         //    if(t.canActivate && !t.disabled){
239         //        this.setActiveItem(t, true);
240         //    }
241         //}
242         
243         this.fireEvent("mouseover", this, e, t);
244     },
245     isVisible : function(){
246         return !this.hidden;
247     },
248     onMouseOut : function(e){
249         var t  = this.findTargetItem(e);
250         
251         //if(t ){
252         //    if(t == this.activeItem && t.shouldDeactivate(e)){
253         //        this.activeItem.deactivate();
254         //        delete this.activeItem;
255         //    }
256         //}
257         this.fireEvent("mouseout", this, e, t);
258     },
259     
260     
261     /**
262      * Displays this menu relative to another element
263      * @param {String/HTMLElement/Roo.Element} element The element to align to
264      * @param {String} position (optional) The {@link Roo.Element#alignTo} anchor position to use in aligning to
265      * the element (defaults to this.defaultAlign)
266      * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
267      */
268     show : function(el, pos, parentMenu)
269     {
270         if (false === this.fireEvent("beforeshow", this)) {
271             Roo.log("show canceled");
272             return;
273         }
274         this.parentMenu = parentMenu;
275         if(!this.el){
276             this.render();
277         }
278         
279         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign), parentMenu, false);
280     },
281      /**
282      * Displays this menu at a specific xy position
283      * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
284      * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
285      */
286     showAt : function(xy, parentMenu, /* private: */_e){
287         this.parentMenu = parentMenu;
288         if(!this.el){
289             this.render();
290         }
291         if(_e !== false){
292             this.fireEvent("beforeshow", this);
293             //xy = this.el.adjustForConstraints(xy);
294         }
295         
296         //this.el.show();
297         this.hideMenuItems();
298         this.hidden = false;
299         this.triggerEl.addClass('open');
300         this.el.addClass('show');
301         
302         // reassign x when hitting right
303         if(this.el.getWidth() + xy[0] >= Roo.lib.Dom.getViewWidth()){
304             xy[0] = xy[0] - this.el.getWidth() + this.triggerEl.getWidth();
305         }
306         
307         // reassign y when hitting bottom
308         if(this.el.getHeight() + xy[1] >= Roo.lib.Dom.getViewHeight()){
309             xy[1] = xy[1] - this.el.getHeight() - this.triggerEl.getHeight();
310         }
311         
312         // but the list may align on trigger left or trigger top... should it be a properity?
313         
314         if(this.el.getStyle('top') != 'auto' && this.el.getStyle('top').slice(-1) != "%"){
315             this.el.setXY(xy);
316         }
317         
318         this.focus();
319         this.fireEvent("show", this);
320     },
321     
322     focus : function(){
323         return;
324         if(!this.hidden){
325             this.doFocus.defer(50, this);
326         }
327     },
328
329     doFocus : function(){
330         if(!this.hidden){
331             this.focusEl.focus();
332         }
333     },
334
335     /**
336      * Hides this menu and optionally all parent menus
337      * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
338      */
339     hide : function(deep)
340     {
341         if (false === this.fireEvent("beforehide", this)) {
342             Roo.log("hide canceled");
343             return;
344         }
345         this.hideMenuItems();
346         if(this.el && this.isVisible()){
347            
348             if(this.activeItem){
349                 this.activeItem.deactivate();
350                 this.activeItem = null;
351             }
352             this.triggerEl.removeClass('open');;
353             this.el.removeClass('show');
354             this.hidden = true;
355             this.fireEvent("hide", this);
356         }
357         if(deep === true && this.parentMenu){
358             this.parentMenu.hide(true);
359         }
360     },
361     
362     onTriggerClick : function(e)
363     {
364         Roo.log('trigger click');
365         
366         var target = e.getTarget();
367         
368         Roo.log(target.nodeName.toLowerCase());
369         
370         if(target.nodeName.toLowerCase() === 'i'){
371             e.preventDefault();
372         }
373         
374     },
375     
376     onTriggerPress  : function(e)
377     {
378         Roo.log('trigger press');
379         //Roo.log(e.getTarget());
380        // Roo.log(this.triggerEl.dom);
381        
382         // trigger only occurs on normal menu's -- if it's a treeview or dropdown... do not hide/show..
383         var pel = Roo.get(e.getTarget());
384         if (pel.findParent('.dropdown-menu') || pel.findParent('.treeview-menu') ) {
385             Roo.log('is treeview or dropdown?');
386             return;
387         }
388         
389         if(e.getTarget().nodeName.toLowerCase() !== 'i' && this.isLink){
390             return;
391         }
392         
393         if (this.isVisible()) {
394             Roo.log('hide');
395             this.hide();
396         } else {
397             Roo.log('show');
398             this.show(this.triggerEl, '?', false);
399         }
400         
401         if(this.stopEvent || e.getTarget().nodeName.toLowerCase() === 'i'){
402             e.stopEvent();
403         }
404         
405     },
406        
407     
408     hideMenuItems : function()
409     {
410         Roo.log("hide Menu Items");
411         if (!this.el) { 
412             return;
413         }
414         
415         this.el.select('.open',true).each(function(aa) {
416             
417             aa.removeClass('open');
418          
419         });
420     },
421     addxtypeChild : function (tree, cntr) {
422         var comp= Roo.bootstrap.Menu.superclass.addxtypeChild.call(this, tree, cntr);
423           
424         this.menuitems.add(comp);
425         return comp;
426
427     },
428     getEl : function()
429     {
430         Roo.log(this.el);
431         return this.el;
432     },
433     
434     clear : function()
435     {
436         this.getEl().dom.innerHTML = '';
437         this.menuitems.clear();
438     }
439 });
440
441  
442