/**
* @class Ext.layout.container.Border
* @extends Ext.layout.container.Container
* <p>This is a multi-pane, application-oriented UI layout style that supports multiple
* nested panels, automatic bars between regions and built-in
* {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.</p>
* <p>This class is intended to be extended or created via the <code>layout:'border'</code>
* {@link Ext.container.Container#layout} config, and should generally not need to be created directly
* via the new keyword.</p>
* {@img Ext.layout.container.Border/Ext.layout.container.Border.png Ext.layout.container.Border container layout}
* <p>Example usage:</p>
* <pre><code>
Ext.create('Ext.panel.Panel', {
width: 500,
height: 400,
title: 'Border Layout',
layout: 'border',
items: [{
title: 'South Region is resizable',
region: 'south', // position for region
xtype: 'panel',
height: 100,
split: true, // enable resizing
margins: '0 5 5 5'
},{
// xtype: 'panel' implied by default
title: 'West Region is collapsible',
region:'west',
xtype: 'panel',
margins: '5 0 0 5',
width: 200,
collapsible: true, // make collapsible
id: 'west-region-container',
layout: 'fit'
},{
title: 'Center Region',
region: 'center', // center region is required, no width/height specified
xtype: 'panel',
layout: 'fit',
margins: '5 5 0 0'
}],
renderTo: Ext.getBody()
});
</code></pre>
* <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
* <li>Any Container using the Border layout <b>must</b> have a child item with <code>region:'center'</code>.
* The child item in the center region will always be resized to fill the remaining space not used by
* the other regions in the layout.</li>
* <li>Any child items with a region of <code>west</code> or <code>east</code> may be configured with either
* an initial <code>width</code>, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width <b>string</b> (Which is simply divided by 100 and used as a flex value). The 'center' region has a flex value of <code>1</code>.</li>
* <li>Any child items with a region of <code>north</code> or <code>south</code> may be configured with either
* an initial <code>height</code>, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height <b>string</b> (Which is simply divided by 100 and used as a flex value). The 'center' region has a flex value of <code>1</code>.</li>
* <li>The regions of a BorderLayout are <b>fixed at render time</b> and thereafter, its child Components may not be removed or added</b>.To add/remove
* Components within a BorderLayout, have them wrapped by an additional Container which is directly
* managed by the BorderLayout. If the region is to be collapsible, the Container used directly
* by the BorderLayout manager should be a Panel. In the following example a Container (an Ext.panel.Panel)
* is added to the west region:<pre><code>
wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
wrc.{@link Ext.container.Container#removeAll removeAll}();
wrc.{@link Ext.container.Container#add add}({
title: 'Added Panel',
html: 'Some content'
});
* </code></pre>
* </li>
* <li><b>There is no BorderLayout.Region class in ExtJS 4.0+</b></li>
* </ul></div>
*/
Ext.define('Ext.layout.container.Border', {
alias: ['layout.border'],
extend: 'Ext.layout.container.Container',
requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
alternateClassName: 'Ext.layout.BorderLayout',
targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
itemCls: Ext.baseCSSPrefix + 'border-item',
bindToOwnerCtContainer: true,
fixedLayout: false,
percentageRe: /(\d+)%/,
slideDirection: {
north: 't',
south: 'b',
west: 'l',
east: 'r'
},
constructor: function(config) {
this.initialConfig = config;
this.callParent(arguments);
},
onLayout: function() {
var me = this;
if (!me.borderLayoutInitialized) {
me.initializeBorderLayout();
}
// Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
me.shadowLayout.onLayout();
if (me.embeddedContainer) {
me.embeddedContainer.layout.onLayout();
}
// If the panel was originally configured with collapsed: true, it will have
// been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
if (!me.initialCollapsedComplete) {
Ext.iterate(me.regions, function(name, region){
if (region.borderCollapse) {
me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
}
});
me.initialCollapsedComplete = true;
}
},
isValidParent : function(item, target, position) {
if (!this.borderLayoutInitialized) {
this.initializeBorderLayout();
}
// Delegate this operation to the shadow "V" or "H" box layout.
return this.shadowLayout.isValidParent(item, target, position);
},
beforeLayout: function() {
if (!this.borderLayoutInitialized) {
this.initializeBorderLayout();
}
// Delegate this operation to the shadow "V" or "H" box layout.
this.shadowLayout.beforeLayout();
},
renderItems: function(items, target) {
//<debug>
Ext.Error.raise('This should not be called');
//</debug>
},
renderItem: function(item) {
//<debug>
Ext.Error.raise('This should not be called');
//</debug>
},
initializeBorderLayout: function() {
var me = this,
i = 0,
items = me.getLayoutItems(),
ln = items.length,
regions = (me.regions = {}),
vBoxItems = [],
hBoxItems = [],
horizontalFlex = 0,
verticalFlex = 0,
comp, percentage;
// Map of Splitters for each region
me.splitters = {};
// Map of regions
for (; i < ln; i++) {
comp = items[i];
regions[comp.region] = comp;
// Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
// This layout intercepts any initial collapsed state. Panel must not do this itself.
comp.borderCollapse = comp.collapsed;
delete comp.collapsed;
comp.on({
beforecollapse: me.onBeforeRegionCollapse,
beforeexpand: me.onBeforeRegionExpand,
destroy: me.onRegionDestroy,
scope: me
});
me.setupState(comp);
}
}
//<debug>
if (!regions.center) {
Ext.Error.raise("You must specify a center region when defining a BorderLayout.");
}
//</debug>
comp = regions.center;
if (!comp.flex) {
comp.flex = 1;
}
delete comp.width;
comp.maintainFlex = true;
// Begin the VBox and HBox item list.
comp = regions.west;
if (comp) {
comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
hBoxItems.push(comp);
if (comp.split) {
hBoxItems.push(me.splitters.west = me.createSplitter(comp));
}
percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
if (percentage) {
horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
delete comp.width;
}
}
comp = regions.north;
if (comp) {
comp.collapseDirection = Ext.Component.DIRECTION_TOP;
vBoxItems.push(comp);
if (comp.split) {
vBoxItems.push(me.splitters.north = me.createSplitter(comp));
}
percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
if (percentage) {
verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
delete comp.height;
}
}
// Decide into which Collection the center region goes.
if (regions.north || regions.south) {
if (regions.east || regions.west) {
// Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
xtype: 'container',
region: 'center',
id: me.owner.id + '-embedded-center',
cls: Ext.baseCSSPrefix + 'border-item',
flex: regions.center.flex,
maintainFlex: true,
layout: {
type: 'hbox',
align: 'stretch'
}
}));
hBoxItems.push(regions.center);
}
// No east or west: the original center goes straight into the vbox
else {
vBoxItems.push(regions.center);
}
}
// If we have no north or south, then the center is part of the HBox items
else {
hBoxItems.push(regions.center);
}
// Finish off the VBox and HBox item list.
comp = regions.south;
if (comp) {
comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
if (comp.split) {
vBoxItems.push(me.splitters.south = me.createSplitter(comp));
}
percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
if (percentage) {
verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
delete comp.height;
}
vBoxItems.push(comp);
}
comp = regions.east;
if (comp) {
comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
if (comp.split) {
hBoxItems.push(me.splitters.east = me.createSplitter(comp));
}
percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
if (percentage) {
horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
delete comp.width;
}
hBoxItems.push(comp);
}
// Create the injected "items" collections for the Containers.
// If we have north or south, then the shadow Container will be a VBox.
// If there are also east or west regions, its center will be a shadow HBox.
// If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
if (regions.north || regions.south) {
me.shadowContainer = Ext.create('Ext.container.Container', {
ownerCt: me.owner,
el: me.getTarget(),
layout: Ext.applyIf({
type: 'vbox',
align: 'stretch'
}, me.initialConfig)
});
me.createItems(me.shadowContainer, vBoxItems);
// Allow the Splitters to orientate themselves
if (me.splitters.north) {
me.splitters.north.ownerCt = me.shadowContainer;
}
if (me.splitters.south) {
me.splitters.south.ownerCt = me.shadowContainer;
}
// Inject items into the HBox Container if there is one - if there was an east or west.
if (me.embeddedContainer) {
me.embeddedContainer.ownerCt = me.shadowContainer;
me.createItems(me.embeddedContainer, hBoxItems);
// Allow the Splitters to orientate themselves
if (me.splitters.east) {
me.splitters.east.ownerCt = me.embeddedContainer;
}
if (me.splitters.west) {
me.splitters.west.ownerCt = me.embeddedContainer;
}
// The east or west region wanted a percentage
if (horizontalFlex) {
regions.center.flex -= horizontalFlex;
}
// The north or south region wanted a percentage
if (verticalFlex) {
me.embeddedContainer.flex -= verticalFlex;
}
} else {
// The north or south region wanted a percentage
if (verticalFlex) {
regions.center.flex -= verticalFlex;
}
}
}
// If we have no north or south, then there's only one Container, and it's
// an HBox, or, if only a center region was specified, a Fit.
else {
me.shadowContainer = Ext.create('Ext.container.Container', {
ownerCt: me.owner,
el: me.getTarget(),
layout: Ext.applyIf({
type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
align: 'stretch'
}, me.initialConfig)
});
me.createItems(me.shadowContainer, hBoxItems);
// Allow the Splitters to orientate themselves
if (me.splitters.east) {
me.splitters.east.ownerCt = me.shadowContainer;
}
if (me.splitters.west) {
me.splitters.west.ownerCt = me.shadowContainer;
}
// The east or west region wanted a percentage
if (horizontalFlex) {
regions.center.flex -= verticalFlex;
}
}
// Create upward links from the region Components to their shadow ownerCts
for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
items[i].shadowOwnerCt = me.shadowContainer;
}
if (me.embeddedContainer) {
for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
items[i].shadowOwnerCt = me.embeddedContainer;
}
}
// This is the layout that we delegate all operations to
me.shadowLayout = me.shadowContainer.getLayout();
me.borderLayoutInitialized = true;
},
setupState: function(comp){
var getState = comp.getState;
comp.getState = function(){
// call the original getState
var state = getState.call(comp) || {},
region = comp.region;
state.collapsed = !!comp.collapsed;
if (region == 'west' || region == 'east') {
state.width = comp.getWidth();
} else {
state.height = comp.getHeight();
}
return state;
};
comp.addStateEvents(['collapse', 'expand', 'resize']);
},
/**
* Create the items collection for our shadow/embedded containers
* @private
*/
createItems: function(container, items){
// Have to inject an items Collection *after* construction.
// The child items of the shadow layout must retain their original, user-defined ownerCt
delete container.items;
container.initItems();
container.items.addAll(items);
},
// Private
// Create a splitter for a child of the layout.
createSplitter: function(comp) {
var me = this,
interceptCollapse = (comp.collapseMode != 'header'),
resizer;
resizer = Ext.create('Ext.resizer.Splitter', {
hidden: !!comp.hidden,
collapseTarget: comp,
performCollapse: !interceptCollapse,
listeners: interceptCollapse ? {
click: {
fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
element: 'collapseEl'
}
} : null
});
// Mini collapse means that the splitter is the placeholder Component
if (comp.collapseMode == 'mini') {
comp.placeholder = resizer;
}
// Arrange to hide/show a region's associated splitter when the region is hidden/shown
comp.on({
hide: me.onRegionVisibilityChange,
show: me.onRegionVisibilityChange,
scope: me
});
return resizer;
},
// Hide/show a region's associated splitter when the region is hidden/shown
onRegionVisibilityChange: function(comp){
this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
this.layout();
},
// Called when a splitter mini-collapse tool is clicked on.
// The listener is only added if this layout is controlling collapsing,
// not if the component's collapseMode is 'mini' or 'header'.
onSplitterCollapseClick: function(comp) {
if (comp.collapsed) {
this.onPlaceHolderToolClick(null, null, null, {client: comp});
} else {
comp.collapse();
}
},
/**
* <p>Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the layout will collapse.
* By default, this will be a {@link Ext.panel.Header Header} component (Docked to the appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}.
* config to customize this.</p>
* <p><b>Note that this will be a fully instantiated Component, but will only be <i>rendered</i> when the Panel is first collapsed.</b></p>
* @param {Panel} panel The child Panel of the layout for which to return the {@link Ext.panel.Panel#placeholder placeholder}.
* @returns {Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link Ext.panel.Panel#collapseMode collapseMode} is
* <code>'header'</code>, in which case <i>undefined</i> is returned.
*/
getPlaceholder: function(comp) {
var me = this,
placeholder = comp.placeholder,
shadowContainer = comp.shadowOwnerCt,
shadowLayout = shadowContainer.layout,
oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
horiz = (comp.region == 'north' || comp.region == 'south');
// No placeholder if the collapse mode is not the Border layout default
if (comp.collapseMode == 'header') {
return;
}
// Provide a replacement Container with an expand tool
if (!placeholder) {
if (comp.collapseMode == 'mini') {
placeholder = Ext.create('Ext.resizer.Splitter', {
id: 'collapse-placeholder-' + comp.id,
collapseTarget: comp,
performCollapse: false,
listeners: {
click: {
fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
element: 'collapseEl'
}
}
});
placeholder.addCls(placeholder.collapsedCls);
} else {
placeholder = {
id: 'collapse-placeholder-' + comp.id,
margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
xtype: 'header',
orientation: horiz ? 'horizontal' : 'vertical',
title: comp.title,
textCls: comp.headerTextCls,
iconCls: comp.iconCls,
baseCls: comp.baseCls + '-header',
ui: comp.ui,
indicateDrag: comp.draggable,
cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder',
listeners: comp.floatable ? {
click: {
fn: function(e) {
me.floatCollapsedPanel(e, comp);
},
element: 'el'
}
} : null
};
// Hack for IE6/7/IEQuirks's inability to display an inline-block
if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
placeholder.width = 25;
}
placeholder[horiz ? 'tools' : 'items'] = [{
xtype: 'tool',
client: comp,
type: 'expand-' + oppositeDirection,
handler: me.onPlaceHolderToolClick,
scope: me
}];
}
placeholder = me.owner.createComponent(placeholder);
if (comp.isXType('panel')) {
comp.on({
titlechange: me.onRegionTitleChange,
iconchange: me.onRegionIconChange,
scope: me
});
}
}
// The collapsed Component holds a reference to its placeholder and vice versa
comp.placeholder = placeholder;
placeholder.comp = comp;
return placeholder;
},
/**
* @private
* Update the placeholder title when panel title has been set or changed.
*/
onRegionTitleChange: function(comp, newTitle) {
comp.placeholder.setTitle(newTitle);
},
/**
* @private
* Update the placeholder iconCls when panel iconCls has been set or changed.
*/
onRegionIconChange: function(comp, newIconCls) {
comp.placeholder.setIconCls(newIconCls);
},
/**
* @private
* Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
* when configured with a flex, calls this method on its ownerCt's layout.
* @param {Component} child The child Component to calculate the box for
* @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
*/
calculateChildBox: function(comp) {
var me = this;
if (me.shadowContainer.items.contains(comp)) {
return me.shadowContainer.layout.calculateChildBox(comp);
}
else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
return me.embeddedContainer.layout.calculateChildBox(comp);
}
},
/**
* @private
* Intercepts the Panel's own collapse event and perform's substitution of the Panel
* with a placeholder Header orientated in the appropriate dimension.
* @param comp The Panel being collapsed.
* @param direction
* @param animate
* @returns {Boolean} false to inhibit the Panel from performing its own collapse.
*/
onBeforeRegionCollapse: function(comp, direction, animate) {
var me = this,
compEl = comp.el,
miniCollapse = comp.collapseMode == 'mini',
shadowContainer = comp.shadowOwnerCt,
shadowLayout = shadowContainer.layout,
placeholder = comp.placeholder,
placeholderBox,
targetSize = shadowLayout.getLayoutTargetSize(),
sl = me.owner.suspendLayout,
scsl = shadowContainer.suspendLayout,
isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
// Do not trigger a layout during transition to collapsed Component
me.owner.suspendLayout = true;
shadowContainer.suspendLayout = true;
// Prevent upward notifications from downstream layouts
shadowLayout.layoutBusy = true;
if (shadowContainer.componentLayout) {
shadowContainer.componentLayout.layoutBusy = true;
}
me.shadowContainer.layout.layoutBusy = true;
me.layoutBusy = true;
me.owner.componentLayout.layoutBusy = true;
// Provide a replacement Container with an expand tool
if (!placeholder) {
placeholder = me.getPlaceholder(comp);
}
// placeholder already in place; show it.
if (placeholder.shadowOwnerCt === shadowContainer) {
placeholder.show();
}
// Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
// It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
// Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
else {
shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
placeholder.shadowOwnerCt = shadowContainer;
placeholder.ownerCt = me.owner;
}
// Flag the collapsing Component as hidden and show the placeholder.
// This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
// We hide or slideOut the Component's element
comp.hidden = true;
if (!placeholder.rendered) {
shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
}
// Jobs to be done after the collapse has been done
function afterCollapse() {
// Reinstate automatic laying out.
me.owner.suspendLayout = sl;
shadowContainer.suspendLayout = scsl;
delete shadowLayout.layoutBusy;
if (shadowContainer.componentLayout) {
delete shadowContainer.componentLayout.layoutBusy;
}
delete me.shadowContainer.layout.layoutBusy;
delete me.layoutBusy;
delete me.owner.componentLayout.layoutBusy;
// Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
comp.collapsed = true;
comp.fireEvent('collapse', comp);
}
/*
* Set everything to the new positions. Note that we
* only want to animate the collapse if it wasn't configured
* initially with collapsed: true
*/
if (comp.animCollapse && me.initialCollapsedComplete) {
shadowLayout.layout();
compEl.dom.style.zIndex = 100;
// If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
if (!miniCollapse) {
placeholder.el.hide();
}
compEl.slideOut(me.slideDirection[comp.region], {
duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
listeners: {
afteranimate: function() {
compEl.show().setLeftTop(-10000, -10000);
compEl.dom.style.zIndex = '';
// If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
if (!miniCollapse) {
placeholder.el.slideIn(me.slideDirection[comp.region], {
easing: 'linear',
duration: 100
});
}
afterCollapse();
}
}
});
} else {
compEl.setLeftTop(-10000, -10000);
shadowLayout.layout();
afterCollapse();
// Horrible workaround for https://sencha.jira.com/browse/EXTJSIV-1562
if (Ext.isIE) {
placeholder.setCalculatedSize(placeholder.el.getWidth());
}
}
return false;
},
// Hijack the expand operation to remove the placeholder and slide the region back in.
onBeforeRegionExpand: function(comp, animate) {
this.onPlaceHolderToolClick(null, null, null, {client: comp});
return false;
},
// Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
onPlaceHolderToolClick: function(e, target, owner, tool) {
var me = this,
comp = tool.client,
// Hide the placeholder unless it was the Component's preexisting splitter
hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
compEl = comp.el,
toCompBox,
placeholder = comp.placeholder,
placeholderEl = placeholder.el,
shadowContainer = comp.shadowOwnerCt,
shadowLayout = shadowContainer.layout,
curSize,
sl = me.owner.suspendLayout,
scsl = shadowContainer.suspendLayout,
isFloating;
// If the slide in is still going, stop it.
// This will either leave the Component in its fully floated state (which is processed below)
// or in its collapsed state. Either way, we expand it..
if (comp.getActiveAnimation()) {
comp.stopAnimation();
}
// If the Component is fully floated when they click the placeholder Tool,
// it will be primed with a slide out animation object... so delete that
// and remove the mouseout listeners
if (comp.slideOutAnim) {
// Remove mouse leave monitors
compEl.un(comp.panelMouseMon);
placeholderEl.un(comp.placeholderMouseMon);
delete comp.slideOutAnim;
delete comp.panelMouseMon;
delete comp.placeholderMouseMon;
// If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
isFloating = true;
}
// Do not trigger a layout during transition to expanded Component
me.owner.suspendLayout = true;
shadowContainer.suspendLayout = true;
// Prevent upward notifications from downstream layouts
shadowLayout.layoutBusy = true;
if (shadowContainer.componentLayout) {
shadowContainer.componentLayout.layoutBusy = true;
}
me.shadowContainer.layout.layoutBusy = true;
me.layoutBusy = true;
me.owner.componentLayout.layoutBusy = true;
// Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
// Find where the shadow Box layout plans to put the expanding Component.
comp.hidden = false;
comp.collapsed = false;
if (hidePlaceholder) {
placeholder.hidden = true;
}
toCompBox = shadowLayout.calculateChildBox(comp);
// Show the collapse tool in case it was hidden by the slide-in
if (comp.collapseTool) {
comp.collapseTool.show();
}
// If we're going to animate, we need to hide the component before moving it back into position
if (comp.animCollapse && !isFloating) {
compEl.setStyle('visibility', 'hidden');
}
compEl.setLeftTop(toCompBox.left, toCompBox.top);
// Equalize the size of the expanding Component prior to animation
// in case the layout area has changed size during the time it was collapsed.
curSize = comp.getSize();
if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
me.setItemSize(comp, toCompBox.width, toCompBox.height);
}
// Jobs to be done after the expand has been done
function afterExpand() {
// Reinstate automatic laying out.
me.owner.suspendLayout = sl;
shadowContainer.suspendLayout = scsl;
delete shadowLayout.layoutBusy;
if (shadowContainer.componentLayout) {
delete shadowContainer.componentLayout.layoutBusy;
}
delete me.shadowContainer.layout.layoutBusy;
delete me.layoutBusy;
delete me.owner.componentLayout.layoutBusy;
// In case it was floated out and they clicked the re-expand tool
comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
// Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
comp.fireEvent('expand', comp);
}
// Hide the placeholder
if (hidePlaceholder) {
placeholder.el.hide();
}
// Slide the expanding Component to its new position.
// When that is done, layout the layout.
if (comp.animCollapse && !isFloating) {
compEl.dom.style.zIndex = 100;
compEl.slideIn(me.slideDirection[comp.region], {
duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
listeners: {
afteranimate: function() {
compEl.dom.style.zIndex = '';
comp.hidden = false;
shadowLayout.onLayout();
afterExpand();
}
}
});
} else {
shadowLayout.onLayout();
afterExpand();
}
},
floatCollapsedPanel: function(e, comp) {
if (comp.floatable === false) {
return;
}
var me = this,
compEl = comp.el,
placeholder = comp.placeholder,
placeholderEl = placeholder.el,
shadowContainer = comp.shadowOwnerCt,
shadowLayout = shadowContainer.layout,
placeholderBox = shadowLayout.getChildBox(placeholder),
scsl = shadowContainer.suspendLayout,
curSize, toCompBox, compAnim;
// Ignore clicks on tools.
if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
return;
}
// It's *being* animated, ignore the click.
// Possible future enhancement: Stop and *reverse* the current active Fx.
if (compEl.getActiveAnimation()) {
return;
}
// If the Component is already fully floated when they click the placeholder,
// it will be primed with a slide out animation object... so slide it out.
if (comp.slideOutAnim) {
me.slideOutFloatedComponent(comp);
return;
}
// Function to be called when the mouse leaves the floated Panel
// Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
function onMouseLeaveFloated(e) {
var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
// If mouse is not within slide Region, slide it out
if (!slideRegion.contains(e.getPoint())) {
me.slideOutFloatedComponent(comp);
}
}
// Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
// Do not trigger a layout during slide out of the Component
shadowContainer.suspendLayout = true;
// Prevent upward notifications from downstream layouts
me.layoutBusy = true;
me.owner.componentLayout.layoutBusy = true;
// The collapse tool is hidden while slid.
// It is re-shown on expand.
if (comp.collapseTool) {
comp.collapseTool.hide();
}
// Set flags so that the layout will calculate the boxes for what we want
comp.hidden = false;
comp.collapsed = false;
placeholder.hidden = true;
// Recalculate new arrangement of the Component being floated.
toCompBox = shadowLayout.calculateChildBox(comp);
placeholder.hidden = false;
// Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
if (comp.region == 'north' || comp.region == 'west') {
toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
} else {
toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
}
compEl.setStyle('visibility', 'hidden');
compEl.setLeftTop(toCompBox.left, toCompBox.top);
// Equalize the size of the expanding Component prior to animation
// in case the layout area has changed size during the time it was collapsed.
curSize = comp.getSize();
if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
me.setItemSize(comp, toCompBox.width, toCompBox.height);
}
// This animation slides the collapsed Component's el out to just beyond its placeholder
compAnim = {
listeners: {
afteranimate: function() {
shadowContainer.suspendLayout = scsl;
delete me.layoutBusy;
delete me.owner.componentLayout.layoutBusy;
// Prime the Component with an Anim config object to slide it back out
compAnim.listeners = {
afterAnimate: function() {
compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
// Reinstate the correct, current state after slide out animation finishes
comp.hidden = true;
comp.collapsed = true;
delete comp.slideOutAnim;
delete comp.panelMouseMon;
delete comp.placeholderMouseMon;
}
};
comp.slideOutAnim = compAnim;
}
},
duration: 500
};
// Give the element the correct class which places it at a high z-index
compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
// Begin the slide in
compEl.slideIn(me.slideDirection[comp.region], compAnim);
// Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
},
slideOutFloatedComponent: function(comp) {
var compEl = comp.el,
slideOutAnim;
// Remove mouse leave monitors
compEl.un(comp.panelMouseMon);
comp.placeholder.el.un(comp.placeholderMouseMon);
// Slide the Component out
compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
delete comp.slideOutAnim;
delete comp.panelMouseMon;
delete comp.placeholderMouseMon;
},
/*
* @private
* Ensure any collapsed placeholder Component is destroyed along with its region.
* Can't do this in onDestroy because they may remove a Component and use it elsewhere.
*/
onRegionDestroy: function(comp) {
var placeholder = comp.placeholder;
if (placeholder) {
delete placeholder.ownerCt;
placeholder.destroy();
}
},
/*
* @private
* Ensure any shadow Containers are destroyed.
* Ensure we don't keep references to Components.
*/
onDestroy: function() {
var me = this,
shadowContainer = me.shadowContainer,
embeddedContainer = me.embeddedContainer;
if (shadowContainer) {
delete shadowContainer.ownerCt;
Ext.destroy(shadowContainer);
}
if (embeddedContainer) {
delete embeddedContainer.ownerCt;
Ext.destroy(embeddedContainer);
}
delete me.regions;
delete me.splitters;
delete me.shadowContainer;
delete me.embeddedContainer;
me.callParent(arguments);
}
});