/**
* A simple grid-like layout for proportionally dividing container space and allocating it
* to each item. All items in this layout are given one or more percentage sizes and CSS
* `float:left` is used to provide the wrapping.
*
* To select which of the percentage sizes an item uses, this layout adds a viewport
* {@link #states size-dependent} class name to the container. The style sheet must
* provide the rules to select the desired size using the {@link #responsivecolumn-item}
* mixin.
*
* For example, a panel in a responsive column layout might add the following styles:
*
* .my-panel {
* // consume 50% of the available space inside the container by default
* @include responsivecolumn-item(50%);
*
* .x-responsivecolumn-small & {
* // consume 100% of available space in "small" mode
* // (viewport width < 1000 by default)
* @include responsivecolumn-item(100%);
* }
* }
*
* Alternatively, instead of targeting specific panels in CSS, you can create reusable
* classes:
*
* .big-50 {
* // consume 50% of the available space inside the container by default
* @include responsivecolumn-item(50%);
* }
*
* .x-responsivecolumn-small {
* > .small-100 {
* @include responsivecolumn-item(100%);
* }
* }
*
* These can be added to components in the layout using the `responsiveCls` config:
*
* items: [{
* xtype: 'my-panel',
*
* // Use 50% of space when viewport is "big" and 100% when viewport
* // is "small":
* responsiveCls: 'big-50 small-100'
* }]
*
* The `responsiveCls` config is provided by this layout to avoid overwriting classes
* specified using `cls` or other standard configs.
*
* Internally, this layout simply uses `float:left` and CSS `calc()` (except on IE8) to
* "flex" each item. The calculation is always based on a percentage with a spacing taken
* into account to separate the items from each other.
*/
Ext.define('Ext.ux.layout.ResponsiveColumn', {
extend: 'Ext.layout.container.Auto',
alias: 'layout.responsivecolumn',
/**
* @cfg {Object} states
*
* A set of layout state names corresponding to viewport size thresholds. One of the
* states will be used to assign the responsive column CSS class to the container to
* trigger appropriate item sizing.
*
* For example:
*
* layout: {
* type: 'responsivecolumn',
* states: {
* small: 800,
* medium: 1200,
* large: 0
* }
* }
*
* Given the above set of responsive states, one of the following CSS classes will be
* added to the container:
*
* - `x-responsivecolumn-small` - If the viewport is <= 800px
* - `x-responsivecolumn-medium` - If the viewport is > 800px and <= 1200px
* - `x-responsivecolumn-large` - If the viewport is > 1200px
*
* For sake of efficiency these classes are based on the size of the browser viewport
* (the browser window) and not on the container size. As the size of the viewport
* changes, this layout will maintain the appropriate CSS class on the container which
* will then activate the appropriate CSS rules to size the child items.
*/
states: {
small: 1000,
large: 0
},
_responsiveCls: Ext.baseCSSPrefix + 'responsivecolumn',
initLayout: function() {
this.innerCtCls += ' ' + this._responsiveCls;
this.callParent();
},
beginLayout: function(ownerContext) {
var me = this,
viewportWidth = Ext.Element.getViewportWidth(),
states = me.states,
activeThreshold = Infinity,
innerCt = me.innerCt,
currentState = me._currentState,
name, threshold, newState;
for (name in states) {
threshold = states[name] || Infinity;
if (viewportWidth <= threshold && threshold <= activeThreshold) {
activeThreshold = threshold;
newState = name;
}
}
if (newState !== currentState) {
innerCt.replaceCls(currentState, newState, me._responsiveCls);
me._currentState = newState;
}
me.callParent(arguments);
},
onAdd: function(item) {
var responsiveCls;
this.callParent([item]);
responsiveCls = item.responsiveCls;
if (responsiveCls) {
item.addCls(responsiveCls);
}
}
}, function(Responsive) {
//--------------------------------------------------------------------------------------
// IE8 does not support CSS calc expressions, so we have to fallback to more traditional
// for of layout. This is very similar but much simpler than Column layout.
//
if (Ext.isIE8) {
Responsive.override({
responsiveSizePolicy: {
readsWidth: 0,
readsHeight: 0,
setsWidth: 1,
setsHeight: 0
},
setsItemSize: true,
calculateItems: function(ownerContext, containerSize) {
var me = this,
targetContext = ownerContext.targetContext,
items = ownerContext.childItems,
len = items.length,
gotWidth = containerSize.gotWidth,
contentWidth = containerSize.width,
i, itemContext, itemMarginWidth, itemWidth;
// No parallel measurement, cannot lay out boxes.
if (gotWidth === false) {
targetContext.domBlock(me, 'width');
return false;
}
if (!gotWidth) {
// gotWidth is undefined, which means we must be width shrink wrap.
// Cannot calculate item widths if we're shrink wrapping.
return true;
}
for (i = 0; i < len; ++i) {
itemContext = items[i];
// The mixin encodes these in background-position syles since it is
// unlikely a component will have a background-image.
itemWidth = parseInt(itemContext.el.getStyle('background-position-x'), 10);
itemMarginWidth =
parseInt(itemContext.el.getStyle('background-position-y'), 10);
itemContext.setWidth(
(itemWidth / 100 * (contentWidth - itemMarginWidth)) - itemMarginWidth
);
}
ownerContext.setContentWidth(contentWidth +
ownerContext.paddingContext.getPaddingInfo().width);
return true;
},
getItemSizePolicy: function() {
return this.responsiveSizePolicy;
}
});
}
});