<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>The source code</title> <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" /> <script type="text/javascript" src="../resources/prettify/prettify.js"></script> <style type="text/css"> .highlight { display: block; background-color: #ddd; } </style> <script type="text/javascript"> function highlight() { document.getElementById(location.hash.replace(/#/, "")).className = "highlight"; } </script> </head> <body onload="prettyPrint(); highlight();"> <pre class="prettyprint lang-js"><span id='Ext-layout-ContextItem'>/** </span> * 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', { requires: ['Ext.layout.ClassList'], heightModel: null, widthModel: null, sizeModel: null, 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, remainingChildLayouts: 0, remainingComponentChildLayouts: 0, remainingContainerChildLayouts: 0, // the current set of property values: props: null, <span id='Ext-layout-ContextItem-property-state'> /** </span> * @property {Object} state * State variables that are cleared when invalidated. Only applies to component items. */ state: null, <span id='Ext-layout-ContextItem-property-wrapsComponent'> /** </span> * @property {Boolean} wrapsComponent * True if this item wraps a Component (rather than an Element). * @readonly */ wrapsComponent: false, constructor: function (config) { var me = this, el, ownerCt, ownerCtContext, sizeModel, target; Ext.apply(me, config); el = me.el; me.id = el.id; me.lastBox = el.lastBox; // 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 = {}; // the set of cached styles for the element: me.styles = {}; target = me.target; if (target.isComponent) { me.wrapsComponent = true; // 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 = 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. me.sizeModel = sizeModel = target.getSizeModel(ownerCtContext && ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]); me.widthModel = sizeModel.width; me.heightModel = sizeModel.height; // 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). } }, <span id='Ext-layout-ContextItem-method-init'> /** </span> * 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. * @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; me.dirty = me.invalid = false; me.props = {}; 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(); } // 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 = target.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; } 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); } if (me.heightModel.calculated) { me.recoverProp('height', oldProps, oldDirty); } } 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; }, <span id='Ext-layout-ContextItem-method-initContinue'> /** </span> * @private */ initContinue: function (full) { var me = this, ownerCtContext = me.ownerCtContext, widthModel = me.widthModel, boxParent; 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; }, <span id='Ext-layout-ContextItem-method-initDone'> /** </span> * @private */ initDone: function (full, componentChildrenDone, containerChildrenDone, containerLayoutDone) { var me = this, props = me.props, state = me.state; // These properties are only set when they are true: if (componentChildrenDone) { props.componentChildrenDone = true; } if (containerChildrenDone) { props.containerChildrenDone = 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; } }, <span id='Ext-layout-ContextItem-method-initAnimation'> /** </span> * @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); } }, noFraming: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 }, <span id='Ext-layout-ContextItem-method-addCls'> /** </span> * Queue the addition of a class name (or array of class names) to this ContextItem's target when next flushed. */ addCls: function(newCls) { this.getClassList().addMany(newCls); }, <span id='Ext-layout-ContextItem-method-removeCls'> /** </span> * Queue the removal of a class name (or array of class names) from this ContextItem's target when next flushed. */ removeCls: function(removeCls) { this.getClassList().removeMany(removeCls); }, <span id='Ext-layout-ContextItem-method-addBlock'> /** </span> * 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 ]; } } }, <span id='Ext-layout-ContextItem-method-addTrigger'> /** </span> * 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); } } }, <span id='Ext-layout-ContextItem-method-clearBlocks'> /** </span> * 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); } } } }, <span id='Ext-layout-ContextItem-method-block'> /** </span> * 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); }, <span id='Ext-layout-ContextItem-method-domBlock'> /** </span> * 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); }, <span id='Ext-layout-ContextItem-method-fireTriggers'> /** </span> * 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); } } } }, <span id='Ext-layout-ContextItem-method-flush'> /** </span> * 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; // Flush added/removed classes if (me.classList && me.classList.dirty) { me.classList.flush(); } // 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); } }, <span id='Ext-layout-ContextItem-method-flushAnimations'> /** </span> * @private */ flushAnimations: function() { var me = this, animateFrom = me.lastBox, target, targetAnim, duration, animateProps, anim, changeCount, j, propsLen, propName, oldValue, newValue; // Only animate if the Component has been previously layed out: first layout should not animate if (animateFrom) { target = me.target; targetAnim = target.layout && target.layout.animate; if (targetAnim) { duration = Ext.isNumber(targetAnim) ? targetAnim : 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) { 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); Ext.fx.Manager.getFxQueue(me.el.id)[0].on({ afteranimate: function() { if (me.isCollapsingOrExpanding === 1) { target.componentLayout.redoLayout(me); target.afterCollapse(true); } else if (me.isCollapsingOrExpanding === 2) { target.afterExpand(true); } } }); } } }, <span id='Ext-layout-ContextItem-method-getBorderInfo'> /** </span> * 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; }, <span id='Ext-layout-ContextItem-method-getClassList'> /** </span> * Returns a ClassList-like object to buffer access to this item's element's classes. */ getClassList: function () { return this.classList || (this.classList = new Ext.layout.ClassList(this)); }, <span id='Ext-layout-ContextItem-method-getEl'> /** </span> * 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. */ 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; }, getFraming: function () { var me = this; if (!me.framingInfo) { me.framingInfo = me.target.frameSize || me.noFraming; } return me.framingInfo; }, <span id='Ext-layout-ContextItem-method-getFrameInfo'> /** </span> * 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, frame, border; if (!info) { frame = me.getFraming(); border = me.getBorderInfo(); me.frameInfo = info = { top : frame.top + border.top, right : frame.right + border.right, bottom: frame.bottom + border.bottom, left : frame.left + border.left, width : frame.width + border.width, height: frame.height + border.height }; } return info; }, <span id='Ext-layout-ContextItem-method-getMarginInfo'> /** </span> * 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, margins, 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; // Option #1 for configuring margins on components is the "margin" config // property. When supplied, this config is written to the DOM during the // render process (see AbstractComponent#initStyles). // // Option #2 is available to some layouts (e.g., Box, Border, Fit) that // handle margin calculations themselves. These layouts support a "margins" // config property on their items and they have a "defaultMargins" config // property. These margin values are added to the "natural" margins read // from the DOM and 0's are written to the DOM after they are added. // To avoid having to do all this on every layout, we cache the results on // the component in the (private) "margin$" property. We identify the cache // results as belonging to the appropriate ownerLayout in case items are // moved around. info = comp.margin$; if (info && info.ownerId !== ownerLayoutId) { // got one but from the wrong owner info = null; // if (manageMargins) { // TODO: clear inline margins (the 0's we wrote last time)??? // } } if (!info) { // if (no cache) // CSS margins are only checked if there isn't a margin property on the component info = me.parseMargins(comp.margin) || me.checkCache('marginInfo'); // Some layouts also support margins and defaultMargins, e.g. Fit, Border, Box if (manageMargins) { margins = me.parseMargins(comp.margins, ownerLayout.defaultMargins); if (margins) { // if (using 'margins' and/or 'defaultMargins') // margin and margins can both be present at the same time and must be combined info = { top: info.top + margins.top, right: info.right + margins.right, bottom: info.bottom + margins.bottom, left: info.left + margins.left }; } 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; }, <span id='Ext-layout-ContextItem-method-clearMarginCache'> /** </span> * 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$; }, <span id='Ext-layout-ContextItem-method-getPaddingInfo'> /** </span> * 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; }, <span id='Ext-layout-ContextItem-method-getProp'> /** </span> * 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; }, <span id='Ext-layout-ContextItem-method-getDomProp'> /** </span> * 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; }, <span id='Ext-layout-ContextItem-method-getStyle'> /** </span> * 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; }, <span id='Ext-layout-ContextItem-method-getStyles'> /** </span> * 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; }, <span id='Ext-layout-ContextItem-method-hasProp'> /** </span> * 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) { var value = this.getProp(propName); return typeof value != 'undefined'; }, <span id='Ext-layout-ContextItem-method-hasDomProp'> /** </span> * 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) { var value = this.getDomProp(propName); return typeof value != 'undefined'; }, <span id='Ext-layout-ContextItem-method-invalidate'> /** </span> * 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 (margins, defaultMargins) { if (margins === true) { margins = 5; } var type = typeof margins, ret; if (type == 'string' || type == 'number') { ret = Ext.util.Format.parseBox(margins); } else if (margins || defaultMargins) { ret = { top: 0, right: 0, bottom: 0, left: 0 }; // base defaults if (defaultMargins) { Ext.apply(ret, this.parseMargins(defaultMargins)); // + layout defaults } Ext.apply(ret, margins); // + config } return ret; }, peek: function (propName) { return this.props[propName]; }, <span id='Ext-layout-ContextItem-method-recoverProp'> /** </span> * 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(); } } }, revertProps: function (props) { var name, flushed = this.flushedProps, reverted = {}; for (name in props) { if (flushed.hasOwnProperty(name)) { reverted[name] = props[name]; } } this.writeProps(reverted); }, <span id='Ext-layout-ContextItem-method-setAttribute'> /** </span> * 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); }, <span id='Ext-layout-ContextItem-method-setContentHeight'> /** </span> * 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); }, <span id='Ext-layout-ContextItem-method-setContentWidth'> /** </span> * 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); }, <span id='Ext-layout-ContextItem-method-setContentSize'> /** </span> * 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; }, <span id='Ext-layout-ContextItem-method-setProp'> /** </span> * 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, borderBox, 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 = {}; } if (propName == 'width' || propName == 'height') { borderBox = me.isBorderBoxValue; if (borderBox == null) { me.isBorderBoxValue = borderBox = !!me.el.isBorderBox(); } if (!borderBox) { me.borderInfo || me.getBorderInfo(); me.paddingInfo || me.getPaddingInfo(); } } me.dirty[propName] = value; me.markDirty(); } } // we always clear soft blocks on set me.fireTriggers('triggers', propName); me.clearBlocks('blocks', propName); return 1; }, <span id='Ext-layout-ContextItem-method-setHeight'> /** </span> * 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 /*, private {Boolean} force */) { var me = this, comp = me.target, frameBody, frameInfo, padding; if (height < 0) { height = 0; } if (!me.wrapsComponent) { if (!me.setProp('height', height, dirty)) { return NaN; } } else { height = Ext.Number.constrain(height, comp.minHeight || 0, comp.maxHeight); if (!me.setProp('height', height, dirty)) { return NaN; } frameBody = me.frameBodyContext; if (frameBody){ frameInfo = me.getFrameInfo(); frameBody.setHeight(height - frameInfo.height, dirty); } } return height; }, <span id='Ext-layout-ContextItem-method-setWidth'> /** </span> * 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 /*, private {Boolean} force */) { var me = this, comp = me.target, frameBody, frameInfo, padding; if (width < 0) { width = 0; } if (!me.wrapsComponent) { if (!me.setProp('width', width, dirty)) { return NaN; } } else { width = Ext.Number.constrain(width, comp.minWidth || 0, comp.maxWidth); if (!me.setProp('width', width, dirty)) { return NaN; } //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.frameMC) { frameContext = ownerContext.frameContext || (ownerContext.frameContext = ownerContext.getEl('frameMC')); 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 warn> Ext.Logger.warn('writeProps expected dirtyProps to be an object'); //</debug> return; } var me = this, el = me.el, styles = {}, styleCount = 0, // used as a boolean, the exact count doesn't matter styleInfo = me.styleInfo, info, propName, numericValue, dirtyX = 'x' in dirtyProps, dirtyY = 'y' in dirtyProps, x = dirtyProps.x, y = dirtyProps.y, width = dirtyProps.width, height = dirtyProps.height, isBorderBox = me.isBorderBoxValue, target = me.target, max = Math.max, paddingWidth = 0, paddingHeight = 0, hasWidth, hasHeight, isAbsolute, scrollbarSize, style, targetEl; // 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 (dirtyX || dirtyY) { if (target.isComponent) { // Ensure we always pass the current coordinate in if one coordinate has not been dirtied by a calculation cycle. target.setPosition(x||me.props.x, y||me.props.y); } else { // we wrap an element, so convert x/y to styles: if (dirtyX) { styles.left = x + 'px'; ++styleCount; } if (dirtyY) { styles.top = y + 'px'; ++styleCount; } } } // Support for the content-box box model... if (!isBorderBox && (width > 0 || height > 0)) { // no need to subtract from 0 // The width and height values assume the border-box box model, // so we must remove the padding & border to calculate the content-box. //<debug> if (!(me.borderInfo && me.paddingInfo)) { throw Error("Needed to have gotten the borderInfo and paddingInfo when the width or height was setProp'd"); } //</debug> if(!me.frameBodyContext) { // Padding needs to be removed only if the element is not framed. paddingWidth = me.paddingInfo.width; paddingHeight = me.paddingInfo.height; } if (width) { width = max(parseInt(width, 10) - (me.borderInfo.width + paddingWidth), 0); styles.width = width + 'px'; ++styleCount; } if (height) { height = max(parseInt(height, 10) - (me.borderInfo.height + paddingHeight), 0); styles.height = height + 'px'; ++styleCount; } } // IE9 strict 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 && Ext.isStrict) { // 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 and border-box: isAbsolute = me.isAbsolute; if (isAbsolute === undefined) { isAbsolute = false; targetEl = me.target.getTargetEl(); style = targetEl.getStyle('position'); if (style == 'absolute') { style = targetEl.getStyle('box-sizing'); isAbsolute = (style == 'border-box'); } me.isAbsolute = isAbsolute; // cache it } if (isAbsolute) { scrollbarSize = Ext.getScrollbarSize(); 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); } } }, 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 = { childrenDone: faux, componentChildrenDone: faux, containerChildrenDone: faux, containerLayoutDone: faux, displayed: faux, done: faux, x: faux, y: faux, // For Ext.grid.ColumnLayout columnWidthsDone: 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 }; }); </pre> </body> </html>