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