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