* @class Ext.layout.container.boxOverflow.Menu
* @extends Ext.layout.container.boxOverflow.None
* @private
*/
Ext.define('Ext.layout.container.boxOverflow.Menu', {
/* Begin Definitions */
extend: 'Ext.layout.container.boxOverflow.None',
requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
alternateClassName: 'Ext.layout.boxOverflow.Menu',
/* End Definitions */
* @cfg {String} afterCtCls
* CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
* which must always be present at the rightmost edge of the Container
*/
* @property noItemsMenuText
* @type String
* HTML fragment to render into the toolbar overflow menu if there are no items to display
*/
noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
constructor: function(layout) {
var me = this;
me.callParent(arguments);
// Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
* @property menuItems
* @type Array
* Array of all items that are currently hidden and should go into the dropdown menu
*/
me.menuItems = [];
},
onRemove: function(comp){
Ext.Array.remove(this.menuItems, comp);
},
handleOverflow: function(calculations, targetSize) {
var me = this,
layout = me.layout,
methodName = 'get' + layout.parallelPrefixCap,
newSize = {},
posArgs = [null, null];
me.callParent(arguments);
this.createMenu(calculations, targetSize);
newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
// Center the menuTrigger button.
// TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
return { targetSize: newSize };
},
* @private
* Called by the layout, when it determines that there is no overflow.
* Also called as an interceptor to the layout's onLayout method to reshow
* previously hidden overflowing items.
*/
clearOverflow: function(calculations, targetSize) {
var me = this,
newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
items = me.menuItems,
i = 0,
length = items.length,
item;
me.hideTrigger();
for (; i < length; i++) {
items[i].show();
}
items.length = 0;
return targetSize ? {
targetSize: {
height: targetSize.height,
width : newWidth
}
} : null;
},
* @private
*/
showTrigger: function() {
this.menuTrigger.show();
},
* @private
*/
hideTrigger: function() {
if (this.menuTrigger !== undefined) {
this.menuTrigger.hide();
}
},
* @private
* Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
*/
beforeMenuShow: function(menu) {
var me = this,
items = me.menuItems,
i = 0,
len = items.length,
item,
prev;
var needsSep = function(group, prev){
return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
};
me.clearMenu();
menu.removeAll();
for (; i < len; i++) {
item = items[i];
// Do not show a separator as a first item
if (!i && (item instanceof Ext.toolbar.Separator)) {
continue;
}
if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
menu.add('-');
}
me.addComponentToMenu(menu, item);
prev = item;
}
// put something so the menu isn't empty if no compatible items found
if (menu.items.length < 1) {
menu.add(me.noItemsMenuText);
}
},
* @private
* Returns a menu config for a given component. This config is used to create a menu item
* to be added to the expander menu
* @param {Ext.Component} component The component to create the config for
* @param {Boolean} hideOnClick Passed through to the menu item
*/
createMenuConfig : function(component, hideOnClick) {
var config = Ext.apply({}, component.initialConfig),
group = component.toggleGroup;
Ext.copyTo(config, component, [
'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
]);
Ext.apply(config, {
text : component.overflowText || component.text,
hideOnClick: hideOnClick,
destroyMenu: false
});
if (group || component.enableToggle) {
Ext.apply(config, {
group : group,
checked: component.pressed,
listeners: {
checkchange: function(item, checked){
component.toggle(checked);
}
}
});
}
delete config.ownerCt;
delete config.xtype;
delete config.id;
return config;
},
* @private
* Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
* @param {Ext.menu.Menu} menu The menu to add to
* @param {Ext.Component} component The component to add
*/
addComponentToMenu : function(menu, component) {
var me = this;
if (component instanceof Ext.toolbar.Separator) {
menu.add('-');
} else if (component.isComponent) {
if (component.isXType('splitbutton')) {
menu.add(me.createMenuConfig(component, true));
} else if (component.isXType('button')) {
menu.add(me.createMenuConfig(component, !component.menu));
} else if (component.isXType('buttongroup')) {
component.items.each(function(item){
me.addComponentToMenu(menu, item);
});
} else {
menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
}
}
},
* @private
* Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
* splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
*/
clearMenu : function() {
var menu = this.moreMenu;
if (menu && menu.items) {
menu.items.each(function(item) {
if (item.menu) {
delete item.menu;
}
});
}
},
* @private
* Creates the overflow trigger and menu used when enableOverflow is set to true and the items
* in the layout are too wide to fit in the space available
*/
createMenu: function(calculations, targetSize) {
var me = this,
layout = me.layout,
startProp = layout.parallelBefore,
sizeProp = layout.parallelPrefix,
available = targetSize[sizeProp],
boxes = calculations.boxes,
i = 0,
len = boxes.length,
box;
if (!me.menuTrigger) {
me.createInnerElements();
* @private
* @property menu
* @type Ext.menu.Menu
* The expand menu - holds items for every item that cannot be shown
* because the container is currently not large enough.
*/
me.menu = Ext.create('Ext.menu.Menu', {
listeners: {
scope: me,
beforeshow: me.beforeMenuShow
}
});
* @private
* @property menuTrigger
* @type Ext.button.Button
* The expand button which triggers the overflow menu to be shown
*/
me.menuTrigger = Ext.create('Ext.button.Button', {
ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
iconCls : Ext.baseCSSPrefix + layout.owner.getXType() + '-more-icon',
ui : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
menu : me.menu,
getSplitCls: function() { return '';},
renderTo: me.afterCt
});
}
me.showTrigger();
available -= me.afterCt.getWidth();
// Hide all items which are off the end, and store them to allow them to be restored
// before each layout operation.
me.menuItems.length = 0;
for (; i < len; i++) {
box = boxes[i];
if (box[startProp] + box[sizeProp] > available) {
me.menuItems.push(box.component);
box.component.hide();
}
}
},
* @private
* Creates the beforeCt, innerCt and afterCt elements if they have not already been created
* @param {Ext.container.Container} container The Container attached to this Layout instance
* @param {Ext.core.Element} target The target Element
*/
createInnerElements: function() {
var me = this,
target = me.layout.getRenderTarget();
if (!this.afterCt) {
target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
this.afterCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
}
},
* @private
*/
destroy: function() {
Ext.destroy(this.menu, this.menuTrigger);
}
});