/** * 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 };});