/**
* This is the layout style of choice for creating structural layouts in a multi-column format
* where the width of each column can be specified as a percentage or fixed width, but the height
* is allowed to vary based on the content. This class is intended to be extended or created
* via the `layout: 'column'` {@link Ext.container.Container#layout} config, and should generally
* not need to be created directly via the new keyword.
*
* ColumnLayout does not have any direct config options (other than inherited ones), but it does
* support a specific config property of `columnWidth` that can be included in the config
* of any panel added to it. The layout will use the columnWidth (if present) or width of each panel
* during layout to determine how to size each panel. If width or columnWidth is not specified
* for a given panel, its width will default to the panel's width (or auto).
*
* The width property is always evaluated as pixels, and must be a number greater than or equal
* to 1. The columnWidth property is always evaluated as a percentage, and must be a decimal value
* greater than 0 and less than 1 (e.g., .25).
*
* The basic rules for specifying column widths are pretty simple. The logic makes two passes
* through the set of contained panels. During the first layout pass, all panels that either
* have a fixed width or none specified (auto) are skipped, but their widths are subtracted
* from the overall container width.
*
* During the second pass, all panels with columnWidths are assigned pixel widths in proportion
* to their percentages based on the total **remaining** container width. In other words,
* percentage width panels are designed to fill the space left over by all the fixed-width
* and/or auto-width panels. Because of this, while you can specify any number of columns
* with different percentages, the columnWidths must always add up to 1 (or 100%) when added
* together, otherwise your layout may not render as expected.
*
* @example
* // All columns are percentages -- they must add up to 1
* Ext.create('Ext.panel.Panel', {
* title: 'Column Layout - Percentage Only',
* width: 350,
* height: 250,
* layout:'column',
* items: [{
* title: 'Column 1',
* columnWidth: 0.25
* }, {
* title: 'Column 2',
* columnWidth: 0.55
* }, {
* title: 'Column 3',
* columnWidth: 0.20
* }],
* renderTo: Ext.getBody()
* });
*
* // Mix of width and columnWidth -- all columnWidth values must add up
* // to 1. The first column will take up exactly 120px, and the last two
* // columns will fill the remaining container width.
*
* Ext.create('Ext.Panel', {
* title: 'Column Layout - Mixed',
* width: 350,
* height: 250,
* layout:'column',
* items: [{
* title: 'Column 1',
* width: 120
* }, {
* title: 'Column 2',
* columnWidth: 0.7
* }, {
* title: 'Column 3',
* columnWidth: 0.3
* }],
* renderTo: Ext.getBody()
* });
*/
Ext.define('Ext.layout.container.Column', {
extend: 'Ext.layout.container.Auto',
alternateClassName: 'Ext.layout.ColumnLayout',
alias: 'layout.column',
type: 'column',
itemCls: Ext.baseCSSPrefix + 'column',
targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
// The clear value to force floats to wrap back to zero
clearSide: 'left',
// Columns with a columnWidth have their width managed.
columnWidthSizePolicy: {
readsWidth: 0,
readsHeight: 1,
setsWidth: 1,
setsHeight: 0
},
createsInnerCt: true,
manageOverflow: true,
// Column layout needs to set the size of items configured with columnWidth, and it
// needs to read the size of items with a configured width.
setsItemSize: true,
needsItemSize: true,
isItemShrinkWrap: function(item) {
return true;
},
getItemSizePolicy: function(item, ownerSizeModel) {
if (item.columnWidth) {
if (!ownerSizeModel) {
ownerSizeModel = this.owner.getSizeModel();
}
if (!ownerSizeModel.width.shrinkWrap) {
return this.columnWidthSizePolicy;
}
}
return this.autoSizePolicy;
},
calculateItems: function(ownerContext, containerSize) {
var me = this,
columnCount = me.columnCount,
targetContext = ownerContext.targetContext,
items = ownerContext.childItems,
len = items.length,
contentWidth = 0,
gotWidth = containerSize.gotWidth,
blocked, availableWidth, i, itemContext, itemMarginWidth, itemWidth;
// No parallel measurement, cannot lay out boxes.
if (gotWidth === false) { // \\ TODO: Deal with target padding width
// TODO: only block if we have items with columnWidth
targetContext.domBlock(me, 'width');
blocked = true;
}
else if (gotWidth) {
availableWidth = containerSize.width;
}
else {
// gotWidth is undefined, which means we must be width shrink wrap.
// cannot calculate columnWidths if we're shrink wrapping.
return true;
}
// we need the widths of the columns we don't manage to proceed so we block on them
// if they are not ready...
for (i = 0; i < len; ++i) {
itemContext = items[i];
// Ensure that each row start clears to start of row.
// Tall items would block it as below.
// "Item 4" requires clear:left to begin at column zero.
// +------------------------------- +
// |+--------+ +--------+ +--------+|
// || | | | | ||
// || Item 1 | | Item 2 | | Item 3 ||
// || | +--------+ +--------+|
// || | +--------+ |
// |+--------+ | | |
// | | Item 4 | |
// | | | |
// | +--------+ |
// +--------------------------------+
if (columnCount) {
if (i % columnCount) {
itemContext.setProp('clear', null);
}
else {
itemContext.setProp('clear', me.clearSide);
}
}
// this is needed below for non-calculated columns, but is also needed in the
// next loop for calculated columns... this way we only call getMarginInfo in
// this loop and use the marginInfo property in the next...
itemMarginWidth = itemContext.getMarginInfo().width;
if (!itemContext.widthModel.calculated) {
itemWidth = itemContext.getProp('width');
if (typeof itemWidth !== 'number') {
itemContext.block(me, 'width');
blocked = true;
}
contentWidth += itemWidth + itemMarginWidth;
}
}
if (!blocked) {
contentWidth = me.calculateItemSizeWithContent(availableWidth, contentWidth, items);
ownerContext.setContentWidth(
contentWidth + ownerContext.paddingContext.getPaddingInfo().width
);
}
// we registered all the values that block this calculation, so abort now if blocked...
return !blocked;
},
calculateItemSizeWithContent: function(availableWidth, contentWidth, items) {
var itemMarginWidth, itemContext,
itemWidth, i,
len = items.length;
availableWidth = (availableWidth < contentWidth) ? 0 : availableWidth - contentWidth;
for (i = 0; i < len; ++i) {
itemContext = items[i];
if (itemContext.widthModel.calculated) {
itemMarginWidth = itemContext.marginInfo.width; // always set by above loop
itemWidth = itemContext.target.columnWidth;
itemWidth = Math.floor(itemWidth * availableWidth) - itemMarginWidth;
itemWidth = itemContext.setWidth(itemWidth); // constrains to min/maxWidth
contentWidth += itemWidth + itemMarginWidth;
}
}
return contentWidth;
}
});