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; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
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}.
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}.
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.
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}.
32 * Ext.create('Ext.menu.Menu', {
36 * floating: false, // usually you want this set to True (default)
37 * renderTo: Ext.getBody(), // usually rendered by it's containing component
39 * text: 'regular item 1'
41 * text: 'regular item 2'
43 * text: 'regular item 3'
47 * Ext.create('Ext.menu.Menu', {
51 * floating: false, // usually you want this set to True (default)
52 * renderTo: Ext.getBody(), // usually rendered by it's containing component
54 * text: 'plain item 1'
56 * text: 'plain item 2'
58 * text: 'plain item 3'
62 Ext.define('Ext.menu.Menu', {
63 extend: 'Ext.panel.Panel',
66 'Ext.layout.container.Fit',
67 'Ext.layout.container.VBox',
75 <span id='Ext-menu-Menu-property-parentMenu'> /**
76 </span> * @property {Ext.menu.Menu} parentMenu
77 * The parent Menu of this Menu.
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.
84 allowOtherMenus: false,
86 <span id='Ext-menu-Menu-cfg-ariaRole'> /**
87 </span> * @cfg {String} ariaRole @hide
91 <span id='Ext-menu-Menu-cfg-autoRender'> /**
92 </span> * @cfg {Boolean} autoRender @hide
93 * floating is true, so autoRender always happens
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.
101 defaultAlign: 'tl-bl?',
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}.
111 <span id='Ext-menu-Menu-cfg-constrain'> /**
112 </span> * @cfg {Boolean} @hide
113 * Menus are constrained to the document body by default
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.
121 * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
125 hideMode: 'visibility',
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.
132 ignoreParentClicks: false,
136 <span id='Ext-menu-Menu-cfg-layout'> /**
137 </span> * @cfg {String/Object} layout @hide
140 <span id='Ext-menu-Menu-cfg-showSeparator'> /**
141 </span> * @cfg {Boolean} showSeparator
142 * True to show the icon separator.
144 showSeparator : true,
146 <span id='Ext-menu-Menu-cfg-minWidth'> /**
147 </span> * @cfg {Number} minWidth
148 * The minimum width of the Menu.
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.
157 initComponent: function() {
159 prefix = Ext.baseCSSPrefix,
160 cls = [prefix + 'menu'],
161 bodyCls = me.bodyCls ? [me.bodyCls] : [];
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}.
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}
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}
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}
199 Ext.menu.Manager.register(me);
203 cls.push(prefix + 'menu-plain');
205 me.cls = cls.join(' ');
208 bodyCls.unshift(prefix + 'menu-body');
209 me.bodyCls = bodyCls.join(' ');
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.
219 clearInnerCtOnLayout: true,
220 overflowHandler: 'Scroller'
223 // hidden defaults to false if floating is configured as false
224 if (me.floating === false && me.initialConfig.hidden !== true) {
228 me.callParent(arguments);
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 && me.rendered) {
236 me.el.setStyle('visibility', null);
242 afterRender: function(ct) {
244 prefix = Ext.baseCSSPrefix,
245 space = '&#160;';
247 me.callParent(arguments);
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',
257 me.focusEl = me.el.createChild({
258 cls: prefix + 'menu-focus',
265 mouseover: me.onMouseOver,
268 me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
270 if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
271 me.iconSepEl.setHeight(me.el.getHeight());
274 me.keyNav = Ext.create('Ext.menu.KeyNav', me);
277 afterLayout: function() {
279 me.callParent(arguments);
281 // For IE6 & 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 && Ext.isIE) || Ext.isIE6) {
286 var innerCt = me.layout.getRenderTarget(),
288 dis = me.dockedItems,
293 innerCtWidth = innerCt.getWidth();
295 newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
297 // First set the body to the new width
298 me.body.setWidth(newWidth);
300 // Now we calculate additional width (docked items) and set the el's width
301 for (; i < l, di = dis.getAt(i); i++) {
302 if (di.dock == 'left' || di.dock == 'right') {
303 newWidth += di.getWidth();
306 me.el.setWidth(newWidth);
310 getBubbleTarget: function(){
311 return this.parentMenu || this.callParent();
314 <span id='Ext-menu-Menu-method-canActivateItem'> /**
315 </span> * Returns whether a menu item can be activated or not.
318 canActivateItem: function(item) {
319 return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
322 <span id='Ext-menu-Menu-method-deactivateActiveItem'> /**
323 </span> * Deactivates the current active item on the menu, if one exists.
325 deactivateActiveItem: function() {
329 me.activeItem.deactivate();
330 if (!me.activeItem.activated) {
331 delete me.activeItem;
335 // only blur if focusedItem is not a filter
336 if (me.focusedItem && !me.filtered) {
337 me.focusedItem.blur();
338 if (!me.focusedItem.$focused) {
339 delete me.focusedItem;
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:
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;
354 item.el.setWidth(null);
364 me.callParent(arguments);
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);
372 onRemove: function () {
374 this.callParent(arguments);
378 redoComponentLayout: function () {
381 this.doComponentLayout();
386 getFocusEl: function() {
392 this.deactivateActiveItem();
393 this.callParent(arguments);
397 getItemFromEvent: function(e) {
398 return this.getChildByElement(e.getTarget());
401 lookupComponent: function(cmp) {
404 if (Ext.isString(cmp)) {
405 cmp = me.lookupItemFromString(cmp);
406 } else if (Ext.isObject(cmp)) {
407 cmp = me.lookupItemFromObject(cmp);
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;
418 lookupItemFromObject: function(cmp) {
420 prefix = Ext.baseCSSPrefix,
424 if (!cmp.isComponent) {
426 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
428 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
432 if (cmp.isMenuItem) {
436 if (!cmp.isMenuItem && !cmp.dock) {
437 cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
438 intercept = Ext.Function.createInterceptor;
440 // Wrap focus/blur to control component focus
441 cmp.focus = intercept(cmp.focus, function() {
442 this.$focused = true;
444 cmp.blur = intercept(cmp.blur, function() {
445 this.$focused = false;
448 if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
449 cls.push(prefix + 'menu-item-indent');
455 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
457 cmp.isMenuItem = true;
463 lookupItemFromString: function(cmp) {
464 return (cmp == 'separator' || cmp == '-') ?
465 Ext.createWidget('menuseparator')
466 : Ext.createWidget('menuitem', {
474 onClick: function(e) {
483 if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
484 item = me.getItemFromEvent(e) || me.activeItem;
487 if (item.getXTypes().indexOf('menuitem') >= 0) {
488 if (!item.menu || !me.ignoreParentClicks) {
495 me.fireEvent('click', me, item, e);
499 onDestroy: function() {
502 Ext.menu.Manager.unregister(me);
504 me.el.un(me.mouseMonitor);
508 me.callParent(arguments);
511 onMouseLeave: function(e) {
514 me.deactivateActiveItem();
520 me.fireEvent('mouseleave', me, e);
523 onMouseOver: function(e) {
525 fromEl = e.getRelatedTarget(),
526 mouseEnter = !me.el.contains(fromEl),
527 item = me.getItemFromEvent(e);
529 if (mouseEnter && me.parentMenu) {
530 me.parentMenu.setActiveItem(me.parentItem);
531 me.parentMenu.mouseMonitor.mouseenter();
539 me.setActiveItem(item);
540 if (item.activated && item.expandMenu) {
545 me.fireEvent('mouseenter', me, e);
547 me.fireEvent('mouseover', me, item, e);
550 setActiveItem: function(item) {
553 if (item && (item != me.activeItem && item != me.focusedItem)) {
554 me.deactivateActiveItem();
555 if (me.canActivateItem(item)) {
558 if (item.activated) {
559 me.activeItem = item;
560 me.focusedItem = item;
565 me.focusedItem = item;
568 item.el.scrollIntoView(me.layout.getRenderTarget());
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.
580 showBy: function(cmp, pos, off) {
585 if (me.floating && cmp) {
586 me.layout.autoSize = true;
588 // show off-screen first so that we can calc position without causing a visual jump
590 delete me.needsLayout;
592 // Component or Element
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();
607 doConstrain : function() {
612 returnY = y, normalY, parentEl, scrollTop, viewHeight;
616 full = me.getHeight();
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
624 normalY = y - scrollTop;
625 max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
626 if (full > 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 < full) {
631 returnY = y - (full - max);
635 max = me.getHeight();
637 // Always respect maxHeight
639 max = Math.min(me.maxHeight, max);
641 if (full > max && max > 0){
642 me.layout.autoSize = false;
644 if (me.showSeparator){
645 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
648 vector = me.getConstrainVector(me.el.getScopeParent());
650 me.setPosition(me.getPosition()[0] + vector[0]);