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