fix menu children
[roojs1] / Roo / bootstrap / menu / Menu.js
1  
2 /**
3  * @class Roo.bootstrap.menu.Menu
4  * @extends Roo.bootstrap.Component
5  * @licence LGPL
6  * @children Roo.bootstrap.menu.Item Roo.bootstrap.menu.Separator
7  * @parent none
8  * Bootstrap Menu class - container for MenuItems - normally has to be added to a object that supports the menu property
9  * 
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. 
16  
17  * @constructor
18  * Create a new Menu
19  * @param {Object} config The config objectQ
20  */
21
22
23 Roo.bootstrap.menu.Menu = function(config){
24     
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'; 
29     }
30     
31     Roo.bootstrap.menu.Menu.superclass.constructor.call(this, config);
32     if (this.registerMenu && this.type != 'treeview')  {
33         Roo.bootstrap.menu.Manager.register(this);
34     }
35     
36     
37     this.addEvents({
38         /**
39          * @event beforeshow
40          * Fires before this menu is displayed (return false to block)
41          * @param {Roo.menu.Menu} this
42          */
43         beforeshow : true,
44         /**
45          * @event beforehide
46          * Fires before this menu is hidden (return false to block)
47          * @param {Roo.menu.Menu} this
48          */
49         beforehide : true,
50         /**
51          * @event show
52          * Fires after this menu is displayed
53          * @param {Roo.menu.Menu} this
54          */
55         show : true,
56         /**
57          * @event hide
58          * Fires after this menu is hidden
59          * @param {Roo.menu.Menu} this
60          */
61         hide : true,
62         /**
63          * @event click
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
68          */
69         click : true,
70         /**
71          * @event mouseover
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
76          */
77         mouseover : true,
78         /**
79          * @event mouseout
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
84          */
85         mouseout : true,
86         /**
87          * @event itemclick
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
91          */
92         itemclick: true
93     });
94     this.menuitems = new Roo.util.MixedCollection(false, function(o) { return o.el.id; });
95 };
96
97 Roo.extend(Roo.bootstrap.menu.Menu, Roo.bootstrap.Component,  {
98     
99    /// html : false,
100    
101     triggerEl : false,  // is this set by component builder? -- it should really be fetched from parent()???
102     type: false,
103     /**
104      * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it.
105      */
106     registerMenu : true,
107     
108     menuItems :false, // stores the menu items..
109     
110     hidden:true,
111         
112     parentMenu : false,
113     
114     stopEvent : true,
115     
116     isLink : false,
117     
118     container_method : 'getDocumentBody', // so the menu is rendered on the body and zIndex works.
119     
120     hideTrigger : false,
121     
122     align : 'tl-bl?',
123     
124     
125     getChildContainer : function() {
126         return this.el;  
127     },
128     
129     getAutoCreate : function(){
130          
131         //if (['right'].indexOf(this.align)!==-1) {
132         //    cfg.cn[1].cls += ' pull-right'
133         //}
134          
135         var cfg = {
136             tag : 'ul',
137             cls : 'dropdown-menu shadow' ,
138             style : 'z-index:1000'
139             
140         };
141         
142         if (this.type === 'submenu') {
143             cfg.cls = 'submenu active';
144         }
145         if (this.type === 'treeview') {
146             cfg.cls = 'treeview-menu';
147         }
148         
149         return cfg;
150     },
151     initEvents : function() {
152         
153        // Roo.log("ADD event");
154        // Roo.log(this.triggerEl.dom);
155         if (this.triggerEl) {
156             
157             this.triggerEl.on('click', this.onTriggerClick, this);
158             
159             this.triggerEl.on(Roo.isTouch ? 'touchstart' : 'mouseup', this.onTriggerPress, this);
160             
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');
165                 } else {
166                     this.triggerEl.addClass('dropdown-toggle');
167                 }
168             }
169         }
170         
171         if (Roo.isTouch) {
172             this.el.on('touchstart'  , this.onTouch, this);
173         }
174         this.el.on('click' , this.onClick, this);
175
176         this.el.on("mouseover", this.onMouseOver, this);
177         this.el.on("mouseout", this.onMouseOut, this);
178         
179     },
180     
181     findTargetItem : function(e)
182     {
183         var t = e.getTarget(".dropdown-menu-item", this.el,  true);
184         if(!t){
185             return false;
186         }
187         //Roo.log(t);         Roo.log(t.id);
188         if(t && t.id){
189             //Roo.log(this.menuitems);
190             return this.menuitems.get(t.id);
191             
192             //return this.items.get(t.menuItemId);
193         }
194         
195         return false;
196     },
197     
198     onTouch : function(e) 
199     {
200         Roo.log("menu.onTouch");
201         //e.stopEvent(); this make the user popdown broken
202         this.onClick(e);
203     },
204     
205     onClick : function(e)
206     {
207         Roo.log("menu.onClick");
208         
209         var t = this.findTargetItem(e);
210         if(!t || t.isContainer){
211             return;
212         }
213         Roo.log(e);
214         /*
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;
219                 return;
220             }
221             if(t.canActivate){
222                 this.setActiveItem(t, true);
223             }
224             return;
225             
226             
227         }
228         */
229        
230         Roo.log('pass click event');
231         
232         t.onClick(e);
233         
234         this.fireEvent("click", this, t, e);
235         
236         var _this = this;
237         
238         if(!t.href.length || t.href == '#'){
239             (function() { _this.hide(); }).defer(100);
240         }
241         
242     },
243     
244     onMouseOver : function(e){
245         var t  = this.findTargetItem(e);
246         //Roo.log(t);
247         //if(t){
248         //    if(t.canActivate && !t.disabled){
249         //        this.setActiveItem(t, true);
250         //    }
251         //}
252         
253         this.fireEvent("mouseover", this, e, t);
254     },
255     isVisible : function(){
256         return !this.hidden;
257     },
258     onMouseOut : function(e){
259         var t  = this.findTargetItem(e);
260         
261         //if(t ){
262         //    if(t == this.activeItem && t.shouldDeactivate(e)){
263         //        this.activeItem.deactivate();
264         //        delete this.activeItem;
265         //    }
266         //}
267         this.fireEvent("mouseout", this, e, t);
268     },
269     
270     
271     /**
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)
277      */
278     show : function(el, pos, parentMenu)
279     {
280         if (false === this.fireEvent("beforeshow", this)) {
281             Roo.log("show canceled");
282             return;
283         }
284         this.parentMenu = parentMenu;
285         if(!this.el){
286             this.render();
287         }
288         this.el.addClass('show'); // show otherwise we do not know how big we are..
289          
290         var xy = this.el.getAlignToXY(el, pos);
291         
292         // bl-tl << left align  below
293         // tl-bl << left align 
294         
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'))
298         }
299         if(xy[0] < 0){
300             // was left align - go right?
301             xy = this.el.getAlignToXY(el, this.align.replace('/r/g', 'l'))
302         }
303         
304         // goes down the bottom
305         if(this.el.getHeight() + xy[1] >= Roo.lib.Dom.getViewHeight() ||
306            xy[1]  < 0 ){
307             var a = this.align.replace('?', '').split('-');
308             xy = this.el.getAlignToXY(el, a[1]  + '-' + a[0] + '?')
309             
310         }
311         
312         this.showAt(  xy , parentMenu, false);
313     },
314      /**
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)
318      */
319     showAt : function(xy, parentMenu, /* private: */_e){
320         this.parentMenu = parentMenu;
321         if(!this.el){
322             this.render();
323         }
324         if(_e !== false){
325             this.fireEvent("beforeshow", this);
326             //xy = this.el.adjustForConstraints(xy);
327         }
328         
329         //this.el.show();
330         this.hideMenuItems();
331         this.hidden = false;
332         if (this.triggerEl) {
333             this.triggerEl.addClass('open');
334         }
335         
336         this.el.addClass('show');
337         
338         
339         
340         // reassign x when hitting right
341         
342         // reassign y when hitting bottom
343         
344         // but the list may align on trigger left or trigger top... should it be a properity?
345         
346         if(this.el.getStyle('top') != 'auto' && this.el.getStyle('top').slice(-1) != "%"){
347             this.el.setXY(xy);
348         }
349         
350         this.focus();
351         this.fireEvent("show", this);
352     },
353     
354     focus : function(){
355         return;
356         if(!this.hidden){
357             this.doFocus.defer(50, this);
358         }
359     },
360
361     doFocus : function(){
362         if(!this.hidden){
363             this.focusEl.focus();
364         }
365     },
366
367     /**
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)
370      */
371     hide : function(deep)
372     {
373         if (false === this.fireEvent("beforehide", this)) {
374             Roo.log("hide canceled");
375             return;
376         }
377         this.hideMenuItems();
378         if(this.el && this.isVisible()){
379            
380             if(this.activeItem){
381                 this.activeItem.deactivate();
382                 this.activeItem = null;
383             }
384             if (this.triggerEl) {
385                 this.triggerEl.removeClass('open');
386             }
387             
388             this.el.removeClass('show');
389             this.hidden = true;
390             this.fireEvent("hide", this);
391         }
392         if(deep === true && this.parentMenu){
393             this.parentMenu.hide(true);
394         }
395     },
396     
397     onTriggerClick : function(e)
398     {
399         Roo.log('trigger click');
400         
401         var target = e.getTarget();
402         
403         Roo.log(target.nodeName.toLowerCase());
404         
405         if(target.nodeName.toLowerCase() === 'i'){
406             e.preventDefault();
407         }
408         
409     },
410     
411     onTriggerPress  : function(e)
412     {
413         Roo.log('trigger press');
414         //Roo.log(e.getTarget());
415        // Roo.log(this.triggerEl.dom);
416        
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?');
421             return;
422         }
423         
424         if(e.getTarget().nodeName.toLowerCase() !== 'i' && this.isLink){
425             return;
426         }
427         
428         if (this.isVisible()) {
429             Roo.log('hide');
430             this.hide();
431         } else {
432             Roo.log('show');
433             
434             this.show(this.triggerEl, this.align, false);
435         }
436         
437         if(this.stopEvent || e.getTarget().nodeName.toLowerCase() === 'i'){
438             e.stopEvent();
439         }
440         
441     },
442        
443     
444     hideMenuItems : function()
445     {
446         Roo.log("hide Menu Items");
447         if (!this.el) { 
448             return;
449         }
450         
451         this.el.select('.open',true).each(function(aa) {
452             
453             aa.removeClass('open');
454          
455         });
456     },
457     addxtypeChild : function (tree, cntr) {
458         var comp= Roo.bootstrap.menu.Menu.superclass.addxtypeChild.call(this, tree, cntr);
459           
460         this.menuitems.add(comp);
461         return comp;
462
463     },
464     getEl : function()
465     {
466         Roo.log(this.el);
467         return this.el;
468     },
469     
470     clear : function()
471     {
472         this.getEl().dom.innerHTML = '';
473         this.menuitems.clear();
474     }
475 });
476
477  
478