Upgrade to ExtJS 3.1.0 - Released 12/16/2009
[extjs.git] / pkgs / pkg-menu-debug.js
1 /*!
2  * Ext JS Library 3.1.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.layout.MenuLayout
9  * @extends Ext.layout.ContainerLayout
10  * <p>Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.</p>
11  */
12  Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, {
13     monitorResize : true,
14
15     setContainer : function(ct){
16         this.monitorResize = !ct.floating;
17         // This event is only fired by the menu in IE, used so we don't couple
18         // the menu with the layout.
19         ct.on('autosize', this.doAutoSize, this);
20         Ext.layout.MenuLayout.superclass.setContainer.call(this, ct);
21     },
22
23     renderItem : function(c, position, target){
24         if (!this.itemTpl) {
25             this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate(
26                 '<li id="{itemId}" class="{itemCls}">',
27                     '<tpl if="needsIcon">',
28                         '<img src="{icon}" class="{iconCls}"/>',
29                     '</tpl>',
30                 '</li>'
31             );
32         }
33
34         if(c && !c.rendered){
35             if(Ext.isNumber(position)){
36                 position = target.dom.childNodes[position];
37             }
38             var a = this.getItemArgs(c);
39
40 //          The Component's positionEl is the <li> it is rendered into
41             c.render(c.positionEl = position ?
42                 this.itemTpl.insertBefore(position, a, true) :
43                 this.itemTpl.append(target, a, true));
44
45 //          Link the containing <li> to the item.
46             c.positionEl.menuItemId = c.getItemId();
47
48 //          If rendering a regular Component, and it needs an icon,
49 //          move the Component rightwards.
50             if (!a.isMenuItem && a.needsIcon) {
51                 c.positionEl.addClass('x-menu-list-item-indent');
52             }
53             this.configureItem(c, position);
54         }else if(c && !this.isValidParent(c, target)){
55             if(Ext.isNumber(position)){
56                 position = target.dom.childNodes[position];
57             }
58             target.dom.insertBefore(c.getActionEl().dom, position || null);
59         }
60     },
61
62     getItemArgs : function(c) {
63         var isMenuItem = c instanceof Ext.menu.Item;
64         return {
65             isMenuItem: isMenuItem,
66             needsIcon: !isMenuItem && (c.icon || c.iconCls),
67             icon: c.icon || Ext.BLANK_IMAGE_URL,
68             iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),
69             itemId: 'x-menu-el-' + c.id,
70             itemCls: 'x-menu-list-item '
71         };
72     },
73
74 //  Valid if the Component is in a <li> which is part of our target <ul>
75     isValidParent : function(c, target) {
76         return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target);
77     },
78
79     onLayout : function(ct, target){
80         this.renderAll(ct, target);
81         this.doAutoSize();
82     },
83
84     doAutoSize : function(){
85         var ct = this.container, w = ct.width;
86         if(ct.floating){
87             if(w){
88                 ct.setWidth(w);
89             }else if(Ext.isIE){
90                 ct.setWidth(Ext.isStrict && (Ext.isIE7 || Ext.isIE8) ? 'auto' : ct.minWidth);
91                 var el = ct.getEl(), t = el.dom.offsetWidth; // force recalc
92                 ct.setWidth(ct.getLayoutTarget().getWidth() + el.getFrameWidth('lr'));
93             }
94         }
95     }
96 });
97 Ext.Container.LAYOUTS['menu'] = Ext.layout.MenuLayout;
98
99 /**
100  * @class Ext.menu.Menu
101  * @extends Ext.Container
102  * <p>A menu object.  This is the container to which you may add menu items.  Menu can also serve as a base class
103  * when you want a specialized menu based off of another component (like {@link Ext.menu.DateMenu} for example).</p>
104  * <p>Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Component}s.</p>
105  * <p>To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items}
106  * specify <tt>iconCls: 'no-icon'</tt>.  This reserves a space for an icon, and indents the Component in line
107  * with the other menu items.  See {@link Ext.form.ComboBox}.{@link Ext.form.ComboBox#getListParent getListParent}
108  * for an example.</p>
109  * <p>By default, Menus are absolutely positioned, floating Components. By configuring a Menu with
110  * <b><tt>{@link #floating}:false</tt></b>, a Menu may be used as child of a Container.</p>
111  *
112  * @xtype menu
113  */
114 Ext.menu.Menu = Ext.extend(Ext.Container, {
115     /**
116      * @cfg {Object} defaults
117      * A config object that will be applied to all items added to this container either via the {@link #items}
118      * config or via the {@link #add} method.  The defaults config can contain any number of
119      * name/value property pairs to be added to each item, and should be valid for the types of items
120      * being added to the menu.
121      */
122     /**
123      * @cfg {Mixed} items
124      * An array of items to be added to this menu. Menus may contain either {@link Ext.menu.Item menu items},
125      * or general {@link Ext.Component Component}s.
126      */
127     /**
128      * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)
129      */
130     minWidth : 120,
131     /**
132      * @cfg {Boolean/String} shadow True or 'sides' for the default effect, 'frame' for 4-way shadow, and 'drop'
133      * for bottom-right shadow (defaults to 'sides')
134      */
135     shadow : 'sides',
136     /**
137      * @cfg {String} subMenuAlign The {@link Ext.Element#alignTo} anchor position value to use for submenus of
138      * this menu (defaults to 'tl-tr?')
139      */
140     subMenuAlign : 'tl-tr?',
141     /**
142      * @cfg {String} defaultAlign The default {@link Ext.Element#alignTo} anchor position value for this menu
143      * relative to its element of origin (defaults to 'tl-bl?')
144      */
145     defaultAlign : 'tl-bl?',
146     /**
147      * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)
148      */
149     allowOtherMenus : false,
150     /**
151      * @cfg {Boolean} ignoreParentClicks True to ignore clicks on any item in this menu that is a parent item (displays
152      * a submenu) so that the submenu is not dismissed when clicking the parent item (defaults to false).
153      */
154     ignoreParentClicks : false,
155     /**
156      * @cfg {Boolean} enableScrolling True to allow the menu container to have scroller controls if the menu is too long (defaults to true).
157      */
158     enableScrolling : true,
159     /**
160      * @cfg {Number} maxHeight The maximum height of the menu. Only applies when enableScrolling is set to True (defaults to null).
161      */
162     maxHeight : null,
163     /**
164      * @cfg {Number} scrollIncrement The amount to scroll the menu. Only applies when enableScrolling is set to True (defaults to 24).
165      */
166     scrollIncrement : 24,
167     /**
168      * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).
169      */
170     showSeparator : true,
171     /**
172      * @cfg {Array} defaultOffsets An array specifying the [x, y] offset in pixels by which to
173      * change the default Menu popup position after aligning according to the {@link #defaultAlign}
174      * configuration. Defaults to <tt>[0, 0]</tt>.
175      */
176     defaultOffsets : [0, 0],
177
178     /**
179      * @cfg {Boolean} plain
180      * True to remove the incised line down the left side of the menu. Defaults to <tt>false</tt>.
181      */
182     plain : false,
183
184     /**
185      * @cfg {Boolean} floating
186      * <p>By default, a Menu configured as <b><code>floating:true</code></b>
187      * will be rendered as an {@link Ext.Layer} (an absolutely positioned,
188      * floating Component with zindex=15000).
189      * If configured as <b><code>floating:false</code></b>, the Menu may be
190      * used as child item of another Container instead of a free-floating
191      * {@link Ext.Layer Layer}.
192      */
193     floating : true,
194
195     // private
196     hidden : true,
197
198     /**
199      * @cfg {String/Object} layout
200      * This class assigns a default layout (<code>layout:'<b>menu</b>'</code>).
201      * Developers <i>may</i> override this configuration option if another layout is required.
202      * See {@link Ext.Container#layout} for additional information.
203      */
204     layout : 'menu',
205     hideMode : 'offsets',    // Important for laying out Components
206     scrollerHeight : 8,
207     autoLayout : true,       // Provided for backwards compat
208     defaultType : 'menuitem',
209     bufferResize : false,
210
211     initComponent : function(){
212         if(Ext.isArray(this.initialConfig)){
213             Ext.apply(this, {items:this.initialConfig});
214         }
215         this.addEvents(
216             /**
217              * @event click
218              * Fires when this menu is clicked (or when the enter key is pressed while it is active)
219              * @param {Ext.menu.Menu} this
220             * @param {Ext.menu.Item} menuItem The menu item that was clicked
221              * @param {Ext.EventObject} e
222              */
223             'click',
224             /**
225              * @event mouseover
226              * Fires when the mouse is hovering over this menu
227              * @param {Ext.menu.Menu} this
228              * @param {Ext.EventObject} e
229              * @param {Ext.menu.Item} menuItem The menu item that was clicked
230              */
231             'mouseover',
232             /**
233              * @event mouseout
234              * Fires when the mouse exits this menu
235              * @param {Ext.menu.Menu} this
236              * @param {Ext.EventObject} e
237              * @param {Ext.menu.Item} menuItem The menu item that was clicked
238              */
239             'mouseout',
240             /**
241              * @event itemclick
242              * Fires when a menu item contained in this menu is clicked
243              * @param {Ext.menu.BaseItem} baseItem The BaseItem that was clicked
244              * @param {Ext.EventObject} e
245              */
246             'itemclick'
247         );
248         Ext.menu.MenuMgr.register(this);
249         if(this.floating){
250             Ext.EventManager.onWindowResize(this.hide, this);
251         }else{
252             if(this.initialConfig.hidden !== false){
253                 this.hidden = false;
254             }
255             this.internalDefaults = {hideOnClick: false};
256         }
257         Ext.menu.Menu.superclass.initComponent.call(this);
258         if(this.autoLayout){
259             this.on({
260                 add: this.doLayout,
261                 remove: this.doLayout,
262                 scope: this
263             });
264         }
265     },
266
267     //private
268     getLayoutTarget : function() {
269         return this.ul;
270     },
271
272     // private
273     onRender : function(ct, position){
274         if(!ct){
275             ct = Ext.getBody();
276         }
277
278         var dh = {
279             id: this.getId(),
280             cls: 'x-menu ' + ((this.floating) ? 'x-menu-floating x-layer ' : '') + (this.cls || '') + (this.plain ? ' x-menu-plain' : '') + (this.showSeparator ? '' : ' x-menu-nosep'),
281             style: this.style,
282             cn: [
283                 {tag: 'a', cls: 'x-menu-focus', href: '#', onclick: 'return false;', tabIndex: '-1'},
284                 {tag: 'ul', cls: 'x-menu-list'}
285             ]
286         };
287         if(this.floating){
288             this.el = new Ext.Layer({
289                 shadow: this.shadow,
290                 dh: dh,
291                 constrain: false,
292                 parentEl: ct,
293                 zindex:15000
294             });
295         }else{
296             this.el = ct.createChild(dh);
297         }
298         Ext.menu.Menu.superclass.onRender.call(this, ct, position);
299
300         if(!this.keyNav){
301             this.keyNav = new Ext.menu.MenuNav(this);
302         }
303         // generic focus element
304         this.focusEl = this.el.child('a.x-menu-focus');
305         this.ul = this.el.child('ul.x-menu-list');
306         this.mon(this.ul, {
307             scope: this,
308             click: this.onClick,
309             mouseover: this.onMouseOver,
310             mouseout: this.onMouseOut
311         });
312         if(this.enableScrolling){
313             this.mon(this.el, {
314                 scope: this,
315                 delegate: '.x-menu-scroller',
316                 click: this.onScroll,
317                 mouseover: this.deactivateActive
318             });
319         }
320     },
321
322     // private
323     findTargetItem : function(e){
324         var t = e.getTarget('.x-menu-list-item', this.ul, true);
325         if(t && t.menuItemId){
326             return this.items.get(t.menuItemId);
327         }
328     },
329
330     // private
331     onClick : function(e){
332         var t = this.findTargetItem(e);
333         if(t){
334             if(t.isFormField){
335                 this.setActiveItem(t);
336             }else if(t instanceof Ext.menu.BaseItem){
337                 if(t.menu && this.ignoreParentClicks){
338                     t.expandMenu();
339                     e.preventDefault();
340                 }else if(t.onClick){
341                     t.onClick(e);
342                     this.fireEvent('click', this, t, e);
343                 }
344             }
345         }
346     },
347
348     // private
349     setActiveItem : function(item, autoExpand){
350         if(item != this.activeItem){
351             this.deactivateActive();
352             if((this.activeItem = item).isFormField){
353                 item.focus();
354             }else{
355                 item.activate(autoExpand);
356             }
357         }else if(autoExpand){
358             item.expandMenu();
359         }
360     },
361
362     deactivateActive : function(){
363         var a = this.activeItem;
364         if(a){
365             if(a.isFormField){
366                 //Fields cannot deactivate, but Combos must collapse
367                 if(a.collapse){
368                     a.collapse();
369                 }
370             }else{
371                 a.deactivate();
372             }
373             delete this.activeItem;
374         }
375     },
376
377     // private
378     tryActivate : function(start, step){
379         var items = this.items;
380         for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
381             var item = items.get(i);
382             if(!item.disabled && (item.canActivate || item.isFormField)){
383                 this.setActiveItem(item, false);
384                 return item;
385             }
386         }
387         return false;
388     },
389
390     // private
391     onMouseOver : function(e){
392         var t = this.findTargetItem(e);
393         if(t){
394             if(t.canActivate && !t.disabled){
395                 this.setActiveItem(t, true);
396             }
397         }
398         this.over = true;
399         this.fireEvent('mouseover', this, e, t);
400     },
401
402     // private
403     onMouseOut : function(e){
404         var t = this.findTargetItem(e);
405         if(t){
406             if(t == this.activeItem && t.shouldDeactivate && t.shouldDeactivate(e)){
407                 this.activeItem.deactivate();
408                 delete this.activeItem;
409             }
410         }
411         this.over = false;
412         this.fireEvent('mouseout', this, e, t);
413     },
414
415     // private
416     onScroll : function(e, t){
417         if(e){
418             e.stopEvent();
419         }
420         var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');
421         ul.scrollTop += this.scrollIncrement * (top ? -1 : 1);
422         if(top ? ul.scrollTop <= 0 : ul.scrollTop + this.activeMax >= ul.scrollHeight){
423            this.onScrollerOut(null, t);
424         }
425     },
426
427     // private
428     onScrollerIn : function(e, t){
429         var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');
430         if(top ? ul.scrollTop > 0 : ul.scrollTop + this.activeMax < ul.scrollHeight){
431             Ext.fly(t).addClass(['x-menu-item-active', 'x-menu-scroller-active']);
432         }
433     },
434
435     // private
436     onScrollerOut : function(e, t){
437         Ext.fly(t).removeClass(['x-menu-item-active', 'x-menu-scroller-active']);
438     },
439
440     /**
441      * If <code>{@link #floating}=true</code>, shows this menu relative to
442      * another element using {@link #showat}, otherwise uses {@link Ext.Component#show}.
443      * @param {Mixed} element The element to align to
444      * @param {String} position (optional) The {@link Ext.Element#alignTo} anchor position to use in aligning to
445      * the element (defaults to this.defaultAlign)
446      * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
447      */
448     show : function(el, pos, parentMenu){
449         if(this.floating){
450             this.parentMenu = parentMenu;
451             if(!this.el){
452                 this.render();
453                 this.doLayout(false, true);
454             }
455             this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu);
456         }else{
457             Ext.menu.Menu.superclass.show.call(this);
458         }
459     },
460
461     /**
462      * Displays this menu at a specific xy position and fires the 'show' event if a
463      * handler for the 'beforeshow' event does not return false cancelling the operation.
464      * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
465      * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
466      */
467     showAt : function(xy, parentMenu){
468         if(this.fireEvent('beforeshow', this) !== false){
469             this.parentMenu = parentMenu;
470             if(!this.el){
471                 this.render();
472             }
473             if(this.enableScrolling){
474                 // set the position so we can figure out the constrain value.
475                 this.el.setXY(xy);
476                 //constrain the value, keep the y coordinate the same
477                 this.constrainScroll(xy[1]);
478                 xy = [this.el.adjustForConstraints(xy)[0], xy[1]];
479             }else{
480                 //constrain to the viewport.
481                 xy = this.el.adjustForConstraints(xy);
482             }
483             this.el.setXY(xy);
484             this.el.show();
485             Ext.menu.Menu.superclass.onShow.call(this);
486             if(Ext.isIE){
487                 // internal event, used so we don't couple the layout to the menu
488                 this.fireEvent('autosize', this);
489                 if(!Ext.isIE8){
490                     this.el.repaint();
491                 }
492             }
493             this.hidden = false;
494             this.focus();
495             this.fireEvent('show', this);
496         }
497     },
498
499     constrainScroll : function(y){
500         var max, full = this.ul.setHeight('auto').getHeight();
501         if(this.floating){
502             max = this.maxHeight ? this.maxHeight : Ext.fly(this.el.dom.parentNode).getViewSize(false).height - y;
503         }else{
504             max = this.getHeight();
505         }
506         if(full > max && max > 0){
507             this.activeMax = max - this.scrollerHeight * 2 - this.el.getFrameWidth('tb') - Ext.num(this.el.shadowOffset, 0);
508             this.ul.setHeight(this.activeMax);
509             this.createScrollers();
510             this.el.select('.x-menu-scroller').setDisplayed('');
511         }else{
512             this.ul.setHeight(full);
513             this.el.select('.x-menu-scroller').setDisplayed('none');
514         }
515         this.ul.dom.scrollTop = 0;
516     },
517
518     createScrollers : function(){
519         if(!this.scroller){
520             this.scroller = {
521                 pos: 0,
522                 top: this.el.insertFirst({
523                     tag: 'div',
524                     cls: 'x-menu-scroller x-menu-scroller-top',
525                     html: '&#160;'
526                 }),
527                 bottom: this.el.createChild({
528                     tag: 'div',
529                     cls: 'x-menu-scroller x-menu-scroller-bottom',
530                     html: '&#160;'
531                 })
532             };
533             this.scroller.top.hover(this.onScrollerIn, this.onScrollerOut, this);
534             this.scroller.topRepeater = new Ext.util.ClickRepeater(this.scroller.top, {
535                 listeners: {
536                     click: this.onScroll.createDelegate(this, [null, this.scroller.top], false)
537                 }
538             });
539             this.scroller.bottom.hover(this.onScrollerIn, this.onScrollerOut, this);
540             this.scroller.bottomRepeater = new Ext.util.ClickRepeater(this.scroller.bottom, {
541                 listeners: {
542                     click: this.onScroll.createDelegate(this, [null, this.scroller.bottom], false)
543                 }
544             });
545         }
546     },
547
548     onLayout : function(){
549         if(this.isVisible()){
550             if(this.enableScrolling){
551                 this.constrainScroll(this.el.getTop());
552             }
553             if(this.floating){
554                 this.el.sync();
555             }
556         }
557     },
558
559     focus : function(){
560         if(!this.hidden){
561             this.doFocus.defer(50, this);
562         }
563     },
564
565     doFocus : function(){
566         if(!this.hidden){
567             this.focusEl.focus();
568         }
569     },
570
571     /**
572      * Hides this menu and optionally all parent menus
573      * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
574      */
575     hide : function(deep){
576         this.deepHide = deep;
577         Ext.menu.Menu.superclass.hide.call(this);
578         delete this.deepHide;
579     },
580
581     // private
582     onHide : function(){
583         Ext.menu.Menu.superclass.onHide.call(this);
584         this.deactivateActive();
585         if(this.el && this.floating){
586             this.el.hide();
587         }
588         var pm = this.parentMenu;
589         if(this.deepHide === true && pm){
590             if(pm.floating){
591                 pm.hide(true);
592             }else{
593                 pm.deactivateActive();
594             }
595         }
596     },
597
598     // private
599     lookupComponent : function(c){
600          if(Ext.isString(c)){
601             c = (c == 'separator' || c == '-') ? new Ext.menu.Separator() : new Ext.menu.TextItem(c);
602              this.applyDefaults(c);
603          }else{
604             if(Ext.isObject(c)){
605                 c = this.getMenuItem(c);
606             }else if(c.tagName || c.el){ // element. Wrap it.
607                 c = new Ext.BoxComponent({
608                     el: c
609                 });
610             }
611          }
612          return c;
613     },
614
615     applyDefaults : function(c){
616         if(!Ext.isString(c)){
617             c = Ext.menu.Menu.superclass.applyDefaults.call(this, c);
618             var d = this.internalDefaults;
619             if(d){
620                 if(c.events){
621                     Ext.applyIf(c.initialConfig, d);
622                     Ext.apply(c, d);
623                 }else{
624                     Ext.applyIf(c, d);
625                 }
626             }
627         }
628         return c;
629     },
630
631     // private
632     getMenuItem : function(config){
633        if(!config.isXType){
634             if(!config.xtype && Ext.isBoolean(config.checked)){
635                 return new Ext.menu.CheckItem(config)
636             }
637             return Ext.create(config, this.defaultType);
638         }
639         return config;
640     },
641
642     /**
643      * Adds a separator bar to the menu
644      * @return {Ext.menu.Item} The menu item that was added
645      */
646     addSeparator : function(){
647         return this.add(new Ext.menu.Separator());
648     },
649
650     /**
651      * Adds an {@link Ext.Element} object to the menu
652      * @param {Mixed} el The element or DOM node to add, or its id
653      * @return {Ext.menu.Item} The menu item that was added
654      */
655     addElement : function(el){
656         return this.add(new Ext.menu.BaseItem(el));
657     },
658
659     /**
660      * Adds an existing object based on {@link Ext.menu.BaseItem} to the menu
661      * @param {Ext.menu.Item} item The menu item to add
662      * @return {Ext.menu.Item} The menu item that was added
663      */
664     addItem : function(item){
665         return this.add(item);
666     },
667
668     /**
669      * Creates a new {@link Ext.menu.Item} based an the supplied config object and adds it to the menu
670      * @param {Object} config A MenuItem config object
671      * @return {Ext.menu.Item} The menu item that was added
672      */
673     addMenuItem : function(config){
674         return this.add(this.getMenuItem(config));
675     },
676
677     /**
678      * Creates a new {@link Ext.menu.TextItem} with the supplied text and adds it to the menu
679      * @param {String} text The text to display in the menu item
680      * @return {Ext.menu.Item} The menu item that was added
681      */
682     addText : function(text){
683         return this.add(new Ext.menu.TextItem(text));
684     },
685
686     //private
687     onDestroy : function(){
688         var pm = this.parentMenu;
689         if(pm && pm.activeChild == this){
690             delete pm.activeChild;
691         }
692         delete this.parentMenu;
693         Ext.menu.Menu.superclass.onDestroy.call(this);
694         Ext.menu.MenuMgr.unregister(this);
695         Ext.EventManager.removeResizeListener(this.hide, this);
696         if(this.keyNav) {
697             this.keyNav.disable();
698         }
699         var s = this.scroller;
700         if(s){
701             Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom);
702         }
703         Ext.destroy(
704             this.el,
705             this.focusEl,
706             this.ul
707         );
708     }
709 });
710
711 Ext.reg('menu', Ext.menu.Menu);
712
713 // MenuNav is a private utility class used internally by the Menu
714 Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){
715     function up(e, m){
716         if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
717             m.tryActivate(m.items.length-1, -1);
718         }
719     }
720     function down(e, m){
721         if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
722             m.tryActivate(0, 1);
723         }
724     }
725     return {
726         constructor : function(menu){
727             Ext.menu.MenuNav.superclass.constructor.call(this, menu.el);
728             this.scope = this.menu = menu;
729         },
730
731         doRelay : function(e, h){
732             var k = e.getKey();
733 //          Keystrokes within a form Field (e.g.: down in a Combo) do not navigate. Allow only TAB
734             if (this.menu.activeItem && this.menu.activeItem.isFormField && k != e.TAB) {
735                 return false;
736             }
737             if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
738                 this.menu.tryActivate(0, 1);
739                 return false;
740             }
741             return h.call(this.scope || this, e, this.menu);
742         },
743
744         tab: function(e, m) {
745             e.stopEvent();
746             if (e.shiftKey) {
747                 up(e, m);
748             } else {
749                 down(e, m);
750             }
751         },
752
753         up : up,
754
755         down : down,
756
757         right : function(e, m){
758             if(m.activeItem){
759                 m.activeItem.expandMenu(true);
760             }
761         },
762
763         left : function(e, m){
764             m.hide();
765             if(m.parentMenu && m.parentMenu.activeItem){
766                 m.parentMenu.activeItem.activate();
767             }
768         },
769
770         enter : function(e, m){
771             if(m.activeItem){
772                 e.stopPropagation();
773                 m.activeItem.onClick(e);
774                 m.fireEvent('click', this, m.activeItem);
775                 return true;
776             }
777         }
778     };
779 }());
780 /**
781  * @class Ext.menu.MenuMgr
782  * Provides a common registry of all menu items on a page so that they can be easily accessed by id.
783  * @singleton
784  */
785 Ext.menu.MenuMgr = function(){
786    var menus, active, groups = {}, attached = false, lastShow = new Date();
787
788    // private - called when first menu is created
789    function init(){
790        menus = {};
791        active = new Ext.util.MixedCollection();
792        Ext.getDoc().addKeyListener(27, function(){
793            if(active.length > 0){
794                hideAll();
795            }
796        });
797    }
798
799    // private
800    function hideAll(){
801        if(active && active.length > 0){
802            var c = active.clone();
803            c.each(function(m){
804                m.hide();
805            });
806            return true;
807        }
808        return false;
809    }
810
811    // private
812    function onHide(m){
813        active.remove(m);
814        if(active.length < 1){
815            Ext.getDoc().un("mousedown", onMouseDown);
816            attached = false;
817        }
818    }
819
820    // private
821    function onShow(m){
822        var last = active.last();
823        lastShow = new Date();
824        active.add(m);
825        if(!attached){
826            Ext.getDoc().on("mousedown", onMouseDown);
827            attached = true;
828        }
829        if(m.parentMenu){
830           m.getEl().setZIndex(parseInt(m.parentMenu.getEl().getStyle("z-index"), 10) + 3);
831           m.parentMenu.activeChild = m;
832        }else if(last && last.isVisible()){
833           m.getEl().setZIndex(parseInt(last.getEl().getStyle("z-index"), 10) + 3);
834        }
835    }
836
837    // private
838    function onBeforeHide(m){
839        if(m.activeChild){
840            m.activeChild.hide();
841        }
842        if(m.autoHideTimer){
843            clearTimeout(m.autoHideTimer);
844            delete m.autoHideTimer;
845        }
846    }
847
848    // private
849    function onBeforeShow(m){
850        var pm = m.parentMenu;
851        if(!pm && !m.allowOtherMenus){
852            hideAll();
853        }else if(pm && pm.activeChild){
854            pm.activeChild.hide();
855        }
856    }
857
858    // private
859    function onMouseDown(e){
860        if(lastShow.getElapsed() > 50 && active.length > 0 && !e.getTarget(".x-menu")){
861            hideAll();
862        }
863    }
864
865    // private
866    function onBeforeCheck(mi, state){
867        if(state){
868            var g = groups[mi.group];
869            for(var i = 0, l = g.length; i < l; i++){
870                if(g[i] != mi){
871                    g[i].setChecked(false);
872                }
873            }
874        }
875    }
876
877    return {
878
879        /**
880         * Hides all menus that are currently visible
881         * @return {Boolean} success True if any active menus were hidden.
882         */
883        hideAll : function(){
884             return hideAll();  
885        },
886
887        // private
888        register : function(menu){
889            if(!menus){
890                init();
891            }
892            menus[menu.id] = menu;
893            menu.on({
894                beforehide: onBeforeHide,
895                hide: onHide,
896                beforeshow: onBeforeShow,
897                show: onShow
898            });
899        },
900
901         /**
902          * Returns a {@link Ext.menu.Menu} object
903          * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
904          * be used to generate and return a new Menu instance.
905          * @return {Ext.menu.Menu} The specified menu, or null if none are found
906          */
907        get : function(menu){
908            if(typeof menu == "string"){ // menu id
909                if(!menus){  // not initialized, no menus to return
910                    return null;
911                }
912                return menus[menu];
913            }else if(menu.events){  // menu instance
914                return menu;
915            }else if(typeof menu.length == 'number'){ // array of menu items?
916                return new Ext.menu.Menu({items:menu});
917            }else{ // otherwise, must be a config
918                return Ext.create(menu, 'menu');
919            }
920        },
921
922        // private
923        unregister : function(menu){
924            delete menus[menu.id];
925            menu.un("beforehide", onBeforeHide);
926            menu.un("hide", onHide);
927            menu.un("beforeshow", onBeforeShow);
928            menu.un("show", onShow);
929        },
930
931        // private
932        registerCheckable : function(menuItem){
933            var g = menuItem.group;
934            if(g){
935                if(!groups[g]){
936                    groups[g] = [];
937                }
938                groups[g].push(menuItem);
939                menuItem.on("beforecheckchange", onBeforeCheck);
940            }
941        },
942
943        // private
944        unregisterCheckable : function(menuItem){
945            var g = menuItem.group;
946            if(g){
947                groups[g].remove(menuItem);
948                menuItem.un("beforecheckchange", onBeforeCheck);
949            }
950        },
951
952        getCheckedItem : function(groupId){
953            var g = groups[groupId];
954            if(g){
955                for(var i = 0, l = g.length; i < l; i++){
956                    if(g[i].checked){
957                        return g[i];
958                    }
959                }
960            }
961            return null;
962        },
963
964        setCheckedItem : function(groupId, itemId){
965            var g = groups[groupId];
966            if(g){
967                for(var i = 0, l = g.length; i < l; i++){
968                    if(g[i].id == itemId){
969                        g[i].setChecked(true);
970                    }
971                }
972            }
973            return null;
974        }
975    };
976 }();
977 /**
978  * @class Ext.menu.BaseItem
979  * @extends Ext.Component
980  * The base class for all items that render into menus.  BaseItem provides default rendering, activated state
981  * management and base configuration options shared by all menu components.
982  * @constructor
983  * Creates a new BaseItem
984  * @param {Object} config Configuration options
985  * @xtype menubaseitem
986  */
987 Ext.menu.BaseItem = Ext.extend(Ext.Component, {
988     /**
989      * @property parentMenu
990      * @type Ext.menu.Menu
991      * The parent Menu of this Item.
992      */
993     /**
994      * @cfg {Function} handler
995      * A function that will handle the click event of this menu item (optional).
996      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
997      * <li><code>b</code> : Item<div class="sub-desc">This menu Item.</div></li>
998      * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
999      * </ul></div>
1000      */
1001     /**
1002      * @cfg {Object} scope
1003      * The scope (<tt><b>this</b></tt> reference) in which the handler function will be called.
1004      */
1005     /**
1006      * @cfg {Boolean} canActivate True if this item can be visually activated (defaults to false)
1007      */
1008     canActivate : false,
1009     /**
1010      * @cfg {String} activeClass The CSS class to use when the item becomes activated (defaults to "x-menu-item-active")
1011      */
1012     activeClass : "x-menu-item-active",
1013     /**
1014      * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to true)
1015      */
1016     hideOnClick : true,
1017     /**
1018      * @cfg {Number} clickHideDelay Length of time in milliseconds to wait before hiding after a click (defaults to 100)
1019      */
1020     clickHideDelay : 1,
1021
1022     // private
1023     ctype : "Ext.menu.BaseItem",
1024
1025     // private
1026     actionMode : "container",
1027     
1028     initComponent : function(){
1029         Ext.menu.BaseItem.superclass.initComponent.call(this);
1030         this.addEvents(
1031                 /**
1032                  * @event click
1033                  * Fires when this item is clicked
1034                  * @param {Ext.menu.BaseItem} this
1035                  * @param {Ext.EventObject} e
1036                  */
1037                 'click',
1038                 /**
1039                  * @event activate
1040                  * Fires when this item is activated
1041                  * @param {Ext.menu.BaseItem} this
1042                  */
1043                 'activate',
1044                 /**
1045                  * @event deactivate
1046                  * Fires when this item is deactivated
1047                  * @param {Ext.menu.BaseItem} this
1048                  */
1049                 'deactivate'
1050             );
1051             if(this.handler){
1052                 this.on("click", this.handler, this.scope);
1053             }
1054     },
1055
1056     // private
1057     onRender : function(container, position){
1058         Ext.menu.BaseItem.superclass.onRender.apply(this, arguments);
1059         if(this.ownerCt && this.ownerCt instanceof Ext.menu.Menu){
1060             this.parentMenu = this.ownerCt;
1061         }else{
1062             this.container.addClass('x-menu-list-item');
1063             this.mon(this.el, {
1064                 scope: this,
1065                 click: this.onClick,
1066                 mouseenter: this.activate,
1067                 mouseleave: this.deactivate
1068             });
1069         }
1070     },
1071
1072     /**
1073      * Sets the function that will handle click events for this item (equivalent to passing in the {@link #handler}
1074      * config property).  If an existing handler is already registered, it will be unregistered for you.
1075      * @param {Function} handler The function that should be called on click
1076      * @param {Object} scope The scope (<code>this</code> reference) in which the handler function is executed. Defaults to this menu item.
1077      */
1078     setHandler : function(handler, scope){
1079         if(this.handler){
1080             this.un("click", this.handler, this.scope);
1081         }
1082         this.on("click", this.handler = handler, this.scope = scope);
1083     },
1084
1085     // private
1086     onClick : function(e){
1087         if(!this.disabled && this.fireEvent("click", this, e) !== false
1088                 && (this.parentMenu && this.parentMenu.fireEvent("itemclick", this, e) !== false)){
1089             this.handleClick(e);
1090         }else{
1091             e.stopEvent();
1092         }
1093     },
1094
1095     // private
1096     activate : function(){
1097         if(this.disabled){
1098             return false;
1099         }
1100         var li = this.container;
1101         li.addClass(this.activeClass);
1102         this.region = li.getRegion().adjust(2, 2, -2, -2);
1103         this.fireEvent("activate", this);
1104         return true;
1105     },
1106
1107     // private
1108     deactivate : function(){
1109         this.container.removeClass(this.activeClass);
1110         this.fireEvent("deactivate", this);
1111     },
1112
1113     // private
1114     shouldDeactivate : function(e){
1115         return !this.region || !this.region.contains(e.getPoint());
1116     },
1117
1118     // private
1119     handleClick : function(e){
1120         var pm = this.parentMenu;
1121         if(this.hideOnClick){
1122             if(pm.floating){
1123                 pm.hide.defer(this.clickHideDelay, pm, [true]);
1124             }else{
1125                 pm.deactivateActive();
1126             }
1127         }
1128     },
1129
1130     // private. Do nothing
1131     expandMenu : Ext.emptyFn,
1132
1133     // private. Do nothing
1134     hideMenu : Ext.emptyFn
1135 });
1136 Ext.reg('menubaseitem', Ext.menu.BaseItem);/**
1137  * @class Ext.menu.TextItem
1138  * @extends Ext.menu.BaseItem
1139  * Adds a static text string to a menu, usually used as either a heading or group separator.
1140  * @constructor
1141  * Creates a new TextItem
1142  * @param {Object/String} config If config is a string, it is used as the text to display, otherwise it
1143  * is applied as a config object (and should contain a <tt>text</tt> property).
1144  * @xtype menutextitem
1145  */
1146 Ext.menu.TextItem = Ext.extend(Ext.menu.BaseItem, {
1147     /**
1148      * @cfg {String} text The text to display for this item (defaults to '')
1149      */
1150     /**
1151      * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to false)
1152      */
1153     hideOnClick : false,
1154     /**
1155      * @cfg {String} itemCls The default CSS class to use for text items (defaults to "x-menu-text")
1156      */
1157     itemCls : "x-menu-text",
1158     
1159     constructor : function(config){
1160         if(typeof config == 'string'){
1161             config = {text: config}
1162         }
1163         Ext.menu.TextItem.superclass.constructor.call(this, config);
1164     },
1165
1166     // private
1167     onRender : function(){
1168         var s = document.createElement("span");
1169         s.className = this.itemCls;
1170         s.innerHTML = this.text;
1171         this.el = s;
1172         Ext.menu.TextItem.superclass.onRender.apply(this, arguments);
1173     }
1174 });
1175 Ext.reg('menutextitem', Ext.menu.TextItem);/**
1176  * @class Ext.menu.Separator
1177  * @extends Ext.menu.BaseItem
1178  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
1179  * add one of these by using "-" in you call to add() or in your items config rather than creating one directly.
1180  * @constructor
1181  * @param {Object} config Configuration options
1182  * @xtype menuseparator
1183  */
1184 Ext.menu.Separator = Ext.extend(Ext.menu.BaseItem, {
1185     /**
1186      * @cfg {String} itemCls The default CSS class to use for separators (defaults to "x-menu-sep")
1187      */
1188     itemCls : "x-menu-sep",
1189     /**
1190      * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to false)
1191      */
1192     hideOnClick : false,
1193     
1194     /** 
1195      * @cfg {String} activeClass
1196      * @hide 
1197      */
1198     activeClass: '',
1199
1200     // private
1201     onRender : function(li){
1202         var s = document.createElement("span");
1203         s.className = this.itemCls;
1204         s.innerHTML = "&#160;";
1205         this.el = s;
1206         li.addClass("x-menu-sep-li");
1207         Ext.menu.Separator.superclass.onRender.apply(this, arguments);
1208     }
1209 });
1210 Ext.reg('menuseparator', Ext.menu.Separator);/**\r
1211  * @class Ext.menu.Item\r
1212  * @extends Ext.menu.BaseItem\r
1213  * A base class for all menu items that require menu-related functionality (like sub-menus) and are not static\r
1214  * display items.  Item extends the base functionality of {@link Ext.menu.BaseItem} by adding menu-specific\r
1215  * activation and click handling.\r
1216  * @constructor\r
1217  * Creates a new Item\r
1218  * @param {Object} config Configuration options\r
1219  * @xtype menuitem\r
1220  */\r
1221 Ext.menu.Item = Ext.extend(Ext.menu.BaseItem, {\r
1222     /**\r
1223      * @property menu\r
1224      * @type Ext.menu.Menu\r
1225      * The submenu associated with this Item if one was configured.\r
1226      */\r
1227     /**\r
1228      * @cfg {Mixed} menu (optional) Either an instance of {@link Ext.menu.Menu} or the config object for an\r
1229      * {@link Ext.menu.Menu} which acts as the submenu when this item is activated.\r
1230      */\r
1231     /**\r
1232      * @cfg {String} icon The path to an icon to display in this item (defaults to Ext.BLANK_IMAGE_URL).  If\r
1233      * icon is specified {@link #iconCls} should not be.\r
1234      */\r
1235     /**\r
1236      * @cfg {String} iconCls A CSS class that specifies a background image that will be used as the icon for\r
1237      * this item (defaults to '').  If iconCls is specified {@link #icon} should not be.\r
1238      */\r
1239     /**\r
1240      * @cfg {String} text The text to display in this item (defaults to '').\r
1241      */\r
1242     /**\r
1243      * @cfg {String} href The href attribute to use for the underlying anchor link (defaults to '#').\r
1244      */\r
1245     /**\r
1246      * @cfg {String} hrefTarget The target attribute to use for the underlying anchor link (defaults to '').\r
1247      */\r
1248     /**\r
1249      * @cfg {String} itemCls The default CSS class to use for menu items (defaults to 'x-menu-item')\r
1250      */\r
1251     itemCls : 'x-menu-item',\r
1252     /**\r
1253      * @cfg {Boolean} canActivate True if this item can be visually activated (defaults to true)\r
1254      */\r
1255     canActivate : true,\r
1256     /**\r
1257      * @cfg {Number} showDelay Length of time in milliseconds to wait before showing this item (defaults to 200)\r
1258      */\r
1259     showDelay: 200,\r
1260     // doc'd in BaseItem\r
1261     hideDelay: 200,\r
1262 \r
1263     // private\r
1264     ctype: 'Ext.menu.Item',\r
1265 \r
1266     initComponent : function(){\r
1267         Ext.menu.Item.superclass.initComponent.call(this);\r
1268         if(this.menu){\r
1269             this.menu = Ext.menu.MenuMgr.get(this.menu);\r
1270             this.menu.ownerCt = this;\r
1271         }\r
1272     },\r
1273 \r
1274     // private\r
1275     onRender : function(container, position){\r
1276         if (!this.itemTpl) {\r
1277             this.itemTpl = Ext.menu.Item.prototype.itemTpl = new Ext.XTemplate(\r
1278                 '<a id="{id}" class="{cls}" hidefocus="true" unselectable="on" href="{href}"',\r
1279                     '<tpl if="hrefTarget">',\r
1280                         ' target="{hrefTarget}"',\r
1281                     '</tpl>',\r
1282                  '>',\r
1283                      '<img src="{icon}" class="x-menu-item-icon {iconCls}"/>',\r
1284                      '<span class="x-menu-item-text">{text}</span>',\r
1285                  '</a>'\r
1286              );\r
1287         }\r
1288         var a = this.getTemplateArgs();\r
1289         this.el = position ? this.itemTpl.insertBefore(position, a, true) : this.itemTpl.append(container, a, true);\r
1290         this.iconEl = this.el.child('img.x-menu-item-icon');\r
1291         this.textEl = this.el.child('.x-menu-item-text');\r
1292         if(!this.href) { // if no link defined, prevent the default anchor event\r
1293             this.mon(this.el, 'click', Ext.emptyFn, null, { preventDefault: true });\r
1294         }\r
1295         Ext.menu.Item.superclass.onRender.call(this, container, position);\r
1296     },\r
1297 \r
1298     getTemplateArgs: function() {\r
1299         return {\r
1300             id: this.id,\r
1301             cls: this.itemCls + (this.menu ?  ' x-menu-item-arrow' : '') + (this.cls ?  ' ' + this.cls : ''),\r
1302             href: this.href || '#',\r
1303             hrefTarget: this.hrefTarget,\r
1304             icon: this.icon || Ext.BLANK_IMAGE_URL,\r
1305             iconCls: this.iconCls || '',\r
1306             text: this.itemText||this.text||'&#160;'\r
1307         };\r
1308     },\r
1309 \r
1310     /**\r
1311      * Sets the text to display in this menu item\r
1312      * @param {String} text The text to display\r
1313      */\r
1314     setText : function(text){\r
1315         this.text = text||'&#160;';\r
1316         if(this.rendered){\r
1317             this.textEl.update(this.text);\r
1318             this.parentMenu.layout.doAutoSize();\r
1319         }\r
1320     },\r
1321 \r
1322     /**\r
1323      * Sets the CSS class to apply to the item's icon element\r
1324      * @param {String} cls The CSS class to apply\r
1325      */\r
1326     setIconClass : function(cls){\r
1327         var oldCls = this.iconCls;\r
1328         this.iconCls = cls;\r
1329         if(this.rendered){\r
1330             this.iconEl.replaceClass(oldCls, this.iconCls);\r
1331         }\r
1332     },\r
1333 \r
1334     //private\r
1335     beforeDestroy: function(){\r
1336         if (this.menu){\r
1337             delete this.menu.ownerCt;\r
1338             this.menu.destroy();\r
1339         }\r
1340         Ext.menu.Item.superclass.beforeDestroy.call(this);\r
1341     },\r
1342 \r
1343     // private\r
1344     handleClick : function(e){\r
1345         if(!this.href){ // if no link defined, stop the event automatically\r
1346             e.stopEvent();\r
1347         }\r
1348         Ext.menu.Item.superclass.handleClick.apply(this, arguments);\r
1349     },\r
1350 \r
1351     // private\r
1352     activate : function(autoExpand){\r
1353         if(Ext.menu.Item.superclass.activate.apply(this, arguments)){\r
1354             this.focus();\r
1355             if(autoExpand){\r
1356                 this.expandMenu();\r
1357             }\r
1358         }\r
1359         return true;\r
1360     },\r
1361 \r
1362     // private\r
1363     shouldDeactivate : function(e){\r
1364         if(Ext.menu.Item.superclass.shouldDeactivate.call(this, e)){\r
1365             if(this.menu && this.menu.isVisible()){\r
1366                 return !this.menu.getEl().getRegion().contains(e.getPoint());\r
1367             }\r
1368             return true;\r
1369         }\r
1370         return false;\r
1371     },\r
1372 \r
1373     // private\r
1374     deactivate : function(){\r
1375         Ext.menu.Item.superclass.deactivate.apply(this, arguments);\r
1376         this.hideMenu();\r
1377     },\r
1378 \r
1379     // private\r
1380     expandMenu : function(autoActivate){\r
1381         if(!this.disabled && this.menu){\r
1382             clearTimeout(this.hideTimer);\r
1383             delete this.hideTimer;\r
1384             if(!this.menu.isVisible() && !this.showTimer){\r
1385                 this.showTimer = this.deferExpand.defer(this.showDelay, this, [autoActivate]);\r
1386             }else if (this.menu.isVisible() && autoActivate){\r
1387                 this.menu.tryActivate(0, 1);\r
1388             }\r
1389         }\r
1390     },\r
1391 \r
1392     // private\r
1393     deferExpand : function(autoActivate){\r
1394         delete this.showTimer;\r
1395         this.menu.show(this.container, this.parentMenu.subMenuAlign || 'tl-tr?', this.parentMenu);\r
1396         if(autoActivate){\r
1397             this.menu.tryActivate(0, 1);\r
1398         }\r
1399     },\r
1400 \r
1401     // private\r
1402     hideMenu : function(){\r
1403         clearTimeout(this.showTimer);\r
1404         delete this.showTimer;\r
1405         if(!this.hideTimer && this.menu && this.menu.isVisible()){\r
1406             this.hideTimer = this.deferHide.defer(this.hideDelay, this);\r
1407         }\r
1408     },\r
1409 \r
1410     // private\r
1411     deferHide : function(){\r
1412         delete this.hideTimer;\r
1413         if(this.menu.over){\r
1414             this.parentMenu.setActiveItem(this, false);\r
1415         }else{\r
1416             this.menu.hide();\r
1417         }\r
1418     }\r
1419 });\r
1420 Ext.reg('menuitem', Ext.menu.Item);/**
1421  * @class Ext.menu.CheckItem
1422  * @extends Ext.menu.Item
1423  * Adds a menu item that contains a checkbox by default, but can also be part of a radio group.
1424  * @constructor
1425  * Creates a new CheckItem
1426  * @param {Object} config Configuration options
1427  * @xtype menucheckitem
1428  */
1429 Ext.menu.CheckItem = Ext.extend(Ext.menu.Item, {
1430     /**
1431      * @cfg {String} group
1432      * All check items with the same group name will automatically be grouped into a single-select
1433      * radio button group (defaults to '')
1434      */
1435     /**
1436      * @cfg {String} itemCls The default CSS class to use for check items (defaults to "x-menu-item x-menu-check-item")
1437      */
1438     itemCls : "x-menu-item x-menu-check-item",
1439     /**
1440      * @cfg {String} groupClass The default CSS class to use for radio group check items (defaults to "x-menu-group-item")
1441      */
1442     groupClass : "x-menu-group-item",
1443
1444     /**
1445      * @cfg {Boolean} checked True to initialize this checkbox as checked (defaults to false).  Note that
1446      * if this checkbox is part of a radio group (group = true) only the last item in the group that is
1447      * initialized with checked = true will be rendered as checked.
1448      */
1449     checked: false,
1450
1451     // private
1452     ctype: "Ext.menu.CheckItem",
1453     
1454     initComponent : function(){
1455         Ext.menu.CheckItem.superclass.initComponent.call(this);
1456             this.addEvents(
1457                 /**
1458                  * @event beforecheckchange
1459                  * Fires before the checked value is set, providing an opportunity to cancel if needed
1460                  * @param {Ext.menu.CheckItem} this
1461                  * @param {Boolean} checked The new checked value that will be set
1462                  */
1463                 "beforecheckchange" ,
1464                 /**
1465                  * @event checkchange
1466                  * Fires after the checked value has been set
1467                  * @param {Ext.menu.CheckItem} this
1468                  * @param {Boolean} checked The checked value that was set
1469                  */
1470                 "checkchange"
1471             );
1472             /**
1473              * A function that handles the checkchange event.  The function is undefined by default, but if an implementation
1474              * is provided, it will be called automatically when the checkchange event fires.
1475              * @param {Ext.menu.CheckItem} this
1476              * @param {Boolean} checked The checked value that was set
1477              * @method checkHandler
1478              */
1479             if(this.checkHandler){
1480                 this.on('checkchange', this.checkHandler, this.scope);
1481             }
1482             Ext.menu.MenuMgr.registerCheckable(this);
1483     },
1484
1485     // private
1486     onRender : function(c){
1487         Ext.menu.CheckItem.superclass.onRender.apply(this, arguments);
1488         if(this.group){
1489             this.el.addClass(this.groupClass);
1490         }
1491         if(this.checked){
1492             this.checked = false;
1493             this.setChecked(true, true);
1494         }
1495     },
1496
1497     // private
1498     destroy : function(){
1499         Ext.menu.MenuMgr.unregisterCheckable(this);
1500         Ext.menu.CheckItem.superclass.destroy.apply(this, arguments);
1501     },
1502
1503     /**
1504      * Set the checked state of this item
1505      * @param {Boolean} checked The new checked value
1506      * @param {Boolean} suppressEvent (optional) True to prevent the checkchange event from firing (defaults to false)
1507      */
1508     setChecked : function(state, suppressEvent){
1509         if(this.checked != state && this.fireEvent("beforecheckchange", this, state) !== false){
1510             if(this.container){
1511                 this.container[state ? "addClass" : "removeClass"]("x-menu-item-checked");
1512             }
1513             this.checked = state;
1514             if(suppressEvent !== true){
1515                 this.fireEvent("checkchange", this, state);
1516             }
1517         }
1518     },
1519
1520     // private
1521     handleClick : function(e){
1522        if(!this.disabled && !(this.checked && this.group)){// disable unselect on radio item
1523            this.setChecked(!this.checked);
1524        }
1525        Ext.menu.CheckItem.superclass.handleClick.apply(this, arguments);
1526     }
1527 });
1528 Ext.reg('menucheckitem', Ext.menu.CheckItem);/**\r
1529  * @class Ext.menu.DateMenu\r
1530  * @extends Ext.menu.Menu\r
1531  * <p>A menu containing an {@link Ext.DatePicker} Component.</p>\r
1532  * <p>Notes:</p><div class="mdetail-params"><ul>\r
1533  * <li>Although not listed here, the <b>constructor</b> for this class\r
1534  * accepts all of the configuration options of <b>{@link Ext.DatePicker}</b>.</li>\r
1535  * <li>If subclassing DateMenu, any configuration options for the DatePicker must be\r
1536  * applied to the <tt><b>initialConfig</b></tt> property of the DateMenu.\r
1537  * Applying {@link Ext.DatePicker DatePicker} configuration settings to\r
1538  * <b><tt>this</tt></b> will <b>not</b> affect the DatePicker's configuration.</li>\r
1539  * </ul></div>\r
1540  * @xtype datemenu\r
1541  */\r
1542  Ext.menu.DateMenu = Ext.extend(Ext.menu.Menu, {\r
1543     /** \r
1544      * @cfg {Boolean} enableScrolling\r
1545      * @hide \r
1546      */\r
1547     enableScrolling : false,\r
1548     /**\r
1549      * @cfg {Function} handler\r
1550      * Optional. A function that will handle the select event of this menu.\r
1551      * The handler is passed the following parameters:<div class="mdetail-params"><ul>\r
1552      * <li><code>picker</code> : DatePicker<div class="sub-desc">The Ext.DatePicker.</div></li>\r
1553      * <li><code>date</code> : Date<div class="sub-desc">The selected date.</div></li>\r
1554      * </ul></div>\r
1555      */\r
1556     /**\r
1557      * @cfg {Object} scope\r
1558      * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>\r
1559      * function will be called.  Defaults to this DateMenu instance.\r
1560      */    \r
1561     /** \r
1562      * @cfg {Boolean} hideOnClick\r
1563      * False to continue showing the menu after a date is selected, defaults to true.\r
1564      */\r
1565     hideOnClick : true,\r
1566     \r
1567     /** \r
1568      * @cfg {String} pickerId\r
1569      * An id to assign to the underlying date picker. Defaults to <tt>null</tt>.\r
1570      */\r
1571     pickerId : null,\r
1572     \r
1573     /** \r
1574      * @cfg {Number} maxHeight\r
1575      * @hide \r
1576      */\r
1577     /** \r
1578      * @cfg {Number} scrollIncrement\r
1579      * @hide \r
1580      */\r
1581     /**\r
1582      * The {@link Ext.DatePicker} instance for this DateMenu\r
1583      * @property picker\r
1584      * @type DatePicker\r
1585      */\r
1586     cls : 'x-date-menu',\r
1587     \r
1588     /**\r
1589      * @event click\r
1590      * @hide\r
1591      */\r
1592     \r
1593     /**\r
1594      * @event itemclick\r
1595      * @hide\r
1596      */\r
1597 \r
1598     initComponent : function(){\r
1599         this.on('beforeshow', this.onBeforeShow, this);\r
1600         if(this.strict = (Ext.isIE7 && Ext.isStrict)){\r
1601             this.on('show', this.onShow, this, {single: true, delay: 20});\r
1602         }\r
1603         Ext.apply(this, {\r
1604             plain: true,\r
1605             showSeparator: false,\r
1606             items: this.picker = new Ext.DatePicker(Ext.applyIf({\r
1607                 internalRender: this.strict || !Ext.isIE,\r
1608                 ctCls: 'x-menu-date-item',\r
1609                 id: this.pickerId\r
1610             }, this.initialConfig))\r
1611         });\r
1612         this.picker.purgeListeners();\r
1613         Ext.menu.DateMenu.superclass.initComponent.call(this);\r
1614         /**\r
1615          * @event select\r
1616          * Fires when a date is selected from the {@link #picker Ext.DatePicker}\r
1617          * @param {DatePicker} picker The {@link #picker Ext.DatePicker}\r
1618          * @param {Date} date The selected date\r
1619          */\r
1620         this.relayEvents(this.picker, ['select']);\r
1621         this.on('show', this.picker.focus, this.picker);\r
1622         this.on('select', this.menuHide, this);\r
1623         if(this.handler){\r
1624             this.on('select', this.handler, this.scope || this);\r
1625         }\r
1626     },\r
1627 \r
1628     menuHide : function() {\r
1629         if(this.hideOnClick){\r
1630             this.hide(true);\r
1631         }\r
1632     },\r
1633 \r
1634     onBeforeShow : function(){\r
1635         if(this.picker){\r
1636             this.picker.hideMonthPicker(true);\r
1637         }\r
1638     },\r
1639 \r
1640     onShow : function(){\r
1641         var el = this.picker.getEl();\r
1642         el.setWidth(el.getWidth()); //nasty hack for IE7 strict mode\r
1643     }\r
1644  });\r
1645  Ext.reg('datemenu', Ext.menu.DateMenu);\r
1646  /**\r
1647  * @class Ext.menu.ColorMenu\r
1648  * @extends Ext.menu.Menu\r
1649  * <p>A menu containing a {@link Ext.ColorPalette} Component.</p>\r
1650  * <p>Notes:</p><div class="mdetail-params"><ul>\r
1651  * <li>Although not listed here, the <b>constructor</b> for this class\r
1652  * accepts all of the configuration options of <b>{@link Ext.ColorPalette}</b>.</li>\r
1653  * <li>If subclassing ColorMenu, any configuration options for the ColorPalette must be\r
1654  * applied to the <tt><b>initialConfig</b></tt> property of the ColorMenu.\r
1655  * Applying {@link Ext.ColorPalette ColorPalette} configuration settings to\r
1656  * <b><tt>this</tt></b> will <b>not</b> affect the ColorPalette's configuration.</li>\r
1657  * </ul></div> * \r
1658  * @xtype colormenu\r
1659  */\r
1660  Ext.menu.ColorMenu = Ext.extend(Ext.menu.Menu, {\r
1661     /** \r
1662      * @cfg {Boolean} enableScrolling\r
1663      * @hide \r
1664      */\r
1665     enableScrolling : false,\r
1666     /**\r
1667      * @cfg {Function} handler\r
1668      * Optional. A function that will handle the select event of this menu.\r
1669      * The handler is passed the following parameters:<div class="mdetail-params"><ul>\r
1670      * <li><code>palette</code> : ColorPalette<div class="sub-desc">The {@link #palette Ext.ColorPalette}.</div></li>\r
1671      * <li><code>color</code> : String<div class="sub-desc">The 6-digit color hex code (without the # symbol).</div></li>\r
1672      * </ul></div>\r
1673      */\r
1674     /**\r
1675      * @cfg {Object} scope\r
1676      * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>\r
1677      * function will be called.  Defaults to this ColorMenu instance.\r
1678      */    \r
1679     \r
1680     /** \r
1681      * @cfg {Boolean} hideOnClick\r
1682      * False to continue showing the menu after a color is selected, defaults to true.\r
1683      */\r
1684     hideOnClick : true,\r
1685     \r
1686     cls : 'x-color-menu',\r
1687     \r
1688     /** \r
1689      * @cfg {String} paletteId\r
1690      * An id to assign to the underlying color palette. Defaults to <tt>null</tt>.\r
1691      */\r
1692     paletteId : null,\r
1693     \r
1694     /** \r
1695      * @cfg {Number} maxHeight\r
1696      * @hide \r
1697      */\r
1698     /** \r
1699      * @cfg {Number} scrollIncrement\r
1700      * @hide \r
1701      */\r
1702     /**\r
1703      * @property palette\r
1704      * @type ColorPalette\r
1705      * The {@link Ext.ColorPalette} instance for this ColorMenu\r
1706      */\r
1707     \r
1708     \r
1709     /**\r
1710      * @event click\r
1711      * @hide\r
1712      */\r
1713     \r
1714     /**\r
1715      * @event itemclick\r
1716      * @hide\r
1717      */\r
1718     \r
1719     initComponent : function(){\r
1720         Ext.apply(this, {\r
1721             plain: true,\r
1722             showSeparator: false,\r
1723             items: this.palette = new Ext.ColorPalette(Ext.applyIf({\r
1724                 id: this.paletteId\r
1725             }, this.initialConfig))\r
1726         });\r
1727         this.palette.purgeListeners();\r
1728         Ext.menu.ColorMenu.superclass.initComponent.call(this);\r
1729         /**\r
1730          * @event select\r
1731          * Fires when a color is selected from the {@link #palette Ext.ColorPalette}\r
1732          * @param {Ext.ColorPalette} palette The {@link #palette Ext.ColorPalette}\r
1733              * @param {String} color The 6-digit color hex code (without the # symbol)\r
1734          */\r
1735         this.relayEvents(this.palette, ['select']);\r
1736         this.on('select', this.menuHide, this);\r
1737         if(this.handler){\r
1738             this.on('select', this.handler, this.scope || this);\r
1739         }\r
1740     },\r
1741 \r
1742     menuHide : function(){\r
1743         if(this.hideOnClick){\r
1744             this.hide(true);\r
1745         }\r
1746     }\r
1747 });\r
1748 Ext.reg('colormenu', Ext.menu.ColorMenu);\r