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