/*!
* Ext JS Library 3.4.0
* Copyright(c) 2006-2011 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.layout.VBoxLayout
* @extends Ext.layout.BoxLayout
*
A layout that arranges items vertically down a Container. This layout optionally divides available vertical
* space between child items containing a numeric flex
configuration.
* This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
*/
Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
/**
* @cfg {String} align
* Controls how the child items of the container are aligned. Acceptable configuration values for this
* property are:
*
* - left : Default
child items are aligned horizontally
* at the left side of the container
* - center :
child items are aligned horizontally at the
* mid-width of the container
* - stretch :
child items are stretched horizontally to fill
* the width of the container
* - stretchmax :
child items are stretched horizontally to
* the size of the largest item.
*
*/
align : 'left', // left, center, stretch, strechmax
type: 'vbox',
/**
* @cfg {String} pack
* Controls how the child items of the container are packed together. Acceptable configuration values
* for this property are:
*
* - start : Default
child items are packed together at
* top side of container
* - center :
child items are packed together at
* mid-height of container
* - end :
child items are packed together at bottom
* side of container
*
*/
/**
* @cfg {Number} flex
* This configuation option is to be applied to child items of the container managed
* by this layout. Each child item with a flex property will be flexed vertically
* according to each item's relative flex value compared to the sum of all items with
* a flex value specified. Any child items that have either a flex = 0 or
* flex = undefined will not be 'flexed' (the initial size will not be changed).
*/
/**
* @private
* Calculates the size and positioning of each item in the VBox. This iterates over all of the rendered,
* visible items and returns a height, width, top and left for each, as well as a reference to each. Also
* returns meta data such as maxHeight which are useful when resizing layout wrappers such as this.innerCt.
* @param {Array} visibleItems The array of all rendered, visible items to be calculated for
* @param {Object} targetSize Object containing target size and height
* @return {Object} Object containing box measurements for each child, plus meta data
*/
calculateChildBoxes: function(visibleItems, targetSize) {
var visibleCount = visibleItems.length,
padding = this.padding,
topOffset = padding.top,
leftOffset = padding.left,
paddingVert = topOffset + padding.bottom,
paddingHoriz = leftOffset + padding.right,
width = targetSize.width - this.scrollOffset,
height = targetSize.height,
availWidth = Math.max(0, width - paddingHoriz),
isStart = this.pack == 'start',
isCenter = this.pack == 'center',
isEnd = this.pack == 'end',
nonFlexHeight= 0,
maxWidth = 0,
totalFlex = 0,
desiredHeight= 0,
minimumHeight= 0,
//used to cache the calculated size and position values for each child item
boxes = [],
//used in the for loops below, just declared here for brevity
child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedHeight,
horizMargins, vertMargins, stretchWidth, length;
//gather the total flex of all flexed items and the width taken up by fixed width items
for (i = 0; i < visibleCount; i++) {
child = visibleItems[i];
childHeight = child.height;
childWidth = child.width;
canLayout = !child.hasLayout && typeof child.doLayout == 'function';
// Static height (numeric) requires no calcs
if (typeof childHeight != 'number') {
// flex and not 'auto' height
if (child.flex && !childHeight) {
totalFlex += child.flex;
// Not flexed or 'auto' height or undefined height
} else {
//Render and layout sub-containers without a flex or width defined, as otherwise we
//don't know how wide the sub-container should be and cannot calculate flexed widths
if (!childHeight && canLayout) {
child.doLayout();
}
childSize = child.getSize();
childWidth = childSize.width;
childHeight = childSize.height;
}
}
childMargins = child.margins;
vertMargins = childMargins.top + childMargins.bottom;
nonFlexHeight += vertMargins + (childHeight || 0);
desiredHeight += vertMargins + (child.flex ? child.minHeight || 0 : childHeight);
minimumHeight += vertMargins + (child.minHeight || childHeight || 0);
// Max width for align - force layout of non-layed out subcontainers without a numeric width
if (typeof childWidth != 'number') {
if (canLayout) {
child.doLayout();
}
childWidth = child.getWidth();
}
maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right);
//cache the size of each child component
boxes.push({
component: child,
height : childHeight || undefined,
width : childWidth || undefined
});
}
var shortfall = desiredHeight - height,
tooNarrow = minimumHeight > height;
//the height available to the flexed items
var availableHeight = Math.max(0, (height - nonFlexHeight - paddingVert));
if (tooNarrow) {
for (i = 0, length = visibleCount; i < length; i++) {
boxes[i].height = visibleItems[i].minHeight || visibleItems[i].height || boxes[i].height;
}
} else {
//all flexed items should be sized to their minimum width, other items should be shrunk down until
//the shortfall has been accounted for
if (shortfall > 0) {
var minHeights = [];
/**
* When we have a shortfall but are not tooNarrow, we need to shrink the height of each non-flexed item.
* Flexed items are immediately reduced to their minHeight and anything already at minHeight is ignored.
* The remaining items are collected into the minHeights array, which is later used to distribute the shortfall.
*/
for (var index = 0, length = visibleCount; index < length; index++) {
var item = visibleItems[index],
minHeight = item.minHeight || 0;
//shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
//shrunk to their minHeight because they're flexible and should be the first to lose height
if (item.flex) {
boxes[index].height = minHeight;
} else {
minHeights.push({
minHeight: minHeight,
available: boxes[index].height - minHeight,
index : index
});
}
}
//sort by descending minHeight value
minHeights.sort(function(a, b) {
return a.available > b.available ? 1 : -1;
});
/*
* Distribute the shortfall (difference between total desired with of all items and actual height available)
* between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
* smallest difference between their height and minHeight first, so that if reducing the height by the average
* amount would make that item less than its minHeight, we carry the remainder over to the next item.
*/
for (var i = 0, length = minHeights.length; i < length; i++) {
var itemIndex = minHeights[i].index;
if (itemIndex == undefined) {
continue;
}
var item = visibleItems[itemIndex],
box = boxes[itemIndex],
oldHeight = box.height,
minHeight = item.minHeight,
newHeight = Math.max(minHeight, oldHeight - Math.ceil(shortfall / (length - i))),
reduction = oldHeight - newHeight;
boxes[itemIndex].height = newHeight;
shortfall -= reduction;
}
} else {
//temporary variables used in the flex height calculations below
var remainingHeight = availableHeight,
remainingFlex = totalFlex;
//calculate the height of each flexed item
for (i = 0; i < visibleCount; i++) {
child = visibleItems[i];
calcs = boxes[i];
childMargins = child.margins;
horizMargins = childMargins.left + childMargins.right;
if (isStart && child.flex && !child.height) {
flexedHeight = Math.ceil((child.flex / remainingFlex) * remainingHeight);
remainingHeight -= flexedHeight;
remainingFlex -= child.flex;
calcs.height = flexedHeight;
calcs.dirtySize = true;
}
}
}
}
if (isCenter) {
topOffset += availableHeight / 2;
} else if (isEnd) {
topOffset += availableHeight;
}
//finally, calculate the left and top position of each item
for (i = 0; i < visibleCount; i++) {
child = visibleItems[i];
calcs = boxes[i];
childMargins = child.margins;
topOffset += childMargins.top;
horizMargins = childMargins.left + childMargins.right;
calcs.left = leftOffset + childMargins.left;
calcs.top = topOffset;
switch (this.align) {
case 'stretch':
stretchWidth = availWidth - horizMargins;
calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
calcs.dirtySize = true;
break;
case 'stretchmax':
stretchWidth = maxWidth - horizMargins;
calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
calcs.dirtySize = true;
break;
case 'center':
var diff = availWidth - calcs.width - horizMargins;
if (diff > 0) {
calcs.left = leftOffset + horizMargins + (diff / 2);
}
}
topOffset += calcs.height + childMargins.bottom;
}
return {
boxes: boxes,
meta : {
maxWidth : maxWidth,
nonFlexHeight: nonFlexHeight,
desiredHeight: desiredHeight,
minimumHeight: minimumHeight,
shortfall : desiredHeight - height,
tooNarrow : tooNarrow
}
};
}
});
Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;