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