/**
* This class is intended to be extended or created via the
* {@link Ext.Component#componentLayout layout} configuration property.
* See {@link Ext.Component#componentLayout} for additional details.
* @private
*/
Ext.define('Ext.layout.component.Component', {
extend: 'Ext.layout.Layout',
type: 'component',
isComponentLayout: true,
nullBox: {},
usesContentHeight: true,
usesContentWidth: true,
usesHeight: true,
usesWidth: true,
widthCache: {},
heightCache: {},
beginLayoutCycle: function(ownerContext, firstCycle) {
var me = this,
owner = me.owner,
ownerCtContext = ownerContext.ownerCtContext,
heightModel = ownerContext.heightModel,
widthModel = ownerContext.widthModel,
body = owner.el.dom === document.body,
lastBox = owner.lastBox || me.nullBox,
lastSize = owner.el.lastBox || me.nullBox,
dirty = !body,
isTopLevel = ownerContext.isTopLevel,
ownerLayout, v, width, height, scroller;
me.callParent([ownerContext, firstCycle]);
if (firstCycle) {
scroller = owner.getScrollable && owner.getScrollable();
if (scroller) {
scroller.flushOnDomScrollEnd();
}
if (me.usesContentWidth) {
++ownerContext.consumersContentWidth;
}
if (me.usesContentHeight) {
++ownerContext.consumersContentHeight;
}
if (me.usesWidth) {
++ownerContext.consumersWidth;
}
if (me.usesHeight) {
++ownerContext.consumersHeight;
}
if (ownerCtContext && !ownerCtContext.hasRawContent) {
ownerLayout = owner.ownerLayout;
if (ownerLayout) {
if (ownerLayout.usesWidth) {
++ownerContext.consumersWidth;
}
if (ownerLayout.usesHeight) {
++ownerContext.consumersHeight;
}
}
}
}
// we want to publish configured dimensions as early as possible and since this is
// a write phase...
if (widthModel.configured) {
// If the owner.el is the body, owner.width is not dirty (we don't want to write
// it to the body el). For other el's, the width may already be correct in the
// DOM (e.g., it is rendered in the markup initially). If the width is not
// correct in the DOM, this is only going to be the case on the first cycle.
width = owner[widthModel.names.width];
if (isTopLevel && widthModel.calculatedFrom) {
width = lastBox.width;
}
if (!body) {
dirty = me.setWidthInDom ||
(firstCycle ? width !== lastSize.width : widthModel.constrained);
}
ownerContext.setWidth(width, dirty);
}
else if (isTopLevel) {
if (widthModel.calculated) {
v = lastBox.width;
ownerContext.setWidth(v, /* dirty= */v !== lastSize.width);
}
else if (widthModel.calculatedFromNatural) {
owner.el.dom.style.width = owner.width;
}
v = lastBox.x;
ownerContext.setProp('x', v, /* dirty= */v !== lastSize.x);
}
if (heightModel.configured) {
height = owner[heightModel.names.height];
if (isTopLevel && heightModel.calculatedFrom) {
height = lastBox.height;
}
if (!body) {
dirty = firstCycle ? height !== lastSize.height : heightModel.constrained;
}
ownerContext.setHeight(height, dirty);
}
else if (isTopLevel) {
if (heightModel.calculated) {
v = lastBox.height;
ownerContext.setHeight(v, v !== lastSize.height);
}
else if (heightModel.calculatedFromNatural) {
owner.el.dom.style.height = owner.height;
}
v = lastBox.y;
ownerContext.setProp('y', v, /* dirty= */v !== lastSize.y);
}
},
finishedLayout: function(ownerContext) {
var me = this,
elementChildren = ownerContext.children,
owner = me.owner,
len, i, elContext, lastBox, props;
// NOTE: In the code below we cannot use getProp because that will generate
// a layout dependency
// Set lastBox on managed child Elements.
// So that ContextItem.constructor can snag the lastBox for use by its undo method.
if (elementChildren) {
len = elementChildren.length;
for (i = 0; i < len; i++) {
elContext = elementChildren[i];
elContext.el.lastBox = elContext.props;
}
}
// Cache the size from which we are changing so that notifyOwner
// can notify the owningComponent with all essential information
ownerContext.previousSize = me.lastComponentSize;
// Cache the currently layed out size
me.lastComponentSize = owner.el.lastBox = props = ownerContext.props;
// lastBox is a copy of the defined props to allow save/restore of these (panel
// collapse needs this)
lastBox = owner.lastBox || (owner.lastBox = {});
lastBox.x = props.x;
lastBox.y = props.y;
lastBox.width = props.width;
lastBox.height = props.height;
lastBox.invalid = false;
me.callParent([ownerContext]);
},
notifyOwner: function(ownerContext) {
var me = this,
currentSize = me.lastComponentSize,
prevSize = ownerContext.previousSize;
me.owner.afterComponentLayout(
currentSize.width, currentSize.height,
prevSize ? prevSize.width : undefined,
prevSize ? prevSize.height : undefined
);
},
/**
* Returns the owner component's resize element.
* @return {Ext.dom.Element}
*/
getTarget: function() {
return this.owner.el;
},
/**
* Returns the element into which rendering must take place.
* Defaults to the owner Component's encapsulating element.
*
* May be overridden in Component layout managers which implement an inner element.
* @return {Ext.dom.Element}
*/
getRenderTarget: function() {
return this.owner.el;
},
cacheTargetInfo: function(ownerContext) {
var me = this,
targetInfo = me.targetInfo,
target;
if (!targetInfo) {
target = ownerContext.getEl('getTarget', me);
me.targetInfo = targetInfo = {
padding: target.getPaddingInfo(),
border: target.getBorderInfo()
};
}
return targetInfo;
},
measureAutoDimensions: function(ownerContext, dimensions) {
// Subtle But Important:
//
// We don't want to call getProp/hasProp et.al. unless we in fact need that value
// for our results! If we call it and don't need it, the layout manager will think
// we depend on it and will schedule us again should it change.
var me = this,
owner = me.owner,
containerLayout = owner.layout,
heightModel = ownerContext.heightModel,
widthModel = ownerContext.widthModel,
boxParent = ownerContext.boxParent,
isBoxParent = ownerContext.isBoxParent,
target = ownerContext.target,
props = ownerContext.props,
isContainer,
ret = {
gotWidth: false,
gotHeight: false,
isContainer: (isContainer = !ownerContext.hasRawContent)
},
hv = dimensions || 3,
zeroWidth, zeroHeight,
needed = 0,
got = 0,
ready, size, temp, key, cache, dirty;
// Note: this method is called *a lot*, so we have to be careful not to waste any
// time or make useless calls or, especially, read the DOM when we can avoid it.
//---------------------------------------------------------------------
// Width
if (widthModel.shrinkWrap && ownerContext.consumersContentWidth) {
++needed;
zeroWidth = !(hv & 1);
if (isContainer) {
// as a componentLayout for a container, we rely on the container layout to
// produce contentWidth...
if (zeroWidth) {
ret.contentWidth = 0;
ret.gotWidth = true;
++got;
}
else if ((ret.contentWidth = ownerContext.getProp('contentWidth')) !== undefined) {
ret.gotWidth = true;
++got;
}
}
else {
size = props.contentWidth;
if (typeof size === 'number') { // if (already determined)
ret.contentWidth = size;
ret.gotWidth = true;
++got;
}
else {
if (zeroWidth) {
ready = true;
}
else if (!ownerContext.hasDomProp('containerChildrenSizeDone')) {
ready = false;
}
else if (isBoxParent || !boxParent || boxParent.widthModel.shrinkWrap) {
// if we have no boxParent, we are ready, but a shrinkWrap boxParent
// artificially provides width early in the measurement process so
// we are ready to go in that case as well...
ready = true;
}
else {
// lastly, we have a boxParent that will be given a width, so we
// can wait for that width to be set in order to properly measure
// whatever is inside...
ready = boxParent.hasDomProp('width');
}
if (ready) {
if (zeroWidth) {
temp = 0;
}
else if (containerLayout && containerLayout.measureContentWidth) {
// Allow the container layout to do the measurement since it
// may have a better idea of how to do it even with no items:
temp = containerLayout.measureContentWidth(ownerContext);
}
else {
if (target.cacheWidth) {
// if all instances of a given xtype/UI are the same size,
// only read the DOM once to measure the first instance.
// Thereafter, retrieve the width from the cache.
key = target.xtype + '-' + target.ui;
cache = me.widthCache;
temp = cache[key] ||
(cache[key] = me.measureContentWidth(ownerContext));
}
else {
temp = me.measureContentWidth(ownerContext);
}
}
if (!isNaN(ret.contentWidth = temp)) {
ownerContext.setContentWidth(temp, true);
ret.gotWidth = true;
++got;
}
}
}
}
}
else if (widthModel.natural && ownerContext.consumersWidth) {
++needed;
size = props.width;
// zeroWidth does not apply
if (typeof size === 'number') { // if (already determined)
ret.width = size;
ret.gotWidth = true;
++got;
}
else {
if (isBoxParent || !boxParent) {
ready = true;
}
else {
// lastly, we have a boxParent that will be given a width, so we
// can wait for that width to be set in order to properly measure
// whatever is inside...
ready = boxParent.hasDomProp('width');
}
if (ready) {
if (!isNaN(ret.width = me.measureOwnerWidth(ownerContext))) {
// if minWidth/maxWidth was specified, we need to mark this as dirty
// so the new ret.width is applied to this context.
dirty = !!((target.minWidth || target.maxWidth) &&
typeof target.width !== 'number');
ownerContext.setWidth(ret.width, dirty);
ret.gotWidth = true;
++got;
}
}
}
}
//---------------------------------------------------------------------
// Height
if (heightModel.shrinkWrap && ownerContext.consumersContentHeight) {
++needed;
zeroHeight = !(hv & 2);
if (isContainer) {
// don't ask unless we need to know...
if (zeroHeight) {
ret.contentHeight = 0;
ret.gotHeight = true;
++got;
}
// eslint-disable-next-line no-cond-assign, max-len
else if ((ret.contentHeight = ownerContext.getProp('contentHeight')) !== undefined) {
ret.gotHeight = true;
++got;
}
}
else {
size = props.contentHeight;
if (typeof size === 'number') { // if (already determined)
ret.contentHeight = size;
ret.gotHeight = true;
++got;
}
else {
if (zeroHeight) {
ready = true;
}
else if (!ownerContext.hasDomProp('containerChildrenSizeDone')) {
ready = false;
}
else if (owner.noWrap) {
ready = true;
}
else if (!widthModel.shrinkWrap) {
// fixed width, so we need the width to determine the height...
// eslint-disable-next-line max-len
ready = (ownerContext.bodyContext || ownerContext).hasDomProp('width');// && (!ownerContext.bodyContext || ownerContext.bodyContext.hasDomProp('width'));
}
else if (isBoxParent || !boxParent || boxParent.widthModel.shrinkWrap) {
// if we have no boxParent, we are ready, but an autoWidth boxParent
// artificially provides width early in the measurement process so
// we are ready to go in that case as well...
ready = true;
}
else {
// lastly, we have a boxParent that will be given a width, so we
// can wait for that width to be set in order to properly measure
// whatever is inside...
ready = boxParent.hasDomProp('width');
}
if (ready) {
if (zeroHeight) {
temp = 0;
}
else if (containerLayout && containerLayout.measureContentHeight) {
// Allow the container layout to do the measurement since it
// may have a better idea of how to do it even with no items:
temp = containerLayout.measureContentHeight(ownerContext);
}
else {
if (target.cacheHeight) {
// if all instances of a given xtype/UI are the same size,
// only read the DOM once to measure the first instance.
// Thereafter, retrieve the height from the cache.
key = target.xtype + '-' + target.ui;
cache = me.heightCache;
temp = cache[key] ||
(cache[key] = me.measureContentHeight(ownerContext));
}
else {
temp = me.measureContentHeight(ownerContext);
}
}
if (!isNaN(ret.contentHeight = temp)) {
ownerContext.setContentHeight(temp, true);
ret.gotHeight = true;
++got;
}
}
}
}
}
else if (heightModel.natural && ownerContext.consumersHeight) {
++needed;
size = props.height;
// zeroHeight does not apply
if (typeof size === 'number') { // if (already determined)
ret.height = size;
ret.gotHeight = true;
++got;
}
else {
if (isBoxParent || !boxParent) {
ready = true;
}
else {
// lastly, we have a boxParent that will be given a width, so we
// can wait for that width to be set in order to properly measure
// whatever is inside...
ready = boxParent.hasDomProp('width');
}
if (ready) {
if (!isNaN(ret.height = me.measureOwnerHeight(ownerContext))) {
// if minHeight/maxHeight was specified, we need to mark this as dirty
// so the new ret.height is applied to this context.
dirty = !!((target.minHeight || target.maxHeight) &&
typeof target.height !== 'number');
ownerContext.setHeight(ret.height, dirty);
ret.gotHeight = true;
++got;
}
}
}
}
if (boxParent) {
ownerContext.onBoxMeasured();
}
ret.gotAll = got === needed;
// see if we can avoid calling this method by storing something on ownerContext.
return ret;
},
measureContentWidth: function(ownerContext) {
// contentWidth includes padding, but not border, framing or margins
return ownerContext.el.getWidth() - ownerContext.getFrameInfo().width;
},
measureContentHeight: function(ownerContext) {
// contentHeight includes padding, but not border, framing or margins
return ownerContext.el.getHeight() - ownerContext.getFrameInfo().height;
},
measureOwnerHeight: function(ownerContext) {
return ownerContext.el.getHeight();
},
measureOwnerWidth: function(ownerContext) {
return ownerContext.el.getWidth();
}
});