Upgrade to ExtJS 4.0.2 - Released 06/09/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'>/**
19 </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  *
35  * {@img Ext.menu.Item/Ext.menu.Item.png Ext.menu.Item component}
36  *
37  *__Example Usage__
38  *
39  *     Ext.create('Ext.menu.Menu', {
40  *         width: 100,
41  *         height: 100,
42  *         margin: '0 0 10 0',
43  *         floating: false,  // usually you want this set to True (default)
44  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
45  *         items: [{                        
46  *             text: 'regular item 1'        
47  *         },{
48  *             text: 'regular item 2'
49  *         },{
50  *             text: 'regular item 3'  
51  *         }]
52  *     }); 
53  *     
54  *     Ext.create('Ext.menu.Menu', {
55  *         width: 100,
56  *         height: 100,
57  *         plain: true,
58  *         floating: false,  // usually you want this set to True (default)
59  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
60  *         items: [{                        
61  *             text: 'plain item 1'    
62  *         },{
63  *             text: 'plain item 2'
64  *         },{
65  *             text: 'plain item 3'
66  *         }]
67  *     }); 
68  *
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      * Menus are constrained to the document body by default
120      */
121     constrain: true,
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     hideMode: 'visibility',
132
133 <span id='Ext-menu-Menu-cfg-ignoreParentClicks'>    /**
134 </span>     * @cfg {Boolean} ignoreParentClicks
135      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
136      * so that the submenu is not dismissed when clicking the parent item. Defaults to `false`.
137      * @markdown
138      */
139     ignoreParentClicks: false,
140
141     isMenu: true,
142
143 <span id='Ext-menu-Menu-cfg-layout'>    /**
144 </span>     * @cfg {String/Object} layout @hide
145      */
146
147 <span id='Ext-menu-Menu-cfg-showSeparator'>    /**
148 </span>     * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).
149      */
150     showSeparator : true,
151
152 <span id='Ext-menu-Menu-cfg-minWidth'>    /**
153 </span>     * @cfg {Number} minWidth
154      * The minimum width of the Menu. Defaults to `120`.
155      * @markdown
156      */
157     minWidth: 120,
158
159 <span id='Ext-menu-Menu-cfg-plain'>    /**
160 </span>     * @cfg {Boolean} plain
161      * True to remove the incised line down the left side of the menu and to not
162      * indent general Component items. Defaults to `false`.
163      * @markdown
164      */
165
166     initComponent: function() {
167         var me = this,
168             prefix = Ext.baseCSSPrefix,
169             cls = [prefix + 'menu'],
170             bodyCls = me.bodyCls ? [me.bodyCls] : [];
171
172         me.addEvents(
173 <span id='Ext-menu-Menu-event-click'>            /**
174 </span>             * @event click
175              * Fires when this menu is clicked
176              * @param {Ext.menu.Menu} menu The menu which has been clicked
177              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
178              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
179              * @markdown
180              */
181             'click',
182
183 <span id='Ext-menu-Menu-event-mouseenter'>            /**
184 </span>             * @event mouseenter
185              * Fires when the mouse enters this menu
186              * @param {Ext.menu.Menu} menu The menu
187              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
188              * @markdown
189              */
190             'mouseenter',
191
192 <span id='Ext-menu-Menu-event-mouseleave'>            /**
193 </span>             * @event mouseleave
194              * Fires when the mouse leaves this menu
195              * @param {Ext.menu.Menu} menu The menu
196              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
197              * @markdown
198              */
199             'mouseleave',
200
201 <span id='Ext-menu-Menu-event-mouseover'>            /**
202 </span>             * @event mouseover
203              * Fires when the mouse is hovering over this menu
204              * @param {Ext.menu.Menu} menu The menu
205              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
206              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
207              */
208             'mouseover'
209         );
210
211         Ext.menu.Manager.register(me);
212
213         // Menu classes
214         if (me.plain) {
215             cls.push(prefix + 'menu-plain');
216         }
217         me.cls = cls.join(' ');
218
219         // Menu body classes
220         bodyCls.unshift(prefix + 'menu-body');
221         me.bodyCls = bodyCls.join(' ');
222
223         // Internal vbox layout, with scrolling overflow
224         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
225         // options if we wish to allow for such configurations on the Menu.
226         // e.g., scrolling speed, vbox align stretch, etc.
227         me.layout = {
228             type: 'vbox',
229             align: 'stretchmax',
230             autoSize: true,
231             clearInnerCtOnLayout: true,
232             overflowHandler: 'Scroller'
233         };
234
235         // hidden defaults to false if floating is configured as false
236         if (me.floating === false &amp;&amp; me.initialConfig.hidden !== true) {
237             me.hidden = false;
238         }
239
240         me.callParent(arguments);
241
242         me.on('beforeshow', function() {
243             var hasItems = !!me.items.length;
244             // FIXME: When a menu has its show cancelled because of no items, it
245             // gets a visibility: hidden applied to it (instead of the default display: none)
246             // Not sure why, but we remove this style when we want to show again.
247             if (hasItems &amp;&amp; me.rendered) {
248                 me.el.setStyle('visibility', null);
249             }
250             return hasItems;
251         });
252     },
253
254     afterRender: function(ct) {
255         var me = this,
256             prefix = Ext.baseCSSPrefix,
257             space = '&amp;#160;';
258
259         me.callParent(arguments);
260
261         // TODO: Move this to a subTemplate When we support them in the future
262         if (me.showSeparator) {
263             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
264                 cls: prefix + 'menu-icon-separator',
265                 html: space
266             });
267         }
268
269         me.focusEl = me.el.createChild({
270             cls: prefix + 'menu-focus',
271             tabIndex: '-1',
272             html: space
273         });
274
275         me.mon(me.el, {
276             click: me.onClick,
277             mouseover: me.onMouseOver,
278             scope: me
279         });
280         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
281
282         if (me.showSeparator &amp;&amp; ((!Ext.isStrict &amp;&amp; Ext.isIE) || Ext.isIE6)) {
283             me.iconSepEl.setHeight(me.el.getHeight());
284         }
285
286         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
287     },
288
289     afterLayout: function() {
290         var me = this;
291         me.callParent(arguments);
292
293         // For IE6 &amp; IE quirks, we have to resize the el and body since position: absolute
294         // floating elements inherit their parent's width, making them the width of
295         // document.body instead of the width of their contents.
296         // This includes left/right dock items.
297         if ((!Ext.iStrict &amp;&amp; Ext.isIE) || Ext.isIE6) {
298             var innerCt = me.layout.getRenderTarget(),
299                 innerCtWidth = 0,
300                 dis = me.dockedItems,
301                 l = dis.length,
302                 i = 0,
303                 di, clone, newWidth;
304
305             innerCtWidth = innerCt.getWidth();
306
307             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
308
309             // First set the body to the new width
310             me.body.setWidth(newWidth);
311
312             // Now we calculate additional width (docked items) and set the el's width
313             for (; i &lt; l, di = dis.getAt(i); i++) {
314                 if (di.dock == 'left' || di.dock == 'right') {
315                     newWidth += di.getWidth();
316                 }
317             }
318             me.el.setWidth(newWidth);
319         }
320     },
321
322 <span id='Ext-menu-Menu-method-canActivateItem'>    /**
323 </span>     * Returns whether a menu item can be activated or not.
324      * @return {Boolean}
325      */
326     canActivateItem: function(item) {
327         return item &amp;&amp; !item.isDisabled() &amp;&amp; item.isVisible() &amp;&amp; (item.canActivate || item.getXTypes().indexOf('menuitem') &lt; 0);
328     },
329
330 <span id='Ext-menu-Menu-method-deactivateActiveItem'>    /**
331 </span>     * Deactivates the current active item on the menu, if one exists.
332      */
333     deactivateActiveItem: function() {
334         var me = this;
335
336         if (me.activeItem) {
337             me.activeItem.deactivate();
338             if (!me.activeItem.activated) {
339                 delete me.activeItem;
340             }
341         }
342         if (me.focusedItem) {
343             me.focusedItem.blur();
344             if (!me.focusedItem.$focused) {
345                 delete me.focusedItem;
346             }
347         }
348     },
349
350     clearStretch: function () {
351         // the vbox/stretchmax will set the el sizes and subsequent layouts will not
352         // reconsider them unless we clear the dimensions on the el's here:
353         if (this.rendered) {
354             this.items.each(function (item) {
355                 // each menuItem component needs to layout again, so clear its cache
356                 if (item.componentLayout) {
357                     delete item.componentLayout.lastComponentSize;
358                 }
359                 if (item.el) {
360                     item.el.setWidth(null);
361                 }
362             });
363         }
364     },
365
366     onAdd: function () {
367         var me = this;
368
369         me.clearStretch();
370         me.callParent(arguments);
371
372         if (Ext.isIE6 || Ext.isIE7) {
373             // TODO - why does this need to be done (and not ok to do now)?
374             Ext.Function.defer(me.doComponentLayout, 10, me);
375         }
376     },
377
378     onRemove: function () {
379         this.clearStretch();
380         this.callParent(arguments);
381
382     },
383
384     redoComponentLayout: function () {
385         if (this.rendered) {
386             this.clearStretch();
387             this.doComponentLayout();
388         }
389     },
390
391     // inherit docs
392     getFocusEl: function() {
393         return this.focusEl;
394     },
395
396     // inherit docs
397     hide: function() {
398         this.deactivateActiveItem();
399         this.callParent(arguments);
400     },
401
402     // private
403     getItemFromEvent: function(e) {
404         return this.getChildByElement(e.getTarget());
405     },
406
407     lookupComponent: function(cmp) {
408         var me = this;
409
410         if (Ext.isString(cmp)) {
411             cmp = me.lookupItemFromString(cmp);
412         } else if (Ext.isObject(cmp)) {
413             cmp = me.lookupItemFromObject(cmp);
414         }
415
416         // Apply our minWidth to all of our child components so it's accounted
417         // for in our VBox layout
418         cmp.minWidth = cmp.minWidth || me.minWidth;
419
420         return cmp;
421     },
422
423     // private
424     lookupItemFromObject: function(cmp) {
425         var me = this,
426             prefix = Ext.baseCSSPrefix,
427             cls,
428             intercept;
429
430         if (!cmp.isComponent) {
431             if (!cmp.xtype) {
432                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
433             } else {
434                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
435             }
436         }
437
438         if (cmp.isMenuItem) {
439             cmp.parentMenu = me;
440         }
441
442         if (!cmp.isMenuItem &amp;&amp; !cmp.dock) {
443             cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
444             intercept = Ext.Function.createInterceptor;
445
446             // Wrap focus/blur to control component focus
447             cmp.focus = intercept(cmp.focus, function() {
448                 this.$focused = true;
449             }, cmp);
450             cmp.blur = intercept(cmp.blur, function() {
451                 this.$focused = false;
452             }, cmp);
453
454             if (!me.plain &amp;&amp; (cmp.indent === true || cmp.iconCls === 'no-icon')) {
455                 cls.push(prefix + 'menu-item-indent');
456             }
457
458             if (cmp.rendered) {
459                 cmp.el.addCls(cls);
460             } else {
461                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
462             }
463             cmp.isMenuItem = true;
464         }
465         return cmp;
466     },
467
468     // private
469     lookupItemFromString: function(cmp) {
470         return (cmp == 'separator' || cmp == '-') ?
471             Ext.createWidget('menuseparator')
472             : Ext.createWidget('menuitem', {
473                 canActivate: false,
474                 hideOnClick: false,
475                 plain: true,
476                 text: cmp
477             });
478     },
479
480     onClick: function(e) {
481         var me = this,
482             item;
483
484         if (me.disabled) {
485             e.stopEvent();
486             return;
487         }
488
489         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
490             item = me.getItemFromEvent(e) || me.activeItem;
491
492             if (item) {
493                 if (item.getXTypes().indexOf('menuitem') &gt;= 0) {
494                     if (!item.menu || !me.ignoreParentClicks) {
495                         item.onClick(e);
496                     } else {
497                         e.stopEvent();
498                     }
499                 }
500             }
501             me.fireEvent('click', me, item, e);
502         }
503     },
504
505     onDestroy: function() {
506         var me = this;
507
508         Ext.menu.Manager.unregister(me);
509         if (me.rendered) {
510             me.el.un(me.mouseMonitor);
511             me.keyNav.destroy();
512             delete me.keyNav;
513         }
514         me.callParent(arguments);
515     },
516
517     onMouseLeave: function(e) {
518         var me = this;
519
520         me.deactivateActiveItem();
521
522         if (me.disabled) {
523             return;
524         }
525
526         me.fireEvent('mouseleave', me, e);
527     },
528
529     onMouseOver: function(e) {
530         var me = this,
531             fromEl = e.getRelatedTarget(),
532             mouseEnter = !me.el.contains(fromEl),
533             item = me.getItemFromEvent(e);
534
535         if (mouseEnter &amp;&amp; me.parentMenu) {
536             me.parentMenu.setActiveItem(me.parentItem);
537             me.parentMenu.mouseMonitor.mouseenter();
538         }
539
540         if (me.disabled) {
541             return;
542         }
543
544         if (item) {
545             me.setActiveItem(item);
546             if (item.activated &amp;&amp; item.expandMenu) {
547                 item.expandMenu();
548             }
549         }
550         if (mouseEnter) {
551             me.fireEvent('mouseenter', me, e);
552         }
553         me.fireEvent('mouseover', me, item, e);
554     },
555
556     setActiveItem: function(item) {
557         var me = this;
558
559         if (item &amp;&amp; (item != me.activeItem &amp;&amp; item != me.focusedItem)) {
560             me.deactivateActiveItem();
561             if (me.canActivateItem(item)) {
562                 if (item.activate) {
563                     item.activate();
564                     if (item.activated) {
565                         me.activeItem = item;
566                         me.focusedItem = item;
567                         me.focus();
568                     }
569                 } else {
570                     item.focus();
571                     me.focusedItem = item;
572                 }
573             }
574             item.el.scrollIntoView(me.layout.getRenderTarget());
575         }
576     },
577
578 <span id='Ext-menu-Menu-method-showBy'>    /**
579 </span>     * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.core.Element Element}.
580      * @param {Mixed component} The {@link Ext.Component} or {@link Ext.core.Element} to show the menu by.
581      * @param {String} position (optional) Alignment position as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `{@link #defaultAlign}`.
582      * @param {Array} offsets (optional) Alignment offsets as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `undefined`.
583      * @return {Menu} This Menu.
584      * @markdown
585      */
586     showBy: function(cmp, pos, off) {
587         var me = this,
588             xy,
589             region;
590
591         if (me.floating &amp;&amp; cmp) {
592             me.layout.autoSize = true;
593
594             // show off-screen first so that we can calc position without causing a visual jump
595             me.doAutoRender();
596
597             // Component or Element
598             cmp = cmp.el || cmp;
599
600             // Convert absolute to floatParent-relative coordinates if necessary.
601             xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
602             if (me.floatParent) {
603                 region = me.floatParent.getTargetEl().getViewRegion();
604                 xy[0] -= region.x;
605                 xy[1] -= region.y;
606             }
607             me.showAt(xy);
608         }
609         return me;
610     },
611     
612     // inherit docs
613     showAt: function(){
614         this.callParent(arguments);
615         if (this.floating) {
616             this.doConstrain();
617         }    
618     },
619
620     doConstrain : function() {
621         var me = this,
622             y = me.el.getY(),
623             max, full,
624             vector,
625             returnY = y, normalY, parentEl, scrollTop, viewHeight;
626
627         delete me.height;
628         me.setSize();
629         full = me.getHeight();
630         if (me.floating) {
631             parentEl = Ext.fly(me.el.dom.parentNode);
632             scrollTop = parentEl.getScroll().top;
633             viewHeight = parentEl.getViewSize().height;
634             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
635             //of the view.
636             normalY = y - scrollTop;
637             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
638             if (full &gt; viewHeight) {
639                 max = viewHeight;
640                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
641                 returnY = y - normalY;
642             } else if (max &lt; full) {
643                 returnY = y - (full - max);
644                 max = full;
645             }
646         }else{
647             max = me.getHeight();
648         }
649         // Always respect maxHeight
650         if (me.maxHeight){
651             max = Math.min(me.maxHeight, max);
652         }
653         if (full &gt; max &amp;&amp; max &gt; 0){
654             me.layout.autoSize = false;
655             me.setHeight(max);
656             if (me.showSeparator){
657                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
658             }
659         }
660         vector = me.getConstrainVector(me.el.dom.parentNode);
661         if (vector) {
662             me.setPosition(me.getPosition()[0] + vector[0]);
663         }
664         me.el.setY(returnY);
665     }
666 });</pre>
667 </body>
668 </html>