/**
* This class manages state information for a component or element during a layout.
*
* # Blocks
*
* A "block" is a required value that is preventing further calculation. When a layout has
* encountered a situation where it cannot possibly calculate results, it can associate
* itself with the context item and missing property so that it will not be rescheduled
* until that property is set.
*
* Blocks are a one-shot registration. Once the property changes, the block is removed.
*
* Be careful with blocks. If *any* further calculations can be made, a block is not the
* right choice.
*
* # Triggers
*
* Whenever any call to {@link #getProp}, {@link #getDomProp}, {@link #hasProp} or
* {@link #hasDomProp} is made, the current layout is automatically registered as being
* dependent on that property in the appropriate state. Any changes to the property will
* trigger the layout and it will be queued in the {@link Ext.layout.Context}.
*
* Triggers, once added, remain for the entire layout. Any changes to the property will
* reschedule all unfinished layouts in their trigger set.
*
* @private
*/
Ext.define('Ext.layout.ContextItem', {
heightModel: null,
widthModel: null,
sizeModel: null,
/**
* There are several cases that allow us to skip (opt out) of laying out a component
* and its children as long as its `lastBox` is not marked as `invalid`. If anything
* happens to change things, the `lastBox` is marked as `invalid` by `updateLayout`
* as it ascends the component hierarchy.
*
* @property {Boolean} optOut
* @private
* @readonly
*/
optOut: false,
ownerSizePolicy: null, // plaed here by Component.getSizeModel
boxChildren: null,
boxParent: null,
children: [],
dirty: null,
// The number of dirty properties
dirtyCount: 0,
hasRawContent: true,
isContextItem: true,
isTopLevel: false,
consumersContentHeight: 0,
consumersContentWidth: 0,
consumersContainerHeight: 0,
consumersContainerWidth: 0,
consumersHeight: 0,
consumersWidth: 0,
ownerCtContext: null,
remainingChildDimensions: 0,
// the current set of property values:
props: null,
/**
* @property {Object} state
* State variables that are cleared when invalidated. Only applies to component items.
*/
state: null,
/**
* @property {Boolean} wrapsComponent
* True if this item wraps a Component (rather than an Element).
* @readonly
*/
wrapsComponent: false,
constructor: function(config) {
var me = this,
sizeModels = Ext.layout.SizeModel.sizeModels,
configured = sizeModels.configured,
shrinkWrap = sizeModels.shrinkWrap,
el, lastBox, ownerCt, ownerCtContext, props, sizeModel, target,
lastWidth, lastHeight, sameWidth, sameHeight, widthModel, heightModel, optOut;
Ext.apply(me, config);
target = me.target;
el = me.el;
me.id = target.id;
// These hold collections of layouts that are either blocked or triggered by sets
// to our properties (either ASAP or after flushing to the DOM). All of them have
// the same structure:
//
// me.blocks = {
// width: {
// 'layout-1001': layout1001
// }
// }
//
// The property name is the primary key which yields an object keyed by layout id
// with the layout instance as the value. This prevents duplicate entries for one
// layout and gives O(1) access to the layout instance when we need to iterate and
// process them.
//
// me.blocks = {};
// me.domBlocks = {};
// me.domTriggers = {};
// me.triggers = {};
me.flushedProps = {};
me.props = props = {};
// the set of cached styles for the element:
me.styles = {};
if (!target.isComponent) {
lastBox = el.lastBox;
}
else {
me.wrapsComponent = true;
me.framing = target.frameSize || null;
me.isComponentChild = target.ownerLayout && target.ownerLayout.isComponentLayout;
lastBox = target.lastBox;
// These items are created top-down, so the ContextItem of our ownerCt should
// be available (if it is part of this layout run).
ownerCt = target.ownerCt;
if (ownerCt && (ownerCtContext = ownerCt.el && me.context.items[ownerCt.el.id])) {
me.ownerCtContext = ownerCtContext;
}
// If our ownerCtContext is in the run, it will have a SizeModel that we use to
// optimize the determination of our sizeModel. Also see recalculateSizeModel, similar
// logic exists there.
me.sizeModel = sizeModel = target.getSizeModel(ownerCtContext &&
ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]);
// NOTE: The initial determination of sizeModel is valid (thankfully) and is
// needed to cope with adding components to a layout run on-the-fly (e.g., in
// the menu overflow handler of a box layout). Since this is the case, we do
// not need to recompute the sizeModel in init unless it is a "full" init (as
// our ownerCt's sizeModel could have changed in that case).
me.widthModel = widthModel = sizeModel.width;
me.heightModel = heightModel = sizeModel.height;
// The lastBox is populated early but does not get an "invalid" property
// until layout has occurred. The "false" value is placed in the lastBox
// by Component.finishedLayout.
if (lastBox && lastBox.invalid === false) {
sameWidth = (target.width === (lastWidth = lastBox.width));
sameHeight = (target.height === (lastHeight = lastBox.height));
if (widthModel === shrinkWrap && heightModel === shrinkWrap) {
optOut = true;
}
else if (widthModel === configured && sameWidth) {
optOut = heightModel === shrinkWrap ||
(heightModel === configured && sameHeight);
}
if (optOut) {
// Flag this component and capture its last size...
me.optOut = true;
props.width = lastWidth;
props.height = lastHeight;
}
}
}
me.lastBox = lastBox;
},
/**
* Clears all properties on this object except (perhaps) those not calculated by this
* component. This is more complex than it would seem because a layout can decide to
* invalidate its results and run the component's layouts again, but since some of the
* values may be calculated by the container, care must be taken to preserve those
* values.
*
* @param {Boolean} full True if all properties are to be invalidated, false to keep
* those calculated by the ownerCt.
* @param {Object} options
* @return {Mixed} A value to pass as the first argument to {@link #initContinue}.
* @private
*/
init: function(full, options) {
var me = this,
oldProps = me.props,
oldDirty = me.dirty,
ownerCtContext = me.ownerCtContext,
ownerLayout = me.target.ownerLayout,
firstTime = !me.state,
ret = full || firstTime,
children, i, n, ownerCt, sizeModel, target,
oldHeightModel = me.heightModel,
oldWidthModel = me.widthModel,
newHeightModel, newWidthModel,
remainingCount = 0;
me.dirty = me.invalid = false;
me.props = {};
// Reset the number of child dimensions since the children will add their part:
me.remainingChildDimensions = 0;
if (me.boxChildren) {
me.boxChildren.length = 0; // keep array (more GC friendly)
}
if (!firstTime) {
me.clearAllBlocks('blocks');
me.clearAllBlocks('domBlocks');
}
// For Element wrappers, we are done...
if (!me.wrapsComponent) {
return ret;
}
// From here on, we are only concerned with Component wrappers...
target = me.target;
me.state = {}; // only Component wrappers need a "state"
if (firstTime) {
// This must occur before we proceed since it can do many things (like add
// child items perhaps):
if (target.beforeLayout && target.beforeLayout !== Ext.emptyFn) {
target.beforeLayout();
}
// Determine the ownerCtContext if we aren't given one. Normally the firstTime
// we meet a component is before the context is run, but it is possible for
// components to be added to a run that is already in progress. If so, we have
// to lookup the ownerCtContext since the odds are very high that the new
// component is a child of something already in the run. It is currently
// unsupported to drag in the owner of a running component (needs testing).
if (!ownerCtContext && (ownerCt = target.ownerCt)) {
ownerCtContext = me.context.items[ownerCt.el.id];
}
if (ownerCtContext) {
me.ownerCtContext = ownerCtContext;
me.isBoxParent = ownerLayout && ownerLayout.isItemBoxParent(me);
}
else {
me.isTopLevel = true; // this is used by initAnimation...
}
me.frameBodyContext = me.getEl('frameBody');
}
else {
ownerCtContext = me.ownerCtContext;
// In theory (though untested), this flag can change on-the-fly...
me.isTopLevel = !ownerCtContext;
// Init the children element items since they may have dirty state (no need to
// do this the firstTime).
children = me.children;
for (i = 0, n = children.length; i < n; ++i) {
children[i].init(true);
}
}
// We need to know how we will determine content size: containers can look at the
// results of their items but non-containers or item-less containers with just raw
// markup need to be measured in the DOM:
me.hasRawContent = !(target.isContainer && target.items.items.length > 0);
if (full) {
// We must null these out or getSizeModel will assume they are the correct,
// dynamic size model and return them (the previous dynamic sizeModel).
me.widthModel = me.heightModel = null;
sizeModel = target.getSizeModel(ownerCtContext &&
ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]);
if (firstTime) {
me.sizeModel = sizeModel;
}
me.widthModel = sizeModel.width;
me.heightModel = sizeModel.height;
// if we are a container child (e.g., not a docked item), and this is a full
// init, that means our parent was invalidated, and therefore we must initialize
// our remainingChildDimensions to ensure that containerChildrenSizeDone
// gets set properly once all dimensions have had their sizes determined.
// There are 3 possible scenarios here:
//
// 1. Layouts that both read and set sizes of their items (e.g. box). These
// layouts must always add both dimensions to remainingChildDimensions.
//
// 2. Layouts that neither read nor set the size of their items (e.g.
// autocontainer, form). These layouts will not create context items for their
// children, and so we will never end up here.
//
// 3. Layouts that may set the size of their items, but will never read them
// because they measure an outer containing element in the shrink-wrapping
// dimension(s) (e.g. anchor, column). There are 2 possible outcomes:
// a. The child item uses liquid CSS layout. In this case, the only dimensions
// that affect containerChildrenSizeDone are the dimensions that the owner
// layout is responsible for calculating, and so these are the dimensions
// that are added to remainingChildDimensions. Non-calculated dimensions will
// never be published because the child's component layout does not run.
//
// b. The child item does not use liquid CSS layout. In this case, the
// component layout will run like normal, and any non-calculated dimensions
// will be published, therefore, we need to add both dimensions to
// remainingChildDimensions
if (ownerCtContext && !me.isComponentChild) {
if (ownerLayout.needsItemSize || !target.liquidLayout) {
ownerCtContext.remainingChildDimensions += 2;
}
else {
if (me.widthModel.calculated) {
++ownerCtContext.remainingChildDimensions;
}
if (me.heightModel.calculated) {
++ownerCtContext.remainingChildDimensions;
}
}
}
}
else if (oldProps) {
// these are almost always calculated by the ownerCt (we might need to track
// this at some point more carefully):
me.recoverProp('x', oldProps, oldDirty);
me.recoverProp('y', oldProps, oldDirty);
// if these are calculated by the ownerCt, don't trash them:
if (me.widthModel.calculated) {
me.recoverProp('width', oldProps, oldDirty);
}
else if ('width' in oldProps) {
++remainingCount;
}
if (me.heightModel.calculated) {
me.recoverProp('height', oldProps, oldDirty);
}
else if ('height' in oldProps) {
++remainingCount;
}
// if we are a container child and this is not a full init, that means our
// parent was not invalidated and therefore only the dimensions that were
// set last time and removed from remainingChildDimensions last time, need to
// be added back to remainingChildDimensions. This only needs to happen for
// properties that we don't recover above (model=calculated)
if (ownerCtContext && !me.isComponentChild) {
ownerCtContext.remainingChildDimensions += remainingCount;
}
}
if (oldProps && ownerLayout && ownerLayout.manageMargins) {
me.recoverProp('margin-top', oldProps, oldDirty);
me.recoverProp('margin-right', oldProps, oldDirty);
me.recoverProp('margin-bottom', oldProps, oldDirty);
me.recoverProp('margin-left', oldProps, oldDirty);
}
// Process any invalidate options present. These can only come from explicit calls
// to the invalidate() method.
if (options) {
// Consider a container box with wrapping text. If the box is made wider, the
// text will take up less height (until there is no more wrapping). Conversely,
// if the box is made narrower, the height starts to increase due to wrapping.
//
// Imposing a minWidth constraint would increase the width. This may decrease
// the height. If the box is shrinkWrap, however, the width will already be
// such that there is no wrapping, so the height will not further decrease.
// Since the height will also not increase if we widen the box, there is no
// problem simultaneously imposing a minHeight or maxHeight constraint.
//
// When we impose as maxWidth constraint, however, we are shrinking the box
// which may increase the height. If we are imposing a maxHeight constraint,
// that is fine because a further increased height will still need to be
// constrained. But if we are imposing a minHeight constraint, we cannot know
// whether the increase in height due to wrapping will be greater than the
// minHeight. If we impose a minHeight constraint at the same time, then, we
// could easily be locking in the wrong height.
//
// It is important to note that this logic applies to simultaneously *adding*
// both a maxWidth and a minHeight constraint. It is perfectly fine to have
// a state with both constraints, but we cannot add them both at once.
newHeightModel = options.heightModel;
newWidthModel = options.widthModel;
if (newWidthModel && newHeightModel && oldWidthModel && oldHeightModel) {
if (oldWidthModel.shrinkWrap && oldHeightModel.shrinkWrap) {
if (newWidthModel.constrainedMax && newHeightModel.constrainedMin) {
newHeightModel = null;
}
}
}
// Apply size model updates (if any) and state updates (if any).
if (newWidthModel) {
me.widthModel = newWidthModel;
}
if (newHeightModel) {
me.heightModel = newHeightModel;
}
if (options.state) {
Ext.apply(me.state, options.state);
}
}
return ret;
},
/**
* @private
*/
initContinue: function(full) {
var me = this,
ownerCtContext = me.ownerCtContext,
comp = me.target,
widthModel = me.widthModel,
inheritedState = comp.getInherited(),
boxParent;
if (widthModel.fixed) { // calculated or configured
inheritedState.inShrinkWrapTable = false;
}
else {
delete inheritedState.inShrinkWrapTable;
}
if (full) {
if (ownerCtContext && widthModel.shrinkWrap) {
boxParent = ownerCtContext.isBoxParent ? ownerCtContext : ownerCtContext.boxParent;
if (boxParent) {
boxParent.addBoxChild(me);
}
}
else if (widthModel.natural) {
me.boxParent = ownerCtContext;
}
}
return full;
},
/**
* @private
*/
initDone: function(containerLayoutDone) {
var me = this,
props = me.props,
state = me.state;
// These properties are only set when they are true:
if (me.remainingChildDimensions === 0) {
props.containerChildrenSizeDone = true;
}
if (containerLayoutDone) {
props.containerLayoutDone = true;
}
if (me.boxChildren && me.boxChildren.length && me.widthModel.shrinkWrap) {
// set a very large width to allow the children to measure their natural
// widths (this is cleared once all children have been measured):
me.el.setWidth(10000);
// don't run layouts for this component until we clear this width...
state.blocks = (state.blocks || 0) + 1;
}
},
/**
* @private
*/
initAnimation: function() {
var me = this,
target = me.target,
ownerCtContext = me.ownerCtContext;
if (ownerCtContext && ownerCtContext.isTopLevel) {
// See which properties we are supposed to animate to their new state.
// If there are any, queue ourself to be animated by the owning Context
me.animatePolicy = target.ownerLayout.getAnimatePolicy(me);
}
else if (!ownerCtContext && target.isCollapsingOrExpanding && target.animCollapse) {
// Collapsing/expnding a top level Panel with animation. We need to fabricate
// an animatePolicy depending on which dimension the collapse is using,
// isCollapsingOrExpanding is set during the collapse/expand process.
me.animatePolicy = target.componentLayout.getAnimatePolicy(me);
}
if (me.animatePolicy) {
me.context.queueAnimation(me);
}
},
/**
* Adds a block.
*
* @param {String} name The name of the block list ('blocks' or 'domBlocks').
* @param {Ext.layout.Layout} layout The layout that is blocked.
* @param {String} propName The property name that blocked the layout (e.g., 'width').
* @private
*/
addBlock: function(name, layout, propName) {
var me = this,
collection = me[name] || (me[name] = {}),
blockedLayouts = collection[propName] || (collection[propName] = {});
if (!blockedLayouts[layout.id]) {
blockedLayouts[layout.id] = layout;
++layout.blockCount;
++me.context.blockCount;
}
},
addBoxChild: function(boxChildItem) {
var me = this,
children,
widthModel = boxChildItem.widthModel;
boxChildItem.boxParent = this;
// Children that are widthModel.auto (regardless of heightModel) that measure the
// DOM (by virtue of hasRawContent), need to wait for their "box parent" to be sized.
// If they measure too early, they will be wrong results. In the widthModel.shrinkWrap
// case, the boxParent "crushes" the child. In the case of widthModel.natural, the
// boxParent's width is likely a key part of the child's width (e.g., "50%" or just
// normal block-level behavior of 100% width)
boxChildItem.measuresBox = widthModel.shrinkWrap
? boxChildItem.hasRawContent
: widthModel.natural;
if (boxChildItem.measuresBox) {
children = me.boxChildren;
if (children) {
children.push(boxChildItem);
}
else {
me.boxChildren = [ boxChildItem ];
}
}
},
/**
* Adds x and y values from a props object to a styles object as "left" and "top" values.
* Overridden to add the x property as "right" in rtl mode.
* @param {Object} styles A styles object for an Element
* @param {Object} props A ContextItem props object
* @return {Number} count The number of styles that were set.
* @private
*/
addPositionStyles: function(styles, props) {
var x = props.x,
y = props.y,
count = 0;
if (x !== undefined) {
styles[this.translateProps.x] = x + 'px';
++count;
}
if (y !== undefined) {
styles.top = y + 'px';
++count;
}
return count;
},
/**
* Adds a trigger.
*
* @param {String} propName The property name that triggers the layout (e.g., 'width').
* @param {Boolean} inDom True if the trigger list is `domTriggers`, false if `triggers`.
* @private
*/
addTrigger: function(propName, inDom) {
var me = this,
name = inDom ? 'domTriggers' : 'triggers',
collection = me[name] || (me[name] = {}),
context = me.context,
layout = context.currentLayout,
triggers = collection[propName] || (collection[propName] = {});
if (!triggers[layout.id]) {
triggers[layout.id] = layout;
++layout.triggerCount;
triggers = context.triggers[inDom ? 'dom' : 'data'];
(triggers[layout.id] || (triggers[layout.id] = [])).push({
item: this,
prop: propName
});
if (me.props[propName] !== undefined) {
if (!inDom || !(me.dirty && (propName in me.dirty))) {
++layout.firedTriggers;
}
}
}
},
boxChildMeasured: function() {
var me = this,
state = me.state,
count = (state.boxesMeasured = (state.boxesMeasured || 0) + 1);
if (count === me.boxChildren.length) {
// all of our children have measured themselves, so we can clear the width
// and resume layouts for this component...
state.clearBoxWidth = 1;
++me.context.progressCount;
me.markDirty();
}
},
borderNames: ['border-top-width', 'border-right-width', 'border-bottom-width',
'border-left-width'],
marginNames: ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'],
paddingNames: ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'],
trblNames: ['top', 'right', 'bottom', 'left'],
cacheMissHandlers: {
borderInfo: function(me) {
var info = me.getStyles(me.borderNames, me.trblNames);
info.width = info.left + info.right;
info.height = info.top + info.bottom;
return info;
},
marginInfo: function(me) {
var info = me.getStyles(me.marginNames, me.trblNames);
info.width = info.left + info.right;
info.height = info.top + info.bottom;
return info;
},
paddingInfo: function(me) {
// if this context item's target is a framed component the padding is on the frameBody,
// not on the main el
var item = me.frameBodyContext || me,
info = item.getStyles(me.paddingNames, me.trblNames);
info.width = info.left + info.right;
info.height = info.top + info.bottom;
return info;
}
},
checkCache: function(entry) {
return this.cacheMissHandlers[entry](this);
},
clearAllBlocks: function(name) {
var collection = this[name],
propName;
if (collection) {
for (propName in collection) {
this.clearBlocks(name, propName);
}
}
},
/**
* Removes any blocks on a property in the specified set. Any layouts that were blocked
* by this property and are not still blocked (by other properties) will be rescheduled.
*
* @param {String} name The name of the block list ('blocks' or 'domBlocks').
* @param {String} propName The property name that blocked the layout (e.g., 'width').
* @private
*/
clearBlocks: function(name, propName) {
var collection = this[name],
blockedLayouts = collection && collection[propName],
context, layout, layoutId;
if (blockedLayouts) {
delete collection[propName];
context = this.context;
for (layoutId in blockedLayouts) {
layout = blockedLayouts[layoutId];
--context.blockCount;
if (! --layout.blockCount && !layout.pending && !layout.done) {
context.queueLayout(layout);
}
}
}
},
/**
* Registers a layout in the block list for the given property. Once the property is
* set in the {@link Ext.layout.Context}, the layout is unblocked.
*
* @param {Ext.layout.Layout} layout
* @param {String} propName The property name that blocked the layout (e.g., 'width').
*/
block: function(layout, propName) {
this.addBlock('blocks', layout, propName);
},
/**
* Registers a layout in the DOM block list for the given property. Once the property
* flushed to the DOM by the {@link Ext.layout.Context}, the layout is unblocked.
*
* @param {Ext.layout.Layout} layout
* @param {String} propName The property name that blocked the layout (e.g., 'width').
*/
domBlock: function(layout, propName) {
this.addBlock('domBlocks', layout, propName);
},
/**
* Reschedules any layouts associated with a given trigger.
*
* @param {String} name The name of the trigger list ('triggers' or 'domTriggers').
* @param {String} propName The property name that triggers the layout (e.g., 'width').
* @private
*/
fireTriggers: function(name, propName) {
var collection = this[name],
triggers = collection && collection[propName],
context = this.context,
layout, layoutId;
if (triggers) {
for (layoutId in triggers) {
layout = triggers[layoutId];
++layout.firedTriggers;
if (!layout.done && !layout.blockCount && !layout.pending) {
context.queueLayout(layout);
}
}
}
},
/**
* Flushes any updates in the dirty collection to the DOM. This is only called if there
* are dirty entries because this object is only added to the flushQueue of the
* {@link Ext.layout.Context} when entries become dirty.
*/
flush: function() {
var me = this,
dirty = me.dirty,
state = me.state,
targetEl = me.el;
me.dirtyCount = 0;
// Set any queued DOM attributes
if ('attributes' in me) {
targetEl.set(me.attributes);
delete me.attributes;
}
// Set any queued DOM HTML content
if ('innerHTML' in me) {
targetEl.innerHTML = me.innerHTML;
delete me.innerHTML;
}
if (state && state.clearBoxWidth) {
state.clearBoxWidth = 0;
me.el.setStyle('width', null);
if (! --state.blocks) {
me.context.queueItemLayouts(me);
}
}
if (dirty) {
delete me.dirty;
me.writeProps(dirty, true);
}
},
/**
* @private
*/
flushAnimations: function() {
var me = this,
animateFrom = me.previousSize,
target, animQueue, targetAnim, duration, animateProps, anim,
changeCount, j, propsLen, propName, oldValue, newValue, flag;
// Only animate if the Component has been previously layed out:
// first layout should not animate
if (animateFrom) {
target = me.target;
targetAnim = target.getAnimationProps();
duration = targetAnim.duration;
animateProps = Ext.Object.getKeys(me.animatePolicy);
// Create an animation block using the targetAnim configuration to provide defaults.
// They may want custom duration, or easing, or listeners.
anim = Ext.apply({}, {
from: {},
to: {},
duration: duration || Ext.fx.Anim.prototype.duration
}, targetAnim);
for (changeCount = 0, j = 0, propsLen = animateProps.length; j < propsLen; j++) {
propName = animateProps[j];
oldValue = animateFrom[propName];
newValue = me.peek(propName);
if (oldValue !== newValue && newValue != null) {
propName = me.translateProps[propName] || propName;
anim.from[propName] = oldValue;
anim.to[propName] = newValue;
++changeCount;
}
}
// If any values have changed, kick off animation from the cached old values
// to the new values
if (changeCount) {
// It'a Panel being collapsed. rollback, and then fix the class name string
if (me.isCollapsingOrExpanding === 1) {
target.componentLayout.undoLayout(me);
}
// Otherwise, undo just the animated properties so the animation can proceed
// from the old layout.
else {
me.writeProps(anim.from);
}
me.el.animate(anim);
animQueue = Ext.fx.Manager.getFxQueue(me.el.id);
anim = animQueue[animQueue.length - 1];
target.$layoutAnim = anim;
anim.on({
afteranimate: function() {
var flag;
delete target.$layoutAnim;
// afteranimate can fire when the target is being destroyed
// and the animation queue is being stopped.
if (target.destroying || target.destroyed) {
return;
}
flag = me.isCollapsingOrExpanding;
if (flag === 1) {
target.componentLayout.redoLayout(me);
target.afterCollapse(true);
}
else if (flag === 2) {
target.afterExpand(true);
}
if (target.hasListeners.afterlayoutanimation) {
target.fireEvent('afterlayoutanimation', target);
}
}
});
}
// If no values were changed that could mean that the component
// started its lifecycle already collapsed and we simply don't have
// the proper expanded size. In such case we can't run the animation
// but still have to finish the expand sequence.
else {
flag = me.isCollapsingOrExpanding;
if (flag === 1) {
target.afterCollapse(true);
}
else if (flag === 2) {
target.afterExpand(true);
}
}
}
},
/**
* Gets the border information for the element as an object with left, top, right and
* bottom properties holding border size in pixels. This object is only read from the
* DOM on first request and is cached.
* @return {Object}
*/
getBorderInfo: function() {
var me = this,
info = me.borderInfo;
if (!info) {
me.borderInfo = info = me.checkCache('borderInfo');
}
return info;
},
/**
* @member Ext.layout.ContextItem
* Returns the context item for an owned element. This should only be called on a
* component's item. The list of child items is used to manage invalidating calculated
* results.
* @param {String/Ext.dom.Element} nameOrEl The element or the name of an owned element
* @param {Ext.layout.container.Container/Ext.Component} [owner] The owner of the
* named element if the passed "nameOrEl" parameter is a String. Defaults to this
* ContextItem's "target" property. For more details on owned elements see
* {@link Ext.Component#cfg-childEls childEls} and
* {@link Ext.Component#renderSelectors renderSelectors}
* @return {Ext.layout.ContextItem}
*/
getEl: function(nameOrEl, owner) {
var me = this,
src, el, elContext;
if (nameOrEl) {
if (nameOrEl.dom) {
el = nameOrEl;
}
else {
src = me.target;
if (owner) {
src = owner;
}
el = src[nameOrEl];
if (typeof el === 'function') { // ex 'getTarget'
el = el.call(src);
if (el === me.el) {
return this; // comp.getTarget() often returns comp.el
}
}
}
if (el) {
elContext = me.context.getEl(me, el);
}
}
return elContext || null;
},
/**
* Gets the "frame" information for the element as an object with left, top, right and
* bottom properties holding border+framing size in pixels. This object is calculated
* on first request and is cached.
* @return {Object}
*/
getFrameInfo: function() {
var me = this,
info = me.frameInfo,
framing, border;
if (!info) {
framing = me.framing;
border = me.getBorderInfo();
me.frameInfo = info = framing
? {
top: framing.top + border.top,
right: framing.right + border.right,
bottom: framing.bottom + border.bottom,
left: framing.left + border.left,
width: framing.width + border.width,
height: framing.height + border.height
}
: border;
}
return info;
},
/**
* Gets the margin information for the element as an object with left, top, right and
* bottom properties holding margin size in pixels. This object is only read from the
* DOM on first request and is cached.
* @return {Object}
*/
getMarginInfo: function() {
var me = this,
info = me.marginInfo,
comp, manageMargins, ownerLayout, ownerLayoutId;
if (!info) {
if (!me.wrapsComponent) {
info = me.checkCache('marginInfo');
}
else {
comp = me.target;
ownerLayout = comp.ownerLayout;
ownerLayoutId = ownerLayout ? ownerLayout.id : null;
manageMargins = ownerLayout && ownerLayout.manageMargins;
// TODO: stop caching margin$ on the component EXTJS-13359
info = comp.margin$;
if (info && info.ownerId !== ownerLayoutId) {
// got one but from the wrong owner
info = null;
}
if (!info) { // if (no cache)
// CSS margins are only checked if there isn't a margin property
// on the component
info = me.parseMargins(comp, comp.margin) || me.checkCache('marginInfo');
if (manageMargins) {
// TODO: Stop zeroing out the margins EXTJS-13359
me.setProp('margin-top', 0);
me.setProp('margin-right', 0);
me.setProp('margin-bottom', 0);
me.setProp('margin-left', 0);
}
// cache the layout margins and tag them with the layout id:
info.ownerId = ownerLayoutId;
comp.margin$ = info;
}
info.width = info.left + info.right;
info.height = info.top + info.bottom;
}
me.marginInfo = info;
}
return info;
},
/**
* Clears the margin cache so that marginInfo get re-read from the dom on the next call
* to getMarginInfo(). This is needed in some special cases where the margins have changed
* since the last layout, making the cached values invalid.
* For example collapsed window headers have different margin than expanded ones.
*/
clearMarginCache: function() {
delete this.marginInfo;
delete this.target.margin$;
},
/**
* Gets the padding information for the element as an object with left, top, right and
* bottom properties holding padding size in pixels. This object is only read from the
* DOM on first request and is cached.
* @return {Object}
*/
getPaddingInfo: function() {
var me = this,
info = me.paddingInfo;
if (!info) {
me.paddingInfo = info = me.checkCache('paddingInfo');
}
return info;
},
/**
* Gets a property of this object. Also tracks the current layout as dependent on this
* property so that changes to it will trigger the layout to be recalculated.
* @param {String} propName The property name that blocked the layout (e.g., 'width').
* @return {Object} The property value or undefined if not yet set.
*/
getProp: function(propName) {
var me = this,
result = me.props[propName];
me.addTrigger(propName);
return result;
},
/**
* Gets a property of this object if it is correct in the DOM. Also tracks the current
* layout as dependent on this property so that DOM writes of it will trigger the
* layout to be recalculated.
* @param {String} propName The property name (e.g., 'width').
* @return {Object} The property value or undefined if not yet set or is dirty.
*/
getDomProp: function(propName) {
var me = this,
result = (me.dirty && (propName in me.dirty)) ? undefined : me.props[propName];
me.addTrigger(propName, true);
return result;
},
/**
* Returns a style for this item. Each style is read from the DOM only once on first
* request and is then cached. If the value is an integer, it is parsed automatically
* (so '5px' is not returned, but rather 5).
*
* @param {String} styleName The CSS style name.
* @return {Object} The value of the DOM style (parsed as necessary).
*/
getStyle: function(styleName) {
var me = this,
styles = me.styles,
info, value;
if (styleName in styles) {
value = styles[styleName];
}
else {
info = me.styleInfo[styleName];
value = me.el.getStyle(styleName);
if (info && info.parseInt) {
value = parseInt(value, 10) || 0;
}
styles[styleName] = value;
}
return value;
},
/**
* Returns styles for this item. Each style is read from the DOM only once on first
* request and is then cached. If the value is an integer, it is parsed automatically
* (so '5px' is not returned, but rather 5).
*
* @param {String[]} styleNames The CSS style names.
* @param {String[]} [altNames] The alternate names for the returned styles. If given,
* these names must correspond one-for-one to the `styleNames`.
* @return {Object} The values of the DOM styles (parsed as necessary).
*/
getStyles: function(styleNames, altNames) {
var me = this,
styleCache = me.styles,
values = {},
hits = 0,
n = styleNames.length,
i, missing, missingAltNames, name, info, styleInfo, styles, value;
altNames = altNames || styleNames;
// We are optimizing this for all hits or all misses. If we hit on all styles, we
// don't create a missing[]. If we miss on all styles, we also don't create one.
for (i = 0; i < n; ++i) {
name = styleNames[i];
if (name in styleCache) {
values[altNames[i]] = styleCache[name];
++hits;
if (i && hits === 1) { // if (first hit was after some misses)
missing = styleNames.slice(0, i);
missingAltNames = altNames.slice(0, i);
}
}
else if (hits) {
(missing || (missing = [])).push(name);
(missingAltNames || (missingAltNames = [])).push(altNames[i]);
}
}
if (hits < n) {
missing = missing || styleNames;
missingAltNames = missingAltNames || altNames;
styleInfo = me.styleInfo;
styles = me.el.getStyle(missing);
for (i = missing.length; i--;) {
name = missing[i];
info = styleInfo[name];
value = styles[name];
if (info && info.parseInt) {
value = parseInt(value, 10) || 0;
}
values[missingAltNames[i]] = value;
styleCache[name] = value;
}
}
return values;
},
/**
* Returns true if the given property has been set. This is equivalent to calling
* {@link #getProp} and not getting an undefined result. In particular, this call
* registers the current layout to be triggered by changes to this property.
*
* @param {String} propName The property name (e.g., 'width').
* @return {Boolean}
*/
hasProp: function(propName) {
return this.getProp(propName) != null;
},
/**
* Returns true if the given property is correct in the DOM. This is equivalent to
* calling {@link #getDomProp} and not getting an undefined result. In particular,
* this call registers the current layout to be triggered by flushes of this property.
*
* @param {String} propName The property name (e.g., 'width').
* @return {Boolean}
*/
hasDomProp: function(propName) {
return this.getDomProp(propName) != null;
},
/**
* Invalidates the component associated with this item. The layouts for this component
* and all of its contained items will be re-run after first clearing any computed
* values.
*
* If state needs to be carried forward beyond the invalidation, the `options` parameter
* can be used.
*
* @param {Object} options An object describing how to handle the invalidation.
* @param {Object} options.state An object to {@link Ext#apply} to the {@link #state}
* of this item after invalidation clears all other properties.
* @param {Function} options.before A function to call after the context data is cleared
* and before the {@link Ext.layout.Layout#beginLayoutCycle} methods are called.
* @param {Ext.layout.ContextItem} options.before.item This ContextItem.
* @param {Object} options.before.options The options object passed to {@link #invalidate}.
* @param {Function} options.after A function to call after the context data is cleared
* and after the {@link Ext.layout.Layout#beginLayoutCycle} methods are called.
* @param {Ext.layout.ContextItem} options.after.item This ContextItem.
* @param {Object} options.after.options The options object passed to {@link #invalidate}.
* @param {Object} options.scope The scope to use when calling the callback functions.
*/
invalidate: function(options) {
this.context.queueInvalidate(this, options);
},
markDirty: function() {
if (++this.dirtyCount === 1) {
// our first dirty property... queue us for flush
this.context.queueFlush(this);
}
},
onBoxMeasured: function() {
var boxParent = this.boxParent,
state = this.state;
if (boxParent && boxParent.widthModel.shrinkWrap && !state.boxMeasured &&
this.measuresBox) {
// since an autoWidth boxParent is holding a width on itself to allow each
// child to measure
state.boxMeasured = 1; // best to only call once per child
boxParent.boxChildMeasured();
}
},
parseMargins: function(comp, margins) {
var type, ret;
if (margins === true) {
margins = 5;
}
type = typeof margins;
if (type === 'string' || type === 'number') {
ret = comp.parseBox(margins);
}
else if (margins) {
ret = { top: 0, right: 0, bottom: 0, left: 0 }; // base defaults
if (margins) {
margins = Ext.apply(ret, comp.parseBox(margins)); // + config
}
}
return ret;
},
peek: function(propName) {
return this.props[propName];
},
recalculateSizeModel: function() {
// See the constructor, this logic is very similar. Not broken out into
// a separate method for performance reasons
var me = this,
target = me.target,
componentLayout = target.componentLayout,
ownerCtContext = me.ownerCtContext,
oldContext = componentLayout.ownerContext,
sizeModel;
// If the componentLayout has an ownerContext, it will just use the sizeModel that
// exists on the context. Instead, force it to recalculate
componentLayout.ownerContext = null;
me.sizeModel = sizeModel = target.getSizeModel(ownerCtContext &&
ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]);
me.widthModel = sizeModel.width;
me.heightModel = sizeModel.height;
if (oldContext) {
componentLayout.ownerContext = me;
}
},
/**
* Recovers a property value from the last computation and restores its value and
* dirty state.
*
* @param {String} propName The name of the property to recover.
* @param {Object} oldProps The old "props" object from which to recover values.
* @param {Object} oldDirty The old "dirty" object from which to recover state.
*/
recoverProp: function(propName, oldProps, oldDirty) {
var me = this,
props = me.props,
dirty;
if (propName in oldProps) {
props[propName] = oldProps[propName];
if (oldDirty && propName in oldDirty) {
dirty = me.dirty || (me.dirty = {});
dirty[propName] = oldDirty[propName];
}
}
},
redo: function(deep) {
var me = this,
items, len, i;
me.revertProps(me.props);
if (deep && me.wrapsComponent) {
// Rollback the state of child Components
if (me.childItems) {
for (i = 0, items = me.childItems, len = items.length; i < len; i++) {
items[i].redo(deep);
}
}
// Rollback the state of child Elements
for (i = 0, items = me.children, len = items.length; i < len; i++) {
items[i].redo();
}
}
},
/**
* Removes a cached ContextItem that was created using {@link #getEl}. It may be
* necessary to call this method if the dom reference for owned element changes so
* that {@link #getEl} can be called again to reinitialize the ContextItem with the
* new element.
* @param {String/Ext.dom.Element} nameOrEl The element or the name of an owned element
* @param {Ext.layout.container.Container/Ext.Component} [owner] The owner of the
* named element if the passed "nameOrEl" parameter is a String. Defaults to this
* ContextItem's "target" property.
*/
removeEl: function(nameOrEl, owner) {
var me = this,
src, el;
if (nameOrEl) {
if (nameOrEl.dom) {
el = nameOrEl;
}
else {
src = me.target;
if (owner) {
src = owner;
}
el = src[nameOrEl];
if (typeof el === 'function') { // ex 'getTarget'
el = el.call(src);
if (el === me.el) {
return this; // comp.getTarget() often returns comp.el
}
}
}
if (el) {
me.context.removeEl(el, me);
}
}
},
revertProps: function(props) {
var name,
flushed = this.flushedProps,
reverted = {};
for (name in props) {
if (flushed.hasOwnProperty(name)) {
reverted[name] = props[name];
}
}
this.writeProps(reverted);
},
/**
* Queue the setting of a DOM attribute on this ContextItem's target when next flushed.
*/
setAttribute: function(name, value) {
var me = this;
if (!me.attributes) {
me.attributes = {};
}
me.attributes[name] = value;
me.markDirty();
},
setBox: function(box) {
var me = this;
if ('left' in box) {
me.setProp('x', box.left);
}
if ('top' in box) {
me.setProp('y', box.top);
}
// if sizeModel says we should not be setting these, the appropriate calls will be
// null operations... otherwise, we must set these values, so what we have in box
// is what we go with (undefined, NaN and no change are handled at a lower level):
me.setSize(box.width, box.height);
},
/**
* Sets the contentHeight property. If the component uses raw content, then only the
* measured height is acceptable.
*
* Calculated values can sometimes be NaN or undefined, which generally mean the
* calculation is not done. To indicate that such as value was passed, 0 is returned.
* Otherwise, 1 is returned.
*
* If the caller is not measuring (i.e., they are calculating) and the component has raw
* content, 1 is returned indicating that the caller is done.
*/
setContentHeight: function(height, measured) {
if (!measured && this.hasRawContent) {
return 1;
}
return this.setProp('contentHeight', height);
},
/**
* Sets the contentWidth property. If the component uses raw content, then only the
* measured width is acceptable.
*
* Calculated values can sometimes be NaN or undefined, which generally means that the
* calculation is not done. To indicate that such as value was passed, 0 is returned.
* Otherwise, 1 is returned.
*
* If the caller is not measuring (i.e., they are calculating) and the component has raw
* content, 1 is returned indicating that the caller is done.
*/
setContentWidth: function(width, measured) {
if (!measured && this.hasRawContent) {
return 1;
}
return this.setProp('contentWidth', width);
},
/**
* Sets the contentWidth and contentHeight properties. If the component uses raw content,
* then only the measured values are acceptable.
*
* Calculated values can sometimes be NaN or undefined, which generally means that the
* calculation is not done. To indicate that either passed value was such a value, false
* returned. Otherwise, true is returned.
*
* If the caller is not measuring (i.e., they are calculating) and the component has raw
* content, true is returned indicating that the caller is done.
*/
setContentSize: function(width, height, measured) {
return this.setContentWidth(width, measured) +
this.setContentHeight(height, measured) === 2;
},
/**
* Sets a property value. This will unblock and/or trigger dependent layouts if the
* property value is being changed. Values of NaN and undefined are not accepted by
* this method.
*
* @param {String} propName The property name (e.g., 'width').
* @param {Object} value The new value of the property.
* @param {Boolean} dirty Optionally specifies if the value is currently in the DOM
* (default is `true` which indicates the value is not in the DOM and must be flushed
* at some point).
* @return {Number} 1 if this call specified the property value, 0 if not.
*/
setProp: function(propName, value, dirty) {
var me = this,
valueType = typeof value,
info;
if (valueType === 'undefined' || (valueType === 'number' && isNaN(value))) {
return 0;
}
if (me.props[propName] === value) {
return 1;
}
me.props[propName] = value;
++me.context.progressCount;
if (dirty === false) {
// if the prop is equivalent to what is in the DOM (we won't be writing it),
// we need to clear hard blocks (domBlocks) on that property.
me.fireTriggers('domTriggers', propName);
me.clearBlocks('domBlocks', propName);
}
else {
info = me.styleInfo[propName];
if (info) {
if (!me.dirty) {
me.dirty = {};
}
me.dirty[propName] = value;
me.markDirty();
}
}
// we always clear soft blocks on set
me.fireTriggers('triggers', propName);
me.clearBlocks('blocks', propName);
return 1;
},
/**
* Sets the height and constrains the height to min/maxHeight range.
*
* @param {Number} height The height.
* @param {Boolean} [dirty=true] Specifies if the value is currently in the DOM. A
* value of `false` indicates that the value is already in the DOM.
* @return {Number} The actual height after constraining.
*/
setHeight: function(height, dirty) {
var me = this,
comp = me.target,
ownerCtContext = me.ownerCtContext,
frameBody, frameInfo, min, oldHeight, rem;
if (height < 0) {
height = 0;
}
if (!me.wrapsComponent) {
if (!me.setProp('height', height, dirty)) {
return NaN;
}
}
else {
min = me.collapsedVert ? 0 : (comp.minHeight || 0);
height = Ext.Number.constrain(height, min, comp.maxHeight);
oldHeight = me.props.height;
if (!me.setProp('height', height, dirty)) {
return NaN;
}
// if we are a container child, since the height is now known we can decrement
// the number of remainingChildDimensions that the ownerCtContext is waiting on.
if (ownerCtContext && !me.isComponentChild && isNaN(oldHeight)) {
rem = --ownerCtContext.remainingChildDimensions;
if (!rem) {
// if there are 0 remainingChildDimensions set containerChildrenSizeDone
// on the ownerCtContext to indicate that all of its children's dimensions
// are known
ownerCtContext.setProp('containerChildrenSizeDone', true);
}
}
frameBody = me.frameBodyContext;
if (frameBody) {
frameInfo = me.getFrameInfo();
frameBody[me.el.vertical ? 'setWidth' : 'setHeight'](height - frameInfo.height,
dirty);
}
}
return height;
},
/**
* Sets the height and constrains the width to min/maxWidth range.
*
* @param {Number} width The width.
* @param {Boolean} [dirty=true] Specifies if the value is currently in the DOM. A
* value of `false` indicates that the value is already in the DOM.
* @return {Number} The actual width after constraining.
*/
setWidth: function(width, dirty) {
var me = this,
comp = me.target,
ownerCtContext = me.ownerCtContext,
frameBody, frameInfo, min, oldWidth, rem;
if (width < 0) {
width = 0;
}
if (!me.wrapsComponent) {
if (!me.setProp('width', width, dirty)) {
return NaN;
}
}
else {
min = me.collapsedHorz ? 0 : (comp.minWidth || 0);
width = Ext.Number.constrain(width, min, comp.maxWidth);
oldWidth = me.props.width;
if (!me.setProp('width', width, dirty)) {
return NaN;
}
// if we are a container child, since the width is now known we can decrement
// the number of remainingChildDimensions that the ownerCtContext is waiting on.
if (ownerCtContext && !me.isComponentChild && isNaN(oldWidth)) {
rem = --ownerCtContext.remainingChildDimensions;
if (!rem) {
// if there are 0 remainingChildDimensions set containerChildrenSizeDone
// on the ownerCtContext to indicate that all of its children's dimensions
// are known
ownerCtContext.setProp('containerChildrenSizeDone', true);
}
}
// if ((frameBody = me.target.frameBody) && (frameBody = me.getEl(frameBody))){
frameBody = me.frameBodyContext;
if (frameBody) {
frameInfo = me.getFrameInfo();
frameBody.setWidth(width - frameInfo.width, dirty);
}
/* if (owner.frameBody) {
frameContext = ownerContext.frameContext ||
(ownerContext.frameContext = ownerContext.getEl('frameBody'));
width += (frameContext.paddingInfo || frameContext.getPaddingInfo()).width;
} */
}
return width;
},
setSize: function(width, height, dirty) {
this.setWidth(width, dirty);
this.setHeight(height, dirty);
},
translateProps: {
x: 'left',
y: 'top'
},
undo: function(deep) {
var me = this,
items, len, i;
me.revertProps(me.lastBox);
if (deep && me.wrapsComponent) {
// Rollback the state of child Components
if (me.childItems) {
for (i = 0, items = me.childItems, len = items.length; i < len; i++) {
items[i].undo(deep);
}
}
// Rollback the state of child Elements
for (i = 0, items = me.children, len = items.length; i < len; i++) {
items[i].undo();
}
}
},
unsetProp: function(propName) {
var dirty = this.dirty;
delete this.props[propName];
if (dirty) {
delete dirty[propName];
}
},
writeProps: function(dirtyProps, flushing) {
if (!(dirtyProps && typeof dirtyProps === 'object')) {
//<debug>
Ext.Logger.warn('writeProps expected dirtyProps to be an object');
//</debug>
return;
}
// eslint-disable-next-line vars-on-top
var me = this,
el = me.el,
target = me.target,
styleInfo = me.styleInfo,
styles = {},
width = dirtyProps.width,
height = dirtyProps.height,
styleCount = 0, // used as a boolean, the exact count doesn't matter
info, propName, numericValue, hasWidth, hasHeight, isAbsolute, scrollbarSize,
style, targetEl, scroller;
// Process non-style properties:
if ('displayed' in dirtyProps) {
el.setDisplayed(dirtyProps.displayed);
}
// Unblock any hard blocks (domBlocks) and copy dom styles into 'styles'
for (propName in dirtyProps) {
if (flushing) {
me.fireTriggers('domTriggers', propName);
me.clearBlocks('domBlocks', propName);
me.flushedProps[propName] = 1;
}
info = styleInfo[propName];
if (info && info.dom) {
// Numeric dirty values should have their associated suffix added
if (info.suffix && (numericValue = parseInt(dirtyProps[propName], 10))) {
styles[propName] = numericValue + info.suffix;
}
// Non-numeric (eg "auto") go in unchanged.
else {
styles[propName] = dirtyProps[propName];
}
++styleCount;
}
}
// convert x/y into setPosition (for a component) or left/top styles (for an el)
if ('x' in dirtyProps || 'y' in dirtyProps) {
if (target.isComponent) {
target.setPosition(dirtyProps.x, dirtyProps.y);
}
else {
// we wrap an element, so convert x/y to styles:
styleCount += me.addPositionStyles(styles, dirtyProps);
}
}
// Handle overflow settings updated by layout
if ('overflowX' in dirtyProps) {
scroller = target.getScrollable();
if (scroller) {
scroller.setX(dirtyProps.overflowX);
}
}
if ('overflowY' in dirtyProps) {
if (scroller || (scroller = target.getScrollable())) {
scroller.setY(dirtyProps.overflowY);
}
}
// IE9 subtracts the scrollbar size from the element size when the element
// is absolutely positioned and uses box-sizing: border-box. To workaround this
// issue we have to add the the scrollbar size.
//
// See http://social.msdn.microsoft.com/Forums/da-DK/iewebdevelopment/thread/47c5148f-a142-4a99-9542-5f230c78cb3b
//
if (me.wrapsComponent && Ext.isIE9) {
// when we set a width and we have a vertical scrollbar (overflowY), we need
// to add the scrollbar width... conversely for the height and overflowX
if ((hasWidth = width !== undefined && me.hasOverflowY) ||
(hasHeight = height !== undefined && me.hasOverflowX)) {
// check that the component is absolute positioned.
isAbsolute = me.isAbsolute;
if (isAbsolute === undefined) {
isAbsolute = false;
targetEl = me.target.getTargetEl();
style = targetEl.getStyle('position');
me.isAbsolute = isAbsolute = (style === 'absolute'); // cache it
}
if (isAbsolute) {
scrollbarSize = Ext.scrollbar.size();
if (hasWidth) {
width = parseInt(width, 10) + scrollbarSize.width;
styles.width = width + 'px';
++styleCount;
}
if (hasHeight) {
height = parseInt(height, 10) + scrollbarSize.height;
styles.height = height + 'px';
++styleCount;
}
}
}
}
// we make only one call to setStyle to allow it to optimize itself:
if (styleCount) {
el.setStyle(styles);
}
},
//-------------------------------------------------------------------------
// Diagnostics
debugHooks: {
$enabled: false, // Disable by default
addBlock: function(name, layout, propName) {
var blockedBy = (layout.blockedBy || (layout.blockedBy = {}));
// Ext.log(this.id,'.',propName,' ',name,': ',this.context.getLayoutName(layout));
blockedBy[this.id + '.' + propName + (name.substring(0, 3) === 'dom' ? ':dom' : '')] =
1;
return this.callParent(arguments);
},
addBoxChild: function(boxChildItem) {
var ret = this.callParent(arguments),
boxChildren = this.boxChildren,
boxParents;
if (boxChildren && boxChildren.length === 1) {
// the boxParent collection is created by the run override found in
// Ext.diag.layout.Context, but IE sometimes does not load that override, so
// we work around it for now
boxParents = this.context.boxParents ||
(this.context.boxParents = new Ext.util.MixedCollection());
boxParents.add(this);
}
return ret;
},
addTrigger: function(propName, inDom) {
var layout = this.context.currentLayout,
triggers;
// Ext.log(this.id,'.',propName,' ',inDom ? ':dom' : '',' ',
// this.context.getLayoutName(layout));
this.callParent(arguments);
triggers = this.context.triggersByLayoutId;
(triggers[layout.id] || (triggers[layout.id] = {}))[
this.id + '.' + propName + (inDom ? ':dom' : '')] =
{
item: this,
name: propName
};
},
checkAuthority: function(prop) {
var me = this,
model = me[prop + 'Model'], // not me.sizeModel[prop] since it is immutable
layout = me.context.currentLayout,
ok, setBy;
if (layout === me.target.ownerLayout) {
// the ownerLayout is only allowed to set calculated dimensions
ok = model.calculated;
}
else if (layout.isComponentLayout) {
// the component's componentLayout (normally) is only allowed to set auto or
// configured dimensions. The exception is when a component is run w/o its
// ownerLayout in the picture (isTopLevel), someone must publish the lastBox
// values and that lucky layout is the componentLayout (kinda had to be since
// the ownerLayout is not running)
ok = me.isTopLevel || model.auto || model.configured;
}
if (!ok) {
setBy = me.context.getLayoutName(layout);
Ext.log(setBy + ' cannot set ' + prop);
}
},
clearBlocks: function(name, propName) {
var collection = this[name],
blockedLayouts = collection && collection[propName],
key = this.id + '.' + propName + (name.substring(0, 3) === 'dom' ? ':dom' : ''),
layout, layoutId;
if (blockedLayouts) {
for (layoutId in blockedLayouts) {
layout = blockedLayouts[layoutId];
delete layout.blockedBy[key];
}
}
return this.callParent(arguments);
},
getEl: function(el) {
var child = this.callParent(arguments);
if (child && child !== this && child.parent !== this) {
Ext.raise({
msg: 'Got element from wrong component'
});
}
return child;
},
init: function() {
var me = this,
ret;
ret = me.callParent(arguments);
if (me.context.logOn.initItem) {
Ext.log(me.id, ' consumers: content=', me.consumersContentWidth, '/',
me.consumersContentHeight,
', container=', me.consumersContainerWidth,
'/', me.consumersContainerHeight,
', size=', me.consumersWidth, '/', me.consumersHeight);
}
return ret;
},
invalidate: function() {
if (this.wrapsComponent) {
if (this.context.logOn.invalidate) {
Ext.log('invalidate: ', this.id);
}
}
else {
Ext.raise({
msg: 'Cannot invalidate an element contextItem'
});
}
return this.callParent(arguments);
},
setProp: function(propName, value, dirty) {
var me = this,
layout = me.context.currentLayout,
setBy = me.context.getLayoutName(layout),
fullName = me.id + '.' + propName,
setByProps;
if (value !== null) {
setByProps = me.setBy || (me.setBy = {});
if (!setByProps[propName]) {
setByProps[propName] = setBy;
}
else if (setByProps[propName] !== setBy) {
Ext.log({ level: 'warn' }, 'BAD! ', fullName, ' set by ',
setByProps[propName], ' and ', setBy);
}
}
if (me.context.logOn.setProp) {
if (typeof value !== 'undefined' && !isNaN(value) && me.props[propName] !== value) {
Ext.log('set ', fullName, ' = ', value, ' (', dirty, ')');
}
}
return this.callParent(arguments);
},
setHeight: function(height, dirty, force) {
if (!force && this.wrapsComponent) {
this.checkAuthority('height');
}
return this.callParent(arguments);
},
setWidth: function(width, dirty, force) {
if (!force && this.wrapsComponent) {
this.checkAuthority('width');
}
return this.callParent(arguments);
}
} // End Diagnostics
//-------------------------------------------------------------------------
}, function() {
var px = { dom: true, parseInt: true, suffix: 'px' },
isDom = { dom: true },
faux = { dom: false };
// If a property exists in styleInfo, it participates in some way with the DOM. It may
// be virtualized (like 'x' and y') and be indirect, but still requires a flush cycle
// to reach the DOM. Properties (like 'contentWidth' and 'contentHeight') have no real
// presence in the DOM and hence have no flush intanglements.
//
// For simple styles, the object value on the right contains properties that help in
// decoding values read by getStyle and preparing values to pass to setStyle.
//
this.prototype.styleInfo = {
containerChildrenSizeDone: faux,
containerLayoutDone: faux,
displayed: faux,
done: faux,
x: faux,
y: faux,
// For Ext.grid.ColumnLayout
columnsChanged: faux,
rowHeights: faux,
viewOverflowY: faux,
// Scroller state set by layouts
overflowX: faux,
overflowY: faux,
left: px,
top: px,
right: px,
bottom: px,
width: px,
height: px,
'border-top-width': px,
'border-right-width': px,
'border-bottom-width': px,
'border-left-width': px,
'margin-top': px,
'margin-right': px,
'margin-bottom': px,
'margin-left': px,
'padding-top': px,
'padding-right': px,
'padding-bottom': px,
'padding-left': px,
'line-height': isDom,
display: isDom,
clear: isDom
};
});