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