Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / widgets / menu / Menu.js
1 /*!
2  * Ext JS Library 3.0.3
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**\r
8  * @class Ext.layout.MenuLayout\r
9  * @extends Ext.layout.ContainerLayout\r
10  * <p>Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.</p>\r
11  */\r
12  Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, {\r
13     monitorResize : true,\r
14 \r
15     setContainer : function(ct){\r
16         this.monitorResize = !ct.floating;\r
17         // This event is only fired by the menu in IE, used so we don't couple\r
18         // the menu with the layout.\r
19         ct.on('autosize', this.doAutoSize, this);\r
20         Ext.layout.MenuLayout.superclass.setContainer.call(this, ct);\r
21     },\r
22 \r
23     renderItem : function(c, position, target){\r
24         if (!this.itemTpl) {\r
25             this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate(\r
26                 '<li id="{itemId}" class="{itemCls}">',\r
27                     '<tpl if="needsIcon">',\r
28                         '<img src="{icon}" class="{iconCls}"/>',\r
29                     '</tpl>',\r
30                 '</li>'\r
31             );\r
32         }\r
33 \r
34         if(c && !c.rendered){\r
35             if(Ext.isNumber(position)){\r
36                 position = target.dom.childNodes[position];\r
37             }\r
38             var a = this.getItemArgs(c);\r
39 \r
40 //          The Component's positionEl is the <li> it is rendered into\r
41             c.render(c.positionEl = position ?\r
42                 this.itemTpl.insertBefore(position, a, true) :\r
43                 this.itemTpl.append(target, a, true));\r
44 \r
45 //          Link the containing <li> to the item.\r
46             c.positionEl.menuItemId = c.getItemId();\r
47 \r
48 //          If rendering a regular Component, and it needs an icon,\r
49 //          move the Component rightwards.\r
50             if (!a.isMenuItem && a.needsIcon) {\r
51                 c.positionEl.addClass('x-menu-list-item-indent');\r
52             }\r
53             this.configureItem(c, position);\r
54         }else if(c && !this.isValidParent(c, target)){\r
55             if(Ext.isNumber(position)){\r
56                 position = target.dom.childNodes[position];\r
57             }\r
58             target.dom.insertBefore(c.getActionEl().dom, position || null);\r
59         }\r
60     },\r
61 \r
62     getItemArgs : function(c) {\r
63         var isMenuItem = c instanceof Ext.menu.Item;\r
64         return {\r
65             isMenuItem: isMenuItem,\r
66             needsIcon: !isMenuItem && (c.icon || c.iconCls),\r
67             icon: c.icon || Ext.BLANK_IMAGE_URL,\r
68             iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),\r
69             itemId: 'x-menu-el-' + c.id,\r
70             itemCls: 'x-menu-list-item '\r
71         };\r
72     },\r
73 \r
74 //  Valid if the Component is in a <li> which is part of our target <ul>\r
75     isValidParent : function(c, target) {\r
76         return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target);\r
77     },\r
78 \r
79     onLayout : function(ct, target){\r
80         this.renderAll(ct, target);\r
81         this.doAutoSize();\r
82     },\r
83 \r
84     doAutoSize : function(){\r
85         var ct = this.container, w = ct.width;\r
86         if(ct.floating){\r
87             if(w){\r
88                 ct.setWidth(w);\r
89             }else if(Ext.isIE){\r
90                 ct.setWidth(Ext.isStrict && (Ext.isIE7 || Ext.isIE8) ? 'auto' : ct.minWidth);\r
91                 var el = ct.getEl(), t = el.dom.offsetWidth; // force recalc\r
92                 ct.setWidth(ct.getLayoutTarget().getWidth() + el.getFrameWidth('lr'));\r
93             }\r
94         }\r
95     }\r
96 });\r
97 Ext.Container.LAYOUTS['menu'] = Ext.layout.MenuLayout;\r
98 \r
99 /**\r
100  * @class Ext.menu.Menu\r
101  * @extends Ext.Container\r
102  * <p>A menu object.  This is the container to which you may add menu items.  Menu can also serve as a base class\r
103  * when you want a specialized menu based off of another component (like {@link Ext.menu.DateMenu} for example).</p>\r
104  * <p>Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Component}s.</p>\r
105  * <p>To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items}\r
106  * specify <tt>iconCls: 'no-icon'</tt>.  This reserves a space for an icon, and indents the Component in line\r
107  * with the other menu items.  See {@link Ext.form.ComboBox}.{@link Ext.form.ComboBox#getListParent getListParent}\r
108  * for an example.</p>\r
109  * <p>By default, Menus are absolutely positioned, floating Components. By configuring a Menu with\r
110  * <b><tt>{@link #floating}:false</tt></b>, a Menu may be used as child of a Container.</p>\r
111  *\r
112  * @xtype menu\r
113  */\r
114 Ext.menu.Menu = Ext.extend(Ext.Container, {\r
115     /**\r
116      * @cfg {Object} defaults\r
117      * A config object that will be applied to all items added to this container either via the {@link #items}\r
118      * config or via the {@link #add} method.  The defaults config can contain any number of\r
119      * name/value property pairs to be added to each item, and should be valid for the types of items\r
120      * being added to the menu.\r
121      */\r
122     /**\r
123      * @cfg {Mixed} items\r
124      * An array of items to be added to this menu. Menus may contain either {@link Ext.menu.Item menu items},\r
125      * or general {@link Ext.Component Component}s.\r
126      */\r
127     /**\r
128      * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)\r
129      */\r
130     minWidth : 120,\r
131     /**\r
132      * @cfg {Boolean/String} shadow True or 'sides' for the default effect, 'frame' for 4-way shadow, and 'drop'\r
133      * for bottom-right shadow (defaults to 'sides')\r
134      */\r
135     shadow : 'sides',\r
136     /**\r
137      * @cfg {String} subMenuAlign The {@link Ext.Element#alignTo} anchor position value to use for submenus of\r
138      * this menu (defaults to 'tl-tr?')\r
139      */\r
140     subMenuAlign : 'tl-tr?',\r
141     /**\r
142      * @cfg {String} defaultAlign The default {@link Ext.Element#alignTo} anchor position value for this menu\r
143      * relative to its element of origin (defaults to 'tl-bl?')\r
144      */\r
145     defaultAlign : 'tl-bl?',\r
146     /**\r
147      * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)\r
148      */\r
149     allowOtherMenus : false,\r
150     /**\r
151      * @cfg {Boolean} ignoreParentClicks True to ignore clicks on any item in this menu that is a parent item (displays\r
152      * a submenu) so that the submenu is not dismissed when clicking the parent item (defaults to false).\r
153      */\r
154     ignoreParentClicks : false,\r
155     /**\r
156      * @cfg {Boolean} enableScrolling True to allow the menu container to have scroller controls if the menu is too long (defaults to true).\r
157      */\r
158     enableScrolling : true,\r
159     /**\r
160      * @cfg {Number} maxHeight The maximum height of the menu. Only applies when enableScrolling is set to True (defaults to null).\r
161      */\r
162     maxHeight : null,\r
163     /**\r
164      * @cfg {Number} scrollIncrement The amount to scroll the menu. Only applies when enableScrolling is set to True (defaults to 24).\r
165      */\r
166     scrollIncrement : 24,\r
167     /**\r
168      * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).\r
169      */\r
170     showSeparator : true,\r
171     /**\r
172      * @cfg {Array} defaultOffsets An array specifying the [x, y] offset in pixels by which to\r
173      * change the default Menu popup position after aligning according to the {@link #defaultAlign}\r
174      * configuration. Defaults to <tt>[0, 0]</tt>.\r
175      */\r
176     defaultOffsets : [0, 0],\r
177     \r
178     /**\r
179      * @cfg {Boolean} plain\r
180      * True to remove the incised line down the left side of the menu. Defaults to <tt>false</tt>.\r
181      */\r
182     plain : false,\r
183 \r
184     /**\r
185      * @cfg {Boolean} floating\r
186      * <p>By default, a Menu configured as <b><code>floating:true</code></b>\r
187      * will be rendered as an {@link Ext.Layer} (an absolutely positioned,\r
188      * floating Component with zindex=15000).\r
189      * If configured as <b><code>floating:false</code></b>, the Menu may be\r
190      * used as child item of another Container instead of a free-floating\r
191      * {@link Ext.Layer Layer}.\r
192      */\r
193     floating : true,\r
194 \r
195     // private\r
196     hidden : true,\r
197 \r
198     /**\r
199      * @cfg {String/Object} layout\r
200      * This class assigns a default layout (<code>layout:'<b>menu</b>'</code>).\r
201      * Developers <i>may</i> override this configuration option if another layout is required.\r
202      * See {@link Ext.Container#layout} for additional information.\r
203      */\r
204     layout : 'menu',\r
205     hideMode : 'offsets',    // Important for laying out Components\r
206     scrollerHeight : 8,\r
207     autoLayout : true,       // Provided for backwards compat\r
208     defaultType : 'menuitem',\r
209 \r
210     initComponent : function(){\r
211         if(Ext.isArray(this.initialConfig)){\r
212             Ext.apply(this, {items:this.initialConfig});\r
213         }\r
214         this.addEvents(\r
215             /**\r
216              * @event click\r
217              * Fires when this menu is clicked (or when the enter key is pressed while it is active)\r
218              * @param {Ext.menu.Menu} this\r
219             * @param {Ext.menu.Item} menuItem The menu item that was clicked\r
220              * @param {Ext.EventObject} e\r
221              */\r
222             'click',\r
223             /**\r
224              * @event mouseover\r
225              * Fires when the mouse is hovering over this menu\r
226              * @param {Ext.menu.Menu} this\r
227              * @param {Ext.EventObject} e\r
228              * @param {Ext.menu.Item} menuItem The menu item that was clicked\r
229              */\r
230             'mouseover',\r
231             /**\r
232              * @event mouseout\r
233              * Fires when the mouse exits this menu\r
234              * @param {Ext.menu.Menu} this\r
235              * @param {Ext.EventObject} e\r
236              * @param {Ext.menu.Item} menuItem The menu item that was clicked\r
237              */\r
238             'mouseout',\r
239             /**\r
240              * @event itemclick\r
241              * Fires when a menu item contained in this menu is clicked\r
242              * @param {Ext.menu.BaseItem} baseItem The BaseItem that was clicked\r
243              * @param {Ext.EventObject} e\r
244              */\r
245             'itemclick'\r
246         );\r
247         Ext.menu.MenuMgr.register(this);\r
248         if(this.floating){\r
249             Ext.EventManager.onWindowResize(this.hide, this);\r
250         }else{\r
251             if(this.initialConfig.hidden !== false){\r
252                 this.hidden = false;\r
253             }\r
254             this.internalDefaults = {hideOnClick: false};\r
255         }\r
256         Ext.menu.Menu.superclass.initComponent.call(this);\r
257         if(this.autoLayout){\r
258             this.on({\r
259                 add: this.doLayout,\r
260                 remove: this.doLayout,\r
261                 scope: this\r
262             });\r
263         }\r
264     },\r
265 \r
266     //private\r
267     getLayoutTarget : function() {\r
268         return this.ul;\r
269     },\r
270 \r
271     // private\r
272     onRender : function(ct, position){\r
273         if(!ct){\r
274             ct = Ext.getBody();\r
275         }\r
276 \r
277         var dh = {\r
278             id: this.getId(),\r
279             cls: 'x-menu ' + ((this.floating) ? 'x-menu-floating x-layer ' : '') + (this.cls || '') + (this.plain ? ' x-menu-plain' : '') + (this.showSeparator ? '' : ' x-menu-nosep'),\r
280             style: this.style,\r
281             cn: [\r
282                 {tag: 'a', cls: 'x-menu-focus', href: '#', onclick: 'return false;', tabIndex: '-1'},\r
283                 {tag: 'ul', cls: 'x-menu-list'}\r
284             ]\r
285         };\r
286         if(this.floating){\r
287             this.el = new Ext.Layer({\r
288                 shadow: this.shadow,\r
289                 dh: dh,\r
290                 constrain: false,\r
291                 parentEl: ct,\r
292                 zindex:15000\r
293             });\r
294         }else{\r
295             this.el = ct.createChild(dh);\r
296         }\r
297         Ext.menu.Menu.superclass.onRender.call(this, ct, position);\r
298 \r
299         if(!this.keyNav){\r
300             this.keyNav = new Ext.menu.MenuNav(this);\r
301         }\r
302         // generic focus element\r
303         this.focusEl = this.el.child('a.x-menu-focus');\r
304         this.ul = this.el.child('ul.x-menu-list');\r
305         this.mon(this.ul, {\r
306             scope: this,\r
307             click: this.onClick,\r
308             mouseover: this.onMouseOver,\r
309             mouseout: this.onMouseOut\r
310         });\r
311         if(this.enableScrolling){\r
312             this.mon(this.el, {\r
313                 scope: this,\r
314                 delegate: '.x-menu-scroller',\r
315                 click: this.onScroll,\r
316                 mouseover: this.deactivateActive\r
317             });\r
318         }\r
319     },\r
320 \r
321     // private\r
322     findTargetItem : function(e){\r
323         var t = e.getTarget('.x-menu-list-item', this.ul, true);\r
324         if(t && t.menuItemId){\r
325             return this.items.get(t.menuItemId);\r
326         }\r
327     },\r
328 \r
329     // private\r
330     onClick : function(e){\r
331         var t = this.findTargetItem(e);\r
332         if(t){\r
333             if(t.isFormField){\r
334                 this.setActiveItem(t);\r
335             }else if(t instanceof Ext.menu.BaseItem){\r
336                 if(t.menu && this.ignoreParentClicks){\r
337                     t.expandMenu();\r
338                     e.preventDefault();\r
339                 }else if(t.onClick){\r
340                     t.onClick(e);\r
341                     this.fireEvent('click', this, t, e);\r
342                 }\r
343             }\r
344         }\r
345     },\r
346 \r
347     // private\r
348     setActiveItem : function(item, autoExpand){\r
349         if(item != this.activeItem){\r
350             this.deactivateActive();\r
351             if((this.activeItem = item).isFormField){\r
352                 item.focus();\r
353             }else{\r
354                 item.activate(autoExpand);\r
355             }\r
356         }else if(autoExpand){\r
357             item.expandMenu();\r
358         }\r
359     },\r
360 \r
361     deactivateActive : function(){\r
362         var a = this.activeItem;\r
363         if(a){\r
364             if(a.isFormField){\r
365                 //Fields cannot deactivate, but Combos must collapse\r
366                 if(a.collapse){\r
367                     a.collapse();\r
368                 }\r
369             }else{\r
370                 a.deactivate();\r
371             }\r
372             delete this.activeItem;\r
373         }\r
374     },\r
375 \r
376     // private\r
377     tryActivate : function(start, step){\r
378         var items = this.items;\r
379         for(var i = start, len = items.length; i >= 0 && i < len; i+= step){\r
380             var item = items.get(i);\r
381             if(!item.disabled && (item.canActivate || item.isFormField)){\r
382                 this.setActiveItem(item, false);\r
383                 return item;\r
384             }\r
385         }\r
386         return false;\r
387     },\r
388 \r
389     // private\r
390     onMouseOver : function(e){\r
391         var t = this.findTargetItem(e);\r
392         if(t){\r
393             if(t.canActivate && !t.disabled){\r
394                 this.setActiveItem(t, true);\r
395             }\r
396         }\r
397         this.over = true;\r
398         this.fireEvent('mouseover', this, e, t);\r
399     },\r
400 \r
401     // private\r
402     onMouseOut : function(e){\r
403         var t = this.findTargetItem(e);\r
404         if(t){\r
405             if(t == this.activeItem && t.shouldDeactivate && t.shouldDeactivate(e)){\r
406                 this.activeItem.deactivate();\r
407                 delete this.activeItem;\r
408             }\r
409         }\r
410         this.over = false;\r
411         this.fireEvent('mouseout', this, e, t);\r
412     },\r
413 \r
414     // private\r
415     onScroll : function(e, t){\r
416         if(e){\r
417             e.stopEvent();\r
418         }\r
419         var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');\r
420         ul.scrollTop += this.scrollIncrement * (top ? -1 : 1);\r
421         if(top ? ul.scrollTop <= 0 : ul.scrollTop + this.activeMax >= ul.scrollHeight){\r
422            this.onScrollerOut(null, t);\r
423         }\r
424     },\r
425 \r
426     // private\r
427     onScrollerIn : function(e, t){\r
428         var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');\r
429         if(top ? ul.scrollTop > 0 : ul.scrollTop + this.activeMax < ul.scrollHeight){\r
430             Ext.fly(t).addClass(['x-menu-item-active', 'x-menu-scroller-active']);\r
431         }\r
432     },\r
433 \r
434     // private\r
435     onScrollerOut : function(e, t){\r
436         Ext.fly(t).removeClass(['x-menu-item-active', 'x-menu-scroller-active']);\r
437     },\r
438 \r
439     /**\r
440      * If <code>{@link #floating}=true</code>, shows this menu relative to\r
441      * another element using {@link #showat}, otherwise uses {@link Ext.Component#show}.\r
442      * @param {Mixed} element The element to align to\r
443      * @param {String} position (optional) The {@link Ext.Element#alignTo} anchor position to use in aligning to\r
444      * the element (defaults to this.defaultAlign)\r
445      * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)\r
446      */\r
447     show : function(el, pos, parentMenu){\r
448         if(this.floating){\r
449             this.parentMenu = parentMenu;\r
450             if(!this.el){\r
451                 this.render();\r
452                 this.doLayout(false, true);\r
453             }\r
454             this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu);\r
455         }else{\r
456             Ext.menu.Menu.superclass.show.call(this);\r
457         }\r
458     },\r
459 \r
460     /**\r
461      * Displays this menu at a specific xy position and fires the 'show' event if a\r
462      * handler for the 'beforeshow' event does not return false cancelling the operation.\r
463      * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)\r
464      * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)\r
465      */\r
466     showAt : function(xy, parentMenu){\r
467         if(this.fireEvent('beforeshow', this) !== false){\r
468             this.parentMenu = parentMenu;\r
469             if(!this.el){\r
470                 this.render();\r
471             }\r
472             if(this.enableScrolling){\r
473                 // set the position so we can figure out the constrain value.\r
474                 this.el.setXY(xy);\r
475                 //constrain the value, keep the y coordinate the same\r
476                 this.constrainScroll(xy[1]);\r
477                 xy = [this.el.adjustForConstraints(xy)[0], xy[1]];\r
478             }else{\r
479                 //constrain to the viewport.\r
480                 xy = this.el.adjustForConstraints(xy);\r
481             }\r
482             this.el.setXY(xy);\r
483             this.el.show();\r
484             Ext.menu.Menu.superclass.onShow.call(this);\r
485             if(Ext.isIE){\r
486                 // internal event, used so we don't couple the layout to the menu\r
487                 this.fireEvent('autosize', this);\r
488                 if(!Ext.isIE8){\r
489                     this.el.repaint();\r
490                 }\r
491             }\r
492             this.hidden = false;\r
493             this.focus();\r
494             this.fireEvent('show', this);\r
495         }\r
496     },\r
497 \r
498     constrainScroll : function(y){\r
499         var max, full = this.ul.setHeight('auto').getHeight();\r
500         if(this.floating){\r
501             max = this.maxHeight ? this.maxHeight : Ext.fly(this.el.dom.parentNode).getViewSize().height - y;\r
502         }else{\r
503             max = this.getHeight();\r
504         }\r
505         if(full > max && max > 0){\r
506             this.activeMax = max - this.scrollerHeight * 2 - this.el.getFrameWidth('tb') - Ext.num(this.el.shadowOffset, 0);\r
507             this.ul.setHeight(this.activeMax);\r
508             this.createScrollers();\r
509             this.el.select('.x-menu-scroller').setDisplayed('');\r
510         }else{\r
511             this.ul.setHeight(full);\r
512             this.el.select('.x-menu-scroller').setDisplayed('none');\r
513         }\r
514         this.ul.dom.scrollTop = 0;\r
515     },\r
516 \r
517     createScrollers : function(){\r
518         if(!this.scroller){\r
519             this.scroller = {\r
520                 pos: 0,\r
521                 top: this.el.insertFirst({\r
522                     tag: 'div',\r
523                     cls: 'x-menu-scroller x-menu-scroller-top',\r
524                     html: '&#160;'\r
525                 }),\r
526                 bottom: this.el.createChild({\r
527                     tag: 'div',\r
528                     cls: 'x-menu-scroller x-menu-scroller-bottom',\r
529                     html: '&#160;'\r
530                 })\r
531             };\r
532             this.scroller.top.hover(this.onScrollerIn, this.onScrollerOut, this);\r
533             this.scroller.topRepeater = new Ext.util.ClickRepeater(this.scroller.top, {\r
534                 listeners: {\r
535                     click: this.onScroll.createDelegate(this, [null, this.scroller.top], false)\r
536                 }\r
537             });\r
538             this.scroller.bottom.hover(this.onScrollerIn, this.onScrollerOut, this);\r
539             this.scroller.bottomRepeater = new Ext.util.ClickRepeater(this.scroller.bottom, {\r
540                 listeners: {\r
541                     click: this.onScroll.createDelegate(this, [null, this.scroller.bottom], false)\r
542                 }\r
543             });\r
544         }\r
545     },\r
546 \r
547     onLayout : function(){\r
548         if(this.isVisible()){\r
549             if(this.enableScrolling){\r
550                 this.constrainScroll(this.el.getTop());\r
551             }\r
552             if(this.floating){\r
553                 this.el.sync();\r
554             }\r
555         }\r
556     },\r
557 \r
558     focus : function(){\r
559         if(!this.hidden){\r
560             this.doFocus.defer(50, this);\r
561         }\r
562     },\r
563 \r
564     doFocus : function(){\r
565         if(!this.hidden){\r
566             this.focusEl.focus();\r
567         }\r
568     },\r
569 \r
570     /**\r
571      * Hides this menu and optionally all parent menus\r
572      * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)\r
573      */\r
574     hide : function(deep){\r
575         this.deepHide = deep;\r
576         Ext.menu.Menu.superclass.hide.call(this);\r
577         delete this.deepHide;\r
578     },\r
579 \r
580     // private\r
581     onHide : function(){\r
582         Ext.menu.Menu.superclass.onHide.call(this);\r
583         this.deactivateActive();\r
584         if(this.el && this.floating){\r
585             this.el.hide();\r
586         }\r
587         var pm = this.parentMenu;\r
588         if(this.deepHide === true && pm){\r
589             if(pm.floating){\r
590                 pm.hide(true);\r
591             }else{\r
592                 pm.deactivateActive();\r
593             }\r
594         }\r
595     },\r
596 \r
597     // private\r
598     lookupComponent : function(c){\r
599          if(Ext.isString(c)){\r
600             c = (c == 'separator' || c == '-') ? new Ext.menu.Separator() : new Ext.menu.TextItem(c);\r
601              this.applyDefaults(c);\r
602          }else{\r
603             if(Ext.isObject(c)){\r
604                 c = this.getMenuItem(c);\r
605             }else if(c.tagName || c.el){ // element. Wrap it.\r
606                 c = new Ext.BoxComponent({\r
607                     el: c\r
608                 });\r
609             }\r
610          }\r
611          return c;\r
612     },\r
613 \r
614     applyDefaults : function(c){\r
615         if(!Ext.isString(c)){\r
616             c = Ext.menu.Menu.superclass.applyDefaults.call(this, c);\r
617             var d = this.internalDefaults;\r
618             if(d){\r
619                 if(c.events){\r
620                     Ext.applyIf(c.initialConfig, d);\r
621                     Ext.apply(c, d);\r
622                 }else{\r
623                     Ext.applyIf(c, d);\r
624                 }\r
625             }\r
626         }\r
627         return c;\r
628     },\r
629 \r
630     // private\r
631     getMenuItem : function(config){\r
632        if(!config.isXType){\r
633             if(!config.xtype && Ext.isBoolean(config.checked)){\r
634                 return new Ext.menu.CheckItem(config)\r
635             }\r
636             return Ext.create(config, this.defaultType);\r
637         }\r
638         return config;\r
639     },\r
640 \r
641     /**\r
642      * Adds a separator bar to the menu\r
643      * @return {Ext.menu.Item} The menu item that was added\r
644      */\r
645     addSeparator : function(){\r
646         return this.add(new Ext.menu.Separator());\r
647     },\r
648 \r
649     /**\r
650      * Adds an {@link Ext.Element} object to the menu\r
651      * @param {Mixed} el The element or DOM node to add, or its id\r
652      * @return {Ext.menu.Item} The menu item that was added\r
653      */\r
654     addElement : function(el){\r
655         return this.add(new Ext.menu.BaseItem(el));\r
656     },\r
657 \r
658     /**\r
659      * Adds an existing object based on {@link Ext.menu.BaseItem} to the menu\r
660      * @param {Ext.menu.Item} item The menu item to add\r
661      * @return {Ext.menu.Item} The menu item that was added\r
662      */\r
663     addItem : function(item){\r
664         return this.add(item);\r
665     },\r
666 \r
667     /**\r
668      * Creates a new {@link Ext.menu.Item} based an the supplied config object and adds it to the menu\r
669      * @param {Object} config A MenuItem config object\r
670      * @return {Ext.menu.Item} The menu item that was added\r
671      */\r
672     addMenuItem : function(config){\r
673         return this.add(this.getMenuItem(config));\r
674     },\r
675 \r
676     /**\r
677      * Creates a new {@link Ext.menu.TextItem} with the supplied text and adds it to the menu\r
678      * @param {String} text The text to display in the menu item\r
679      * @return {Ext.menu.Item} The menu item that was added\r
680      */\r
681     addText : function(text){\r
682         return this.add(new Ext.menu.TextItem(text));\r
683     },\r
684 \r
685     //private\r
686     onDestroy : function(){\r
687         Ext.menu.Menu.superclass.onDestroy.call(this);\r
688         Ext.menu.MenuMgr.unregister(this);\r
689         Ext.EventManager.removeResizeListener(this.hide, this);\r
690         if(this.keyNav) {\r
691             this.keyNav.disable();\r
692         }\r
693         var s = this.scroller;\r
694         if(s){\r
695             Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom);\r
696         }\r
697         Ext.destroy(\r
698             this.el,\r
699             this.focusEl,\r
700             this.ul\r
701         );\r
702     }\r
703 });\r
704 \r
705 Ext.reg('menu', Ext.menu.Menu);\r
706 \r
707 // MenuNav is a private utility class used internally by the Menu\r
708 Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){\r
709     function up(e, m){\r
710         if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){\r
711             m.tryActivate(m.items.length-1, -1);\r
712         }\r
713     }\r
714     function down(e, m){\r
715         if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){\r
716             m.tryActivate(0, 1);\r
717         }\r
718     }\r
719     return {\r
720         constructor : function(menu){\r
721             Ext.menu.MenuNav.superclass.constructor.call(this, menu.el);\r
722             this.scope = this.menu = menu;\r
723         },\r
724 \r
725         doRelay : function(e, h){\r
726             var k = e.getKey();\r
727 //          Keystrokes within a form Field (e.g.: down in a Combo) do not navigate. Allow only TAB\r
728             if (this.menu.activeItem && this.menu.activeItem.isFormField && k != e.TAB) {\r
729                 return false;\r
730             }\r
731             if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){\r
732                 this.menu.tryActivate(0, 1);\r
733                 return false;\r
734             }\r
735             return h.call(this.scope || this, e, this.menu);\r
736         },\r
737 \r
738         tab: function(e, m) {\r
739             e.stopEvent();\r
740             if (e.shiftKey) {\r
741                 up(e, m);\r
742             } else {\r
743                 down(e, m);\r
744             }\r
745         },\r
746 \r
747         up : up,\r
748 \r
749         down : down,\r
750 \r
751         right : function(e, m){\r
752             if(m.activeItem){\r
753                 m.activeItem.expandMenu(true);\r
754             }\r
755         },\r
756 \r
757         left : function(e, m){\r
758             m.hide();\r
759             if(m.parentMenu && m.parentMenu.activeItem){\r
760                 m.parentMenu.activeItem.activate();\r
761             }\r
762         },\r
763 \r
764         enter : function(e, m){\r
765             if(m.activeItem){\r
766                 e.stopPropagation();\r
767                 m.activeItem.onClick(e);\r
768                 m.fireEvent('click', this, m.activeItem);\r
769                 return true;\r
770             }\r
771         }\r
772     };\r
773 }());\r