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