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