Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / Menu2.html
1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-menu.Menu-method-constructor'><span id='Ext-menu.Menu'>/**
2 </span></span> * @class Ext.menu.Menu
3  * @extends Ext.panel.Panel
4  *
5  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
6  *
7  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
8  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
9  *
10  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
11  * specify `{@link Ext.menu.Item#iconCls iconCls}: 'no-icon'` _or_ `{@link Ext.menu.Item#indent indent}: true`.
12  * This reserves a space for an icon, and indents the Component in line with the other menu items.
13  * See {@link Ext.form.field.ComboBox}.{@link Ext.form.field.ComboBox#getListParent getListParent} for an example.
14
15  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}:false`,
16  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
17  * {@img Ext.menu.Item/Ext.menu.Item.png Ext.menu.Item component}
18 __Example Usage__
19         Ext.create('Ext.menu.Menu', {
20                 width: 100,
21                 height: 100,
22                 margin: '0 0 10 0',
23                 floating: false,  // usually you want this set to True (default)
24                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
25                 items: [{                        
26                         text: 'regular item 1'        
27                 },{
28                     text: 'regular item 2'
29                 },{
30                         text: 'regular item 3'  
31                 }]
32         }); 
33         
34         Ext.create('Ext.menu.Menu', {
35                 width: 100,
36                 height: 100,
37                 plain: true,
38                 floating: false,  // usually you want this set to True (default)
39                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
40                 items: [{                        
41                         text: 'plain item 1'    
42                 },{
43                     text: 'plain item 2'
44                 },{
45                         text: 'plain item 3'
46                 }]
47         }); 
48  * @xtype menu
49  * @markdown
50  * @constructor
51  * @param {Object} config The config object
52  */
53 Ext.define('Ext.menu.Menu', {
54     extend: 'Ext.panel.Panel',
55     alias: 'widget.menu',
56     requires: [
57         'Ext.layout.container.Fit',
58         'Ext.layout.container.VBox',
59         'Ext.menu.CheckItem',
60         'Ext.menu.Item',
61         'Ext.menu.KeyNav',
62         'Ext.menu.Manager',
63         'Ext.menu.Separator'
64     ],
65
66 <span id='Ext-menu.Menu-cfg-allowOtherMenus'>    /**
67 </span>     * @cfg {Boolean} allowOtherMenus
68      * True to allow multiple menus to be displayed at the same time. Defaults to `false`.
69      * @markdown
70      */
71     allowOtherMenus: false,
72
73 <span id='Ext-menu.Menu-cfg-ariaRole'>    /**
74 </span>     * @cfg {String} ariaRole @hide
75      */
76     ariaRole: 'menu',
77
78 <span id='Ext-menu.Menu-cfg-autoRender'>    /**
79 </span>     * @cfg {Boolean} autoRender @hide
80      * floating is true, so autoRender always happens
81      */
82
83 <span id='Ext-menu.Menu-cfg-defaultAlign'>    /**
84 </span>     * @cfg {String} defaultAlign
85      * The default {@link Ext.core.Element#getAlignToXY Ext.core.Element#getAlignToXY} anchor position value for this menu
86      * relative to its element of origin. Defaults to `'tl-bl?'`.
87      * @markdown
88      */
89     defaultAlign: 'tl-bl?',
90
91 <span id='Ext-menu.Menu-cfg-floating'>    /**
92 </span>     * @cfg {Boolean} floating
93      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
94      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
95      * used as a child item of another {@link Ext.container.Container Container}.
96      * @markdown
97      */
98     floating: true,
99
100 <span id='Ext-menu.Menu-cfg-constrain'>    /**
101 </span>     * @cfg {Boolean} @hide
102      * Menu performs its own size changing constraining, so ensure Component's constraining is not applied
103      */
104     constrain: false,
105
106 <span id='Ext-menu.Menu-cfg-hidden'>    /**
107 </span>     * @cfg {Boolean} hidden
108      * True to initially render the Menu as hidden, requiring to be shown manually.
109      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
110      * @markdown
111      */
112     hidden: true,
113
114 <span id='Ext-menu.Menu-cfg-ignoreParentClicks'>    /**
115 </span>     * @cfg {Boolean} ignoreParentClicks
116      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
117      * so that the submenu is not dismissed when clicking the parent item. Defaults to `false`.
118      * @markdown
119      */
120     ignoreParentClicks: false,
121
122     isMenu: true,
123
124 <span id='Ext-menu.Menu-cfg-layout'>    /**
125 </span>     * @cfg {String/Object} layout @hide
126      */
127
128 <span id='Ext-menu.Menu-cfg-showSeparator'>    /**
129 </span>     * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).
130      */
131     showSeparator : true,
132
133 <span id='Ext-menu.Menu-cfg-minWidth'>    /**
134 </span>     * @cfg {Number} minWidth
135      * The minimum width of the Menu. Defaults to `120`.
136      * @markdown
137      */
138     minWidth: 120,
139
140 <span id='Ext-menu.Menu-cfg-plain'>    /**
141 </span>     * @cfg {Boolean} plain
142      * True to remove the incised line down the left side of the menu and to not
143      * indent general Component items. Defaults to `false`.
144      * @markdown
145      */
146
147     initComponent: function() {
148         var me = this,
149             prefix = Ext.baseCSSPrefix;
150
151         me.addEvents(
152 <span id='Ext-menu.Menu-event-click'>            /**
153 </span>             * @event click
154              * Fires when this menu is clicked
155              * @param {Ext.menu.Menu} menu The menu which has been clicked
156              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
157              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
158              * @markdown
159              */
160             'click',
161
162 <span id='Ext-menu.Menu-event-mouseenter'>            /**
163 </span>             * @event mouseenter
164              * Fires when the mouse enters this menu
165              * @param {Ext.menu.Menu} menu The menu
166              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
167              * @markdown
168              */
169             'mouseenter',
170
171 <span id='Ext-menu.Menu-event-mouseleave'>            /**
172 </span>             * @event mouseleave
173              * Fires when the mouse leaves this menu
174              * @param {Ext.menu.Menu} menu The menu
175              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
176              * @markdown
177              */
178             'mouseleave',
179
180 <span id='Ext-menu.Menu-event-mouseover'>            /**
181 </span>             * @event mouseover
182              * Fires when the mouse is hovering over this menu
183              * @param {Ext.menu.Menu} menu The menu
184              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
185              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
186              */
187             'mouseover'
188         );
189
190         Ext.menu.Manager.register(me);
191
192         // Menu classes
193         var cls = [prefix + 'menu'];
194         if (me.plain) {
195             cls.push(prefix + 'menu-plain');
196         }
197         me.cls = cls.join(' ');
198
199         // Menu body classes
200         var bodyCls = me.bodyCls ? [me.bodyCls] : [];
201         bodyCls.unshift(prefix + 'menu-body');
202         me.bodyCls = bodyCls.join(' ');
203
204         // Internal vbox layout, with scrolling overflow
205         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
206         // options if we wish to allow for such configurations on the Menu.
207         // e.g., scrolling speed, vbox align stretch, etc.
208         me.layout = {
209             type: 'vbox',
210             align: 'stretchmax',
211             autoSize: true,
212             clearInnerCtOnLayout: true,
213             overflowHandler: 'Scroller'
214         };
215
216         // hidden defaults to false if floating is configured as false
217         if (me.floating === false &amp;&amp; me.initialConfig.hidden !== true) {
218             me.hidden = false;
219         }
220
221         me.callParent(arguments);
222
223         me.on('beforeshow', function() {
224             var hasItems = !!me.items.length;
225             // FIXME: When a menu has its show cancelled because of no items, it
226             // gets a visibility: hidden applied to it (instead of the default display: none)
227             // Not sure why, but we remove this style when we want to show again.
228             if (hasItems &amp;&amp; me.rendered) {
229                 me.el.setStyle('visibility', null);
230             }
231             return hasItems;
232         });
233     },
234
235     afterRender: function(ct) {
236         var me = this,
237             prefix = Ext.baseCSSPrefix,
238             space = '&amp;#160;';
239
240         me.callParent(arguments);
241
242         // TODO: Move this to a subTemplate When we support them in the future
243         if (me.showSeparator) {
244             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
245                 cls: prefix + 'menu-icon-separator',
246                 html: space
247             });
248         }
249
250         me.focusEl = me.el.createChild({
251             cls: prefix + 'menu-focus',
252             tabIndex: '-1',
253             html: space
254         });
255
256         me.mon(me.el, {
257             click: me.onClick,
258             mouseover: me.onMouseOver,
259             scope: me
260         });
261         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
262
263         if (me.showSeparator &amp;&amp; ((!Ext.isStrict &amp;&amp; Ext.isIE) || Ext.isIE6)) {
264             me.iconSepEl.setHeight(me.el.getHeight());
265         }
266
267         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
268     },
269
270     afterLayout: function() {
271         var me = this;
272         me.callParent(arguments);
273
274         // For IE6 &amp; IE quirks, we have to resize the el and body since position: absolute
275         // floating elements inherit their parent's width, making them the width of
276         // document.body instead of the width of their contents.
277         // This includes left/right dock items.
278         if ((!Ext.iStrict &amp;&amp; Ext.isIE) || Ext.isIE6) {
279             var innerCt = me.layout.getRenderTarget(),
280                 innerCtWidth = 0,
281                 dis = me.dockedItems,
282                 l = dis.length,
283                 i = 0,
284                 di, clone, newWidth;
285
286             innerCtWidth = innerCt.getWidth();
287
288             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
289
290             // First set the body to the new width
291             me.body.setWidth(newWidth);
292
293             // Now we calculate additional width (docked items) and set the el's width
294             for (; i &lt; l, di = dis.getAt(i); i++) {
295                 if (di.dock == 'left' || di.dock == 'right') {
296                     newWidth += di.getWidth();
297                 }
298             }
299             me.el.setWidth(newWidth);
300         }
301     },
302
303 <span id='Ext-menu.Menu-method-canActivateItem'>    /**
304 </span>     * Returns whether a menu item can be activated or not.
305      * @return {Boolean}
306      */
307     canActivateItem: function(item) {
308         return item &amp;&amp; !item.isDisabled() &amp;&amp; item.isVisible() &amp;&amp; (item.canActivate || item.getXTypes().indexOf('menuitem') &lt; 0);
309     },
310
311 <span id='Ext-menu.Menu-method-deactivateActiveItem'>    /**
312 </span>     * Deactivates the current active item on the menu, if one exists.
313      */
314     deactivateActiveItem: function() {
315         var me = this;
316
317         if (me.activeItem) {
318             me.activeItem.deactivate();
319             if (!me.activeItem.activated) {
320                 delete me.activeItem;
321             }
322         }
323         if (me.focusedItem) {
324             me.focusedItem.blur();
325             if (!me.focusedItem.$focused) {
326                 delete me.focusedItem;
327             }
328         }
329     },
330
331     // inherit docs
332     getFocusEl: function() {
333         return this.focusEl;
334     },
335
336     // inherit docs
337     hide: function() {
338         this.deactivateActiveItem();
339         this.callParent(arguments);
340     },
341
342     // private
343     getItemFromEvent: function(e) {
344         return this.getChildByElement(e.getTarget());
345     },
346
347     lookupComponent: function(cmp) {
348         var me = this;
349
350         if (Ext.isString(cmp)) {
351             cmp = me.lookupItemFromString(cmp);
352         } else if (Ext.isObject(cmp)) {
353             cmp = me.lookupItemFromObject(cmp);
354         }
355
356         // Apply our minWidth to all of our child components so it's accounted
357         // for in our VBox layout
358         cmp.minWidth = cmp.minWidth || me.minWidth;
359
360         return cmp;
361     },
362
363     // private
364     lookupItemFromObject: function(cmp) {
365         var me = this,
366             prefix = Ext.baseCSSPrefix;
367
368         if (!cmp.isComponent) {
369             if (!cmp.xtype) {
370                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
371             } else {
372                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
373             }
374         }
375
376         if (cmp.isMenuItem) {
377             cmp.parentMenu = me;
378         }
379
380         if (!cmp.isMenuItem &amp;&amp; !cmp.dock) {
381             var cls = [
382                     prefix + 'menu-item',
383                     prefix + 'menu-item-cmp'
384                 ],
385                 intercept = Ext.Function.createInterceptor;
386
387             // Wrap focus/blur to control component focus
388             cmp.focus = intercept(cmp.focus, function() {
389                 this.$focused = true;
390             }, cmp);
391             cmp.blur = intercept(cmp.blur, function() {
392                 this.$focused = false;
393             }, cmp);
394
395             if (!me.plain &amp;&amp; (cmp.indent === true || cmp.iconCls === 'no-icon')) {
396                 cls.push(prefix + 'menu-item-indent');
397             }
398
399             if (cmp.rendered) {
400                 cmp.el.addCls(cls);
401             } else {
402                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
403             }
404             cmp.isMenuItem = true;
405         }
406         return cmp;
407     },
408
409     // private
410     lookupItemFromString: function(cmp) {
411         return (cmp == 'separator' || cmp == '-') ?
412             Ext.createWidget('menuseparator')
413             : Ext.createWidget('menuitem', {
414                 canActivate: false,
415                 hideOnClick: false,
416                 plain: true,
417                 text: cmp
418             });
419     },
420
421     onClick: function(e) {
422         var me = this,
423             item;
424
425         if (me.disabled) {
426             e.stopEvent();
427             return;
428         }
429
430         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
431             item = me.getItemFromEvent(e) || me.activeItem;
432
433             if (item) {
434                 if (item.getXTypes().indexOf('menuitem') &gt;= 0) {
435                     if (!item.menu || !me.ignoreParentClicks) {
436                         item.onClick(e);
437                     } else {
438                         e.stopEvent();
439                     }
440                 }
441             }
442             me.fireEvent('click', me, item, e);
443         }
444     },
445
446     onDestroy: function() {
447         var me = this;
448
449         Ext.menu.Manager.unregister(me);
450         if (me.rendered) {
451             me.el.un(me.mouseMonitor);
452             me.keyNav.destroy();
453             delete me.keyNav;
454         }
455         me.callParent(arguments);
456     },
457
458     onMouseLeave: function(e) {
459         var me = this;
460
461         me.deactivateActiveItem();
462
463         if (me.disabled) {
464             return;
465         }
466
467         me.fireEvent('mouseleave', me, e);
468     },
469
470     onMouseOver: function(e) {
471         var me = this,
472             fromEl = e.getRelatedTarget(),
473             mouseEnter = !me.el.contains(fromEl),
474             item = me.getItemFromEvent(e);
475
476         if (mouseEnter &amp;&amp; me.parentMenu) {
477             me.parentMenu.setActiveItem(me.parentItem);
478             me.parentMenu.mouseMonitor.mouseenter();
479         }
480
481         if (me.disabled) {
482             return;
483         }
484
485         if (item) {
486             me.setActiveItem(item);
487             if (item.activated &amp;&amp; item.expandMenu) {
488                 item.expandMenu();
489             }
490         }
491         if (mouseEnter) {
492             me.fireEvent('mouseenter', me, e);
493         }
494         me.fireEvent('mouseover', me, item, e);
495     },
496
497     setActiveItem: function(item) {
498         var me = this;
499
500         if (item &amp;&amp; (item != me.activeItem &amp;&amp; item != me.focusedItem)) {
501             me.deactivateActiveItem();
502             if (me.canActivateItem(item)) {
503                 if (item.activate) {
504                     item.activate();
505                     if (item.activated) {
506                         me.activeItem = item;
507                         me.focusedItem = item;
508                         me.focus();
509                     }
510                 } else {
511                     item.focus();
512                     me.focusedItem = item;
513                 }
514             }
515             item.el.scrollIntoView(me.layout.getRenderTarget());
516         }
517     },
518
519 <span id='Ext-menu.Menu-method-showBy'>    /**
520 </span>     * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.core.Element Element}.
521      * @param {Mixed component} The {@link Ext.Component} or {@link Ext.core.Element} to show the menu by.
522      * @param {String} position (optional) Alignment position as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `{@link #defaultAlign}`.
523      * @param {Array} offsets (optional) Alignment offsets as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `undefined`.
524      * @return {Menu} This Menu.
525      * @markdown
526      */
527     showBy: function(cmp, pos, off) {
528         var me = this;
529
530         if (me.floating &amp;&amp; cmp) {
531             me.layout.autoSize = true;
532             me.show();
533
534             // Component or Element
535             cmp = cmp.el || cmp;
536
537             // Convert absolute to floatParent-relative coordinates if necessary.
538             var xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
539             if (me.floatParent) {
540                 var r = me.floatParent.getTargetEl().getViewRegion();
541                 xy[0] -= r.x;
542                 xy[1] -= r.y;
543             }
544             me.showAt(xy);
545             me.doConstrain();
546         }
547         return me;
548     },
549
550     doConstrain : function() {
551         var me = this,
552             y = this.el.getY(),
553             max, full,
554             returnY = y, normalY, parentEl, scrollTop, viewHeight;
555
556         delete me.height;
557         me.setSize();
558         full = me.getHeight();
559         if (me.floating) {
560             parentEl = Ext.fly(me.el.dom.parentNode);
561             scrollTop = parentEl.getScroll().top;
562             viewHeight = parentEl.getViewSize().height;
563             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
564             //of the view.
565             normalY = y - scrollTop;
566             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
567             if (full &gt; viewHeight) {
568                 max = viewHeight;
569                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
570                 returnY = y - normalY;
571             } else if (max &lt; full) {
572                 returnY = y - (full - max);
573                 max = full;
574             }
575         }else{
576             max = me.getHeight();
577         }
578         // Always respect maxHeight
579         if (me.maxHeight){
580             max = Math.min(me.maxHeight, max);
581         }
582         if (full &gt; max &amp;&amp; max &gt; 0){
583             me.layout.autoSize = false;
584             me.setHeight(max);
585             if (me.showSeparator){
586                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
587             }
588         }
589         me.el.setY(returnY);
590     }
591 });</pre></pre></body></html>