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