Fix #5791 - Search Criteria on Orders / Columns on orders
[roojs1] / Roo / menu / Menu.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11  
12 /**
13  * @class Roo.menu.Menu
14  * @extends Roo.util.Observable
15  * A menu object.  This is the container to which you add all other menu items.  Menu can also serve a as a base class
16  * when you want a specialzed menu based off of another component (like {@link Roo.menu.DateMenu} for example).
17  * @constructor
18  * Creates a new Menu
19  * @param {Object} config Configuration options
20  */
21 Roo.menu.Menu = function(config){
22     
23     Roo.menu.Menu.superclass.constructor.call(this, config);
24     
25     this.id = this.id || Roo.id();
26     this.addEvents({
27         /**
28          * @event beforeshow
29          * Fires before this menu is displayed
30          * @param {Roo.menu.Menu} this
31          */
32         beforeshow : true,
33         /**
34          * @event beforehide
35          * Fires before this menu is hidden
36          * @param {Roo.menu.Menu} this
37          */
38         beforehide : true,
39         /**
40          * @event show
41          * Fires after this menu is displayed
42          * @param {Roo.menu.Menu} this
43          */
44         show : true,
45         /**
46          * @event hide
47          * Fires after this menu is hidden
48          * @param {Roo.menu.Menu} this
49          */
50         hide : true,
51         /**
52          * @event click
53          * Fires when this menu is clicked (or when the enter key is pressed while it is active)
54          * @param {Roo.menu.Menu} this
55          * @param {Roo.menu.Item} menuItem The menu item that was clicked
56          * @param {Roo.EventObject} e
57          */
58         click : true,
59         /**
60          * @event mouseover
61          * Fires when the mouse is hovering over this menu
62          * @param {Roo.menu.Menu} this
63          * @param {Roo.EventObject} e
64          * @param {Roo.menu.Item} menuItem The menu item that was clicked
65          */
66         mouseover : true,
67         /**
68          * @event mouseout
69          * Fires when the mouse exits this menu
70          * @param {Roo.menu.Menu} this
71          * @param {Roo.EventObject} e
72          * @param {Roo.menu.Item} menuItem The menu item that was clicked
73          */
74         mouseout : true,
75         /**
76          * @event itemclick
77          * Fires when a menu item contained in this menu is clicked
78          * @param {Roo.menu.BaseItem} baseItem The BaseItem that was clicked
79          * @param {Roo.EventObject} e
80          */
81         itemclick: true
82     });
83     if (this.registerMenu) {
84         Roo.menu.MenuMgr.register(this);
85     }
86     
87     var mis = this.items;
88     this.items = new Roo.util.MixedCollection();
89     if(mis){
90         this.add.apply(this, mis);
91     }
92 };
93
94 Roo.extend(Roo.menu.Menu, Roo.util.Observable, {
95     /**
96      * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)
97      */
98     minWidth : 120,
99     /**
100      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
101      * for bottom-right shadow (defaults to "sides")
102      */
103     shadow : "sides",
104     /**
105      * @cfg {String} subMenuAlign The {@link Roo.Element#alignTo} anchor position value to use for submenus of
106      * this menu (defaults to "tl-tr?")
107      */
108     subMenuAlign : "tl-tr?",
109     /**
110      * @cfg {String} defaultAlign The default {@link Roo.Element#alignTo) anchor position value for this menu
111      * relative to its element of origin (defaults to "tl-bl?")
112      */
113     defaultAlign : "tl-bl?",
114     /**
115      * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)
116      */
117     allowOtherMenus : false,
118     /**
119      * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it.
120      */
121     registerMenu : true,
122
123     hidden:true,
124
125     // private
126     render : function(){
127         if(this.el){
128             return;
129         }
130         var el = this.el = new Roo.Layer({
131             cls: "x-menu",
132             shadow:this.shadow,
133             constrain: false,
134             parentEl: this.parentEl || document.body,
135             zindex:15000
136         });
137
138         this.keyNav = new Roo.menu.MenuNav(this);
139
140         if(this.plain){
141             el.addClass("x-menu-plain");
142         }
143         if(this.cls){
144             el.addClass(this.cls);
145         }
146         // generic focus element
147         this.focusEl = el.createChild({
148             tag: "a", cls: "x-menu-focus", href: "#", onclick: "return false;", tabIndex:"-1"
149         });
150         var ul = el.createChild({tag: "ul", cls: "x-menu-list"});
151         //disabling touch- as it's causing issues ..
152         //ul.on(Roo.isTouch ? 'touchstart' : 'click'   , this.onClick, this);
153         ul.on('click'   , this.onClick, this);
154         
155         
156         ul.on("mouseover", this.onMouseOver, this);
157         ul.on("mouseout", this.onMouseOut, this);
158         this.items.each(function(item){
159             if (item.hidden) {
160                 return;
161             }
162             
163             var li = document.createElement("li");
164             li.className = "x-menu-list-item";
165             ul.dom.appendChild(li);
166             item.render(li, this);
167         }, this);
168         this.ul = ul;
169         this.autoWidth();
170     },
171
172     // private
173     autoWidth : function(){
174         var el = this.el, ul = this.ul;
175         if(!el){
176             return;
177         }
178         var w = this.width;
179         if(w){
180             el.setWidth(w);
181         }else if(Roo.isIE){
182             el.setWidth(this.minWidth);
183             var t = el.dom.offsetWidth; // force recalc
184             el.setWidth(ul.getWidth()+el.getFrameWidth("lr"));
185         }
186     },
187
188     // private
189     delayAutoWidth : function(){
190         if(this.rendered){
191             if(!this.awTask){
192                 this.awTask = new Roo.util.DelayedTask(this.autoWidth, this);
193             }
194             this.awTask.delay(20);
195         }
196     },
197
198     // private
199     findTargetItem : function(e){
200         var t = e.getTarget(".x-menu-list-item", this.ul,  true);
201         if(t && t.menuItemId){
202             return this.items.get(t.menuItemId);
203         }
204     },
205
206     // private
207     onClick : function(e){
208         Roo.log("menu.onClick");
209         var t = this.findTargetItem(e);
210         if(!t){
211             return;
212         }
213         Roo.log(e);
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         t.onClick(e);
229         this.fireEvent("click", this, t, e);
230     },
231
232     // private
233     setActiveItem : function(item, autoExpand){
234         if(item != this.activeItem){
235             if(this.activeItem){
236                 this.activeItem.deactivate();
237             }
238             this.activeItem = item;
239             item.activate(autoExpand);
240         }else if(autoExpand){
241             item.expandMenu();
242         }
243     },
244
245     // private
246     tryActivate : function(start, step){
247         var items = this.items;
248         for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
249             var item = items.get(i);
250             if(!item.disabled && item.canActivate){
251                 this.setActiveItem(item, false);
252                 return item;
253             }
254         }
255         return false;
256     },
257
258     // private
259     onMouseOver : function(e){
260         var t;
261         if(t = this.findTargetItem(e)){
262             if(t.canActivate && !t.disabled){
263                 this.setActiveItem(t, true);
264             }
265         }
266         this.fireEvent("mouseover", this, e, t);
267     },
268
269     // private
270     onMouseOut : function(e){
271         var t;
272         if(t = this.findTargetItem(e)){
273             if(t == this.activeItem && t.shouldDeactivate(e)){
274                 this.activeItem.deactivate();
275                 delete this.activeItem;
276             }
277         }
278         this.fireEvent("mouseout", this, e, t);
279     },
280
281     /**
282      * Read-only.  Returns true if the menu is currently displayed, else false.
283      * @type Boolean
284      */
285     isVisible : function(){
286         return this.el && !this.hidden;
287     },
288
289     /**
290      * Displays this menu relative to another element
291      * @param {String/HTMLElement/Roo.Element} element The element to align to
292      * @param {String} position (optional) The {@link Roo.Element#alignTo} anchor position to use in aligning to
293      * the element (defaults to this.defaultAlign)
294      * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
295      */
296     show : function(el, pos, parentMenu){
297         this.parentMenu = parentMenu;
298         if(!this.el){
299             this.render();
300         }
301         this.fireEvent("beforeshow", this);
302         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign), parentMenu, false);
303     },
304
305     /**
306      * Displays this menu at a specific xy position
307      * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
308      * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
309      */
310     showAt : function(xy, parentMenu, /* private: */_e){
311         this.parentMenu = parentMenu;
312         if(!this.el){
313             this.render();
314         }
315         if(_e !== false){
316             this.fireEvent("beforeshow", this);
317             xy = this.el.adjustForConstraints(xy);
318         }
319         this.el.setXY(xy);
320         this.el.show();
321         this.hidden = false;
322         this.focus();
323         this.fireEvent("show", this);
324     },
325
326     focus : function(){
327         if(!this.hidden){
328             this.doFocus.defer(50, this);
329         }
330     },
331
332     doFocus : function(){
333         if(!this.hidden){
334             this.focusEl.focus();
335         }
336     },
337
338     /**
339      * Hides this menu and optionally all parent menus
340      * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
341      */
342     hide : function(deep){
343         if(this.el && this.isVisible()){
344             this.fireEvent("beforehide", this);
345             if(this.activeItem){
346                 this.activeItem.deactivate();
347                 this.activeItem = null;
348             }
349             this.el.hide();
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     /**
359      * Addds one or more items of any type supported by the Menu class, or that can be converted into menu items.
360      * Any of the following are valid:
361      * <ul>
362      * <li>Any menu item object based on {@link Roo.menu.Item}</li>
363      * <li>An HTMLElement object which will be converted to a menu item</li>
364      * <li>A menu item config object that will be created as a new menu item</li>
365      * <li>A string, which can either be '-' or 'separator' to add a menu separator, otherwise
366      * it will be converted into a {@link Roo.menu.TextItem} and added</li>
367      * </ul>
368      * Usage:
369      * <pre><code>
370 // Create the menu
371 var menu = new Roo.menu.Menu();
372
373 // Create a menu item to add by reference
374 var menuItem = new Roo.menu.Item({ text: 'New Item!' });
375
376 // Add a bunch of items at once using different methods.
377 // Only the last item added will be returned.
378 var item = menu.add(
379     menuItem,                // add existing item by ref
380     'Dynamic Item',          // new TextItem
381     '-',                     // new separator
382     { text: 'Config Item' }  // new item by config
383 );
384 </code></pre>
385      * @param {Mixed} args One or more menu items, menu item configs or other objects that can be converted to menu items
386      * @return {Roo.menu.Item} The menu item that was added, or the last one if multiple items were added
387      */
388     add : function(){
389         var a = arguments, l = a.length, item;
390         for(var i = 0; i < l; i++){
391             var el = a[i];
392             if ((typeof(el) == "object") && el.xtype && el.xns) {
393                 el = Roo.factory(el, Roo.menu);
394             }
395             
396             if(el.render){ // some kind of Item
397                 item = this.addItem(el);
398             }else if(typeof el == "string"){ // string
399                 if(el == "separator" || el == "-"){
400                     item = this.addSeparator();
401                 }else{
402                     item = this.addText(el);
403                 }
404             }else if(el.tagName || el.el){ // element
405                 item = this.addElement(el);
406             }else if(typeof el == "object"){ // must be menu item config?
407                 item = this.addMenuItem(el);
408             }
409         }
410         return item;
411     },
412
413     /**
414      * Returns this menu's underlying {@link Roo.Element} object
415      * @return {Roo.Element} The element
416      */
417     getEl : function(){
418         if(!this.el){
419             this.render();
420         }
421         return this.el;
422     },
423
424     /**
425      * Adds a separator bar to the menu
426      * @return {Roo.menu.Item} The menu item that was added
427      */
428     addSeparator : function(){
429         return this.addItem(new Roo.menu.Separator());
430     },
431
432     /**
433      * Adds an {@link Roo.Element} object to the menu
434      * @param {String/HTMLElement/Roo.Element} el The element or DOM node to add, or its id
435      * @return {Roo.menu.Item} The menu item that was added
436      */
437     addElement : function(el){
438         return this.addItem(new Roo.menu.BaseItem(el));
439     },
440
441     /**
442      * Adds an existing object based on {@link Roo.menu.Item} to the menu
443      * @param {Roo.menu.Item} item The menu item to add
444      * @return {Roo.menu.Item} The menu item that was added
445      */
446     addItem : function(item){
447         this.items.add(item);
448         if(this.ul){
449             var li = document.createElement("li");
450             li.className = "x-menu-list-item";
451             this.ul.dom.appendChild(li);
452             item.render(li, this);
453             this.delayAutoWidth();
454         }
455         return item;
456     },
457
458     /**
459      * Creates a new {@link Roo.menu.Item} based an the supplied config object and adds it to the menu
460      * @param {Object} config A MenuItem config object
461      * @return {Roo.menu.Item} The menu item that was added
462      */
463     addMenuItem : function(config){
464         if(!(config instanceof Roo.menu.Item)){
465             if(typeof config.checked == "boolean"){ // must be check menu item config?
466                 config = new Roo.menu.CheckItem(config);
467             }else{
468                 config = new Roo.menu.Item(config);
469             }
470         }
471         return this.addItem(config);
472     },
473
474     /**
475      * Creates a new {@link Roo.menu.TextItem} with the supplied text and adds it to the menu
476      * @param {String} text The text to display in the menu item
477      * @return {Roo.menu.Item} The menu item that was added
478      */
479     addText : function(text){
480         return this.addItem(new Roo.menu.TextItem({ text : text }));
481     },
482
483     /**
484      * Inserts an existing object based on {@link Roo.menu.Item} to the menu at a specified index
485      * @param {Number} index The index in the menu's list of current items where the new item should be inserted
486      * @param {Roo.menu.Item} item The menu item to add
487      * @return {Roo.menu.Item} The menu item that was added
488      */
489     insert : function(index, item){
490         this.items.insert(index, item);
491         if(this.ul){
492             var li = document.createElement("li");
493             li.className = "x-menu-list-item";
494             this.ul.dom.insertBefore(li, this.ul.dom.childNodes[index]);
495             item.render(li, this);
496             this.delayAutoWidth();
497         }
498         return item;
499     },
500
501     /**
502      * Removes an {@link Roo.menu.Item} from the menu and destroys the object
503      * @param {Roo.menu.Item} item The menu item to remove
504      */
505     remove : function(item){
506         this.items.removeKey(item.id);
507         item.destroy();
508     },
509
510     /**
511      * Removes and destroys all items in the menu
512      */
513     removeAll : function(){
514         var f;
515         while(f = this.items.first()){
516             this.remove(f);
517         }
518     }
519 });
520
521 // MenuNav is a private utility class used internally by the Menu
522 Roo.menu.MenuNav = function(menu){
523     Roo.menu.MenuNav.superclass.constructor.call(this, menu.el);
524     this.scope = this.menu = menu;
525 };
526
527 Roo.extend(Roo.menu.MenuNav, Roo.KeyNav, {
528     doRelay : function(e, h){
529         var k = e.getKey();
530         if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
531             this.menu.tryActivate(0, 1);
532             return false;
533         }
534         return h.call(this.scope || this, e, this.menu);
535     },
536
537     up : function(e, m){
538         if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
539             m.tryActivate(m.items.length-1, -1);
540         }
541     },
542
543     down : function(e, m){
544         if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
545             m.tryActivate(0, 1);
546         }
547     },
548
549     right : function(e, m){
550         if(m.activeItem){
551             m.activeItem.expandMenu(true);
552         }
553     },
554
555     left : function(e, m){
556         m.hide();
557         if(m.parentMenu && m.parentMenu.activeItem){
558             m.parentMenu.activeItem.activate();
559         }
560     },
561
562     enter : function(e, m){
563         if(m.activeItem){
564             e.stopPropagation();
565             m.activeItem.onClick(e);
566             m.fireEvent("click", this, m.activeItem);
567             return true;
568         }
569     }
570 });