/** * @class Ext.layout.container.Auto * * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to * render any child Components when no `{@link Ext.container.Container#layout layout}` * is configured into a `{@link Ext.container.Container Container}.` AutoLayout provides * only a passthrough of any layout calls to any child containers. * * @example * Ext.create('Ext.Panel', { * width: 500, * height: 280, * title: 'AutoLayout Panel', * layout: 'auto', * renderTo: document.body, * items: [{ * xtype: 'panel', * title: 'Top Inner Panel', * width: '75%', * height: 90 * }, { * xtype: 'panel', * title: 'Bottom Inner Panel', * width: '75%', * height: 90 * }] * }); */Ext.define('Ext.layout.container.Auto', { extend: 'Ext.layout.container.Container', alias: ['layout.auto', 'layout.autocontainer'], type: 'autocontainer', childEls: [ 'outerCt', 'innerCt' ], /** * @cfg {Boolean} [reserveScrollbar=false] * Set to `true` to leave space for a vertical scrollbar (if the OS shows space-consuming * scrollbars) regardless of whether a scrollbar is needed. * * This is useful if content height changes during application usage, but you do not want * the calculated width of child items to change when a scrollbar appears or disappears. * The scrollbar will appear in the reserved space, and the calculated width * of child Components will not change. * * @example * Ext.define('Employee', { * extend: 'Ext.data.Model', * fields: [ * {name: 'rating', type: 'int'}, * {name: 'salary', type: 'float'}, * {name: 'name'} * ] * }); * * function createFakeData(count) { * var firstNames = ['Screech', 'Kelly', 'Zach', 'Jessie', 'Lisa', 'A.C.', 'Richard'], * lastNames = ['Powers', 'Kapowski', 'Morris', 'Spano', 'Turtle', 'Slater', * 'Belding'], * ratings = [1, 2, 3, 4, 5], * salaries = [100, 400, 900, 1500, 1000000], * data = []; * * for (var i = 0; i < (count || 25); i++) { * var ratingId = Math.floor(Math.random() * ratings.length), * salaryId = Math.floor(Math.random() * salaries.length), * firstNameId = Math.floor(Math.random() * firstNames.length), * lastNameId = Math.floor(Math.random() * lastNames.length), * * rating = ratings[ratingId], * salary = salaries[salaryId], * name = Ext.String.format( * "{0} {1}", firstNames[firstNameId], lastNames[lastNameId] * ); * * data.push({ * rating: rating, * salary: salary, * name: name * }); * } * store.loadData(data); * } * * // create the Data Store * var store = Ext.create('Ext.data.Store', { * id: 'store', * model: 'Employee', * proxy: { * type: 'memory' * } * }); * createFakeData(10); * * var grid = Ext.create('Ext.grid.Panel', { * title: 'Grid loaded with varying number of records', * anchor: '100%', * store: store, * columns: [{ * xtype: 'rownumberer', * width: 40, * sortable: false * },{ * text: 'Name', * flex: 1, * sortable: true, * dataIndex: 'name' * },{ * text: 'Rating', * width: 125, * sortable: true, * dataIndex: 'rating' * },{ * text: 'Salary', * width: 125, * sortable: true, * dataIndex: 'salary', * align: 'right', * renderer: Ext.util.Format.usMoney * }] * }); * * Ext.create('Ext.panel.Panel', { * renderTo: document.body, * width: 800, * height: 600, * layout: { * type: 'anchor', * reserveScrollbar: true // There will be a gap even when there's no scrollbar * }, * scrollable: true, * items: grid, * tbar: { * defaults: { * handler: function(b) { * createFakeData(b.count); * } * }, * items: [{ * text: '10 Items', * count: 10 * },{ * text: '100 Items', * count: 100 * },{ * text: '300 Items', * count: 300 * },{ * text: '1000 Items', * count: 1000 * },{ * text: '5000 Items', * count: 5000 * }] * } * }); * */ reserveScrollbar: false, /** * @property {Boolean} [managePadding=true] * indicates that this layout will correct cross browser padding differences when the * container has overflow. * * In some browsers the right and/or bottom padding of a container is lost when * the container has overflow. If managePadding is true the layout will apply the * padding to an inner wrapping element instead of the container element that has the * overflow so that paddding will be included in the scrollable area. * Note: padding will not be managed if it is configured on the container using * a style config or css class. In order to be managed, padding must be added to the * container using the appropriate {@link Ext.Component#contentPaddingProperty * contentPaddingProperty}. For {@link Ext.panel.Panel Panels} use * {@link Ext.panel.Panel#bodyPadding}, and for * {@link Ext.container.Container Containers}, use * {@link Ext.Component#padding padding} */ managePadding: true, /** * @property {Boolean} [manageOverflow=false] * true to rerun the layout if scrollbars are needed. */ manageOverflow: false, // auto layout does not care about the dimensions of individual child items since // it does not size them, and it measures them as a whole when in shrinkWrap mode. needsItemSize: false, setsItemSize: false, // Begin with no previous adjustments lastOverflowAdjust: { width: 0, height: 0 }, outerCtCls: Ext.baseCSSPrefix + 'autocontainer-outerCt', innerCtCls: Ext.baseCSSPrefix + 'autocontainer-innerCt', /* eslint-disable indent, max-len */ // Auto layout's renderTpl wraps the content in an outerCt which is used to accomplish // the following 3 goals: // // 1. When the container has a shrink wrapped width and/or height, the outerCt is used // to measure the size of the content. // 2. When the container has overflow some browsers lose the container's right and/or // bottom padding. To fix this, the padding is rendered to the outerCt instead of // the container target element. This ensures that the padding is included in the // container's scrollWidth/scrollHeight. In Old IE when a table is used, the padding // is rendered to the innerCt td element. // 3. The outerCt contains the margins of its children, that is to say, it prevents // them from collapsing. renderTpl: [ // An outerCt with display:table shrink-wraps contents, and contains child // margins. The table-cell innerCt is required in order to support percentage // heights on child elements. '<div id="{ownerId}-outerCt" data-ref="outerCt" class="{outerCtCls}" role="presentation">', '<div id="{ownerId}-innerCt" data-ref="innerCt" style="{%this.renderPadding(out, values)%}" ', // If raw HTML is used as the component's content, avoid setting // presentation role so as not to mask the content from screen readers '<tpl if="!$comp.html">role="presentation"</tpl>', 'class="{innerCtCls}">', '{%this.renderBody(out,values)%}', '</div>', '</div>' ], /* eslint-enable indent, max-len */ beginLayout: function(ownerContext) { this.callParent(arguments); this.initContextItems(ownerContext); }, beforeLayoutCycle: function(ownerContext) { var comp = this.owner, inheritedState = comp.inheritedState, inheritedStateInner = comp.inheritedStateInner; if (!inheritedState || inheritedState.invalid) { inheritedState = comp.getInherited(); // fixes both inheritedStateInner = comp.inheritedStateInner; } if (ownerContext.widthModel.shrinkWrap) { inheritedStateInner.inShrinkWrapTable = true; } else { delete inheritedStateInner.inShrinkWrapTable; } }, beginLayoutCycle: function(ownerContext) { var me = this, outerCt = me.outerCt, lastOuterCtWidth = me.lastOuterCtWidth || '', lastOuterCtHeight = me.lastOuterCtHeight || '', lastOuterCtTableLayout = me.lastOuterCtTableLayout || '', state = ownerContext.state, overflowXStyle, outerCtWidth, outerCtHeight, outerCtTableLayout, inheritedStateInner; me.callParent(arguments); // Default to "shrink wrap styles". outerCtWidth = outerCtHeight = outerCtTableLayout = ''; if (!ownerContext.widthModel.shrinkWrap) { // if we're not shrink wrapping width, we need to get the innerCt out of the // way to avoid any shrink wrapping effect on child items // fill the available width within the container outerCtWidth = '100%'; inheritedStateInner = me.owner.inheritedStateInner; // expand no further than the available width, even if contents are wider // unless there is a potential for horizontal overflow, then allow // the outerCt to expand to the width of the contents overflowXStyle = me.getOverflowXStyle(ownerContext); outerCtTableLayout = (inheritedStateInner.inShrinkWrapTable || overflowXStyle === 'auto' || overflowXStyle === 'scroll') ? '' : 'fixed'; } if (!ownerContext.heightModel.shrinkWrap && !Ext.supports.PercentageHeightOverflowBug) { // if we're not shrink wrapping height, we need to get the outerCt out of the // way so that percentage height children will be sized correctly. We do this // by giving the outerCt a height of '100%' unless the browser is affected by // the "percentage height overflow bug", in which case the outerCt will get a // pixel height set during the calculate phase after we know the targetEl size. outerCtHeight = '100%'; } // if the outerCt width changed since last time (becuase of a widthModel change) // or if we set a pixel width on the outerCt last time to work around a browser- // specific bug, we need to set the width of the outerCt if ((outerCtWidth !== lastOuterCtWidth) || me.hasOuterCtPxWidth) { outerCt.setStyle('width', outerCtWidth); me.lastOuterCtWidth = outerCtWidth; me.hasOuterCtPxWidth = false; } // Set the outerCt table-layout property if different from last time. if (outerCtTableLayout !== lastOuterCtTableLayout) { outerCt.setStyle('table-layout', outerCtTableLayout); me.lastOuterCtTableLayout = outerCtTableLayout; } // if the outerCt height changed since last time (becuase of a heightModel change) // or if we set a pixel height on the outerCt last time to work around a browser- // specific bug, we need to set the height of the outerCt if ((outerCtHeight !== lastOuterCtHeight) || me.hasOuterCtPxHeight) { outerCt.setStyle('height', outerCtHeight); me.lastOuterCtHeight = outerCtHeight; me.hasOuterCtPxHeight = false; } if (me.hasInnerCtPxHeight) { me.innerCt.setStyle('height', ''); me.hasInnerCtPxHeight = false; } // Begin with the scrollbar adjustment that we used last time - this is more likely // to be correct than beginning with no adjustment at all, but only if it is not // already defined - it may have already been set by invalidate() state.overflowAdjust = state.overflowAdjust || me.lastOverflowAdjust; }, calculate: function(ownerContext) { var me = this, state = ownerContext.state, containerSize = me.getContainerSize(ownerContext, true), calculatedItems; // If subclass has a calculateItems method, call it and cache the result calculatedItems = state.calculatedItems || (state.calculatedItems = me.calculateItems ? me.calculateItems(ownerContext, containerSize) : true); me.setCtSizeIfNeeded(ownerContext, containerSize); if (calculatedItems && ownerContext.hasDomProp('containerChildrenSizeDone')) { me.calculateContentSize(ownerContext); if (containerSize.gotAll) { if (me.manageOverflow && !ownerContext.state.secondPass && !me.reserveScrollbar) { me.calculateOverflow(ownerContext, containerSize); } return; } } me.done = false; }, calculateContentSize: function(ownerContext) { var me = this, containerDimensions = ((ownerContext.widthModel.shrinkWrap ? 1 : 0) | (ownerContext.heightModel.shrinkWrap ? 2 : 0)), calcWidth = (containerDimensions & 1) || undefined, calcHeight = (containerDimensions & 2) || undefined, needed = 0, props = ownerContext.props; if (calcWidth) { if (isNaN(props.contentWidth)) { ++needed; } else { calcWidth = undefined; } } if (calcHeight) { if (isNaN(props.contentHeight)) { ++needed; } else { calcHeight = undefined; } } if (needed) { if (calcWidth && !ownerContext.setContentWidth(me.measureContentWidth(ownerContext))) { me.done = false; } // eslint-disable-next-line max-len if (calcHeight && !ownerContext.setContentHeight(me.measureContentHeight(ownerContext))) { me.done = false; } // if (me.done) { // var el = ownerContext.targetContext.el.dom; // Ext.log(this.owner.id, '.contentSize: ', contentWidth, 'x', contentHeight, // ' => scrollSize: ', el.scrollWidth, 'x', el.scrollHeight); // } } }, /** * Handles overflow processing for a container. In addition to the ownerContext * passed to the {@link #calculate} method, this method also needs the containerSize * (the object returned by {@link #getContainerSize}). * @protected * * @param {Ext.layout.ContextItem} ownerContext */ calculateOverflow: function(ownerContext) { var me = this, width, height, scrollbarSize, scrollbars, xauto, yauto, targetEl; // Determine the dimensions that have overflow:auto applied. If these come by // way of component config, this does not require a DOM read: xauto = (me.getOverflowXStyle(ownerContext) === 'auto'); yauto = (me.getOverflowYStyle(ownerContext) === 'auto'); if (xauto || yauto) { scrollbarSize = Ext.getScrollbarSize(); targetEl = ownerContext.overflowContext.el.dom; scrollbars = 0; if (targetEl.scrollWidth > targetEl.clientWidth) { // has horizontal scrollbar scrollbars |= 1; } if (targetEl.scrollHeight > targetEl.clientHeight) { // has vertical scrollbar scrollbars |= 2; } width = (yauto && (scrollbars & 2)) ? scrollbarSize.width : 0; height = (xauto && (scrollbars & 1)) ? scrollbarSize.height : 0; if (width !== me.lastOverflowAdjust.width || height !== me.lastOverflowAdjust.height) { me.done = false; // we pass overflowAdjust and overflowState in as state for the next // cycle (these are discarded if one of our ownerCt's invalidates): ownerContext.invalidate({ state: { overflowAdjust: { width: width, height: height }, overflowState: scrollbars, secondPass: true } }); } } }, completeLayout: function(ownerContext) { this.lastOverflowAdjust = ownerContext.state.overflowAdjust; }, doRenderBody: function(out, renderData) { // Careful! This method is bolted on to the renderTpl so all we get for context is // the renderData! The "this" pointer is the renderTpl instance! var me = renderData.$layout, XTemplate = Ext.XTemplate, beforeBodyTpl = me.beforeBodyTpl, afterBodyTpl = me.afterBodyTpl; if (beforeBodyTpl) { XTemplate.getTpl(me, 'beforeBodyTpl').applyOut(renderData, out); } this.renderItems(out, renderData); this.renderContent(out, renderData); if (afterBodyTpl) { XTemplate.getTpl(me, 'afterBodyTpl').applyOut(renderData, out); } }, doRenderPadding: function(out, renderData) { // Careful! This method is bolted on to the renderTpl so all we get for context is // the renderData! The "this" pointer is the renderTpl instance! var me = renderData.$layout, owner = renderData.$layout.owner, padding = owner[owner.contentPaddingProperty]; if (me.managePadding && padding) { out.push('padding:', owner.unitizeBox(padding)); } }, finishedLayout: function(ownerContext) { var innerCt = this.innerCt; this.callParent(arguments); if (Ext.isIE8) { // IE8 needs a repaint to render percentage sized child items. innerCt.repaint(); } if (Ext.isOpera) { // Opera also needs a repaint to render percentage sized child items. but // the normal repaint() method doesn't seem to do the trick, but tweaking // the position property in combination with reading scrollWidth does. innerCt.setStyle('position', 'relative'); innerCt.dom.scrollWidth; innerCt.setStyle('position', ''); } }, /** * Returns the container size (that of the target). Only the fixed-sized dimensions can * be returned because the shrinkWrap dimensions are based on the contentWidth/Height * as determined by the container layout. * * If the {@link #calculateOverflow} method is used and if {@link #manageOverflow} is * true, this will adjust the width/height by the size of scrollbars. * * @param {Ext.layout.ContextItem} ownerContext The owner's context item. * @param {Boolean} [inDom=false] True if the container size must be in the DOM. * @return {Object} The size * @return {Number} return.width The width * @return {Number} return.height The height * @protected */ getContainerSize: function(ownerContext, inDom) { // Subtle But Important: // // We don't want to call getProp/hasProp et.al. unless we in fact need that value // for our results! If we call it and don't need it, the layout manager will think // we depend on it and will schedule us again should it change. var size = this.callParent(arguments), overflowAdjust = ownerContext.state.overflowAdjust; if (overflowAdjust) { size.width -= overflowAdjust.width; size.height -= overflowAdjust.height; } return size; }, getRenderData: function() { var me = this, data = me.callParent(); data.innerCtCls = me.innerCtCls; data.outerCtCls = me.outerCtCls; return data; }, // Overridden method from Ext.layout.container.Container. // Used in the beforeLayout method to render all items into. getRenderTarget: function() { return this.innerCt; }, // Overridden method from Ext.layout.container.Container. // Used by Container classes to insert special DOM elements which must exist // in addition to the child components getElementTarget: function() { return this.innerCt; }, /** * Returns the overflow-x style of the render target. * Note: If overflow is configured on a container using style or css class this method * will read the dom the first time it is called. It is therefore preferable for * performance reasons to use the {@link Ext.Component#scrollable scrollable} config when * horizontal overflow is desired. * @protected * @param {Ext.layout.ContextItem} ownerContext * @return {String} */ getOverflowXStyle: function(ownerContext) { return ownerContext.overflowXStyle || (ownerContext.overflowXStyle = this.owner.scrollFlags.overflowX || ownerContext.overflowContext.getStyle('overflow-x')); }, /** * Returns the overflow-y style of the render target. * Note: If overflow is configured on a container using style or css class this method * will read the dom the first time it is called. It is therefore preferable for * performance reasons to use the {@link Ext.Component#scrollable scrollable} config when * vertical overflow is desired. * @protected * @param {Ext.layout.ContextItem} ownerContext * @return {String} */ getOverflowYStyle: function(ownerContext) { return ownerContext.overflowYStyle || (ownerContext.overflowYStyle = this.owner.scrollFlags.overflowY || ownerContext.overflowContext.getStyle('overflow-y')); }, initContextItems: function(ownerContext) { var me = this, target = ownerContext.target, overflowEl = me.owner.getOverflowEl(); ownerContext.outerCtContext = ownerContext.getEl('outerCt', me); ownerContext.innerCtContext = ownerContext.getEl('innerCt', me); ownerContext.overflowContext = (overflowEl === ownerContext.el) ? ownerContext : ownerContext.getEl(overflowEl); if (target[target.contentPaddingProperty] !== undefined) { // If padding was defined using the contentPaddingProperty, we render the // the padding to the innerCt or outerCt (depending on the template that is // being used), so we need to set the paddingContext accordingly. // Otherwise we leave paddingContext as set by Container layout (defaults to // the targetContext) ownerContext.paddingContext = ownerContext.innerCtContext; } }, initLayout: function() { var me = this, scrollbarWidth = Ext.getScrollbarSize().width, owner = me.owner; me.callParent(); // Create a default lastOverflowAdjust based upon scrolling configuration. // If the Container is to overflow, or we *always* reserve space for a scrollbar // then reserve space for a vertical scrollbar if (scrollbarWidth && me.manageOverflow && !me.hasOwnProperty('lastOverflowAdjust')) { if (owner.scrollable || me.reserveScrollbar) { me.lastOverflowAdjust = { width: scrollbarWidth, height: 0 }; } } }, measureContentHeight: function(ownerContext) { // contentHeight includes padding, but not border, framing or margins var contentHeight = this.outerCt.getHeight(), target = ownerContext.target; if (this.managePadding && (target[target.contentPaddingProperty] === undefined)) { // if padding was not configured using the appropriate contentPaddingProperty // then the padding will not be on the paddingContext, and therfore not included // in the outerCt measurement, so we need to read the padding from the // targetContext contentHeight += ownerContext.targetContext.getPaddingInfo().height; } return contentHeight; }, measureContentWidth: function(ownerContext) { var dom, style, old, contentWidth, target; // In the newer Chrome versions, it won't measure the // width correctly without repainting the inner // cell in some circumstances. if (this.chromeCellMeasureBug) { dom = this.innerCt.dom; style = dom.style; old = style.display; if (old === 'table-cell') { style.display = ''; dom.offsetWidth; style.display = old; } } if (Ext.isSafari) { // EXTJS-12041: Safari needs a reflow of the outerCt to measure content width // correctly in some cases. The circumstances which make this happen are // very difficult to isolate, so we have to resort to always triggering a // reflow before measuring. We switch between table-cell and table in hopes // of minimizing the impact of the reflow on surrounding elements dom = this.outerCt.dom; style = dom.style; style.display = 'table-cell'; dom.offsetWidth; dom.style.display = ''; } // contentWidth includes padding, but not border, framing or margins contentWidth = this.outerCt.getWidth(); target = ownerContext.target; if (this.managePadding && (target[target.contentPaddingProperty] === undefined)) { // if padding was not configured using the appropriate contentPaddingProperty // then the padding will not be on the paddingContext, and therfore not included // in the outerCt measurement, so we need to read the padding from the // targetContext contentWidth += ownerContext.targetContext.getPaddingInfo().width; } return contentWidth; }, /** * This method sets the height and/or width of the outerCt/innerCt to adjust for the * following browser-specific issues: * * 1. In some browsers a percentage-height element ignores the horizontal scrollbar * of its parent (see Ext.supports.PercentageHeightOverflowBug). If the browser is * affected by this bug the outerCt needs a pixel height in order to support * percentage-height children when not shrink-wrapping height. If the browser is not * affected by this bug, a height of 100% is assigned to the outerCt (see * beginLayoutCycle). * * 2. IE8 mode has a bug with percentage height children. if the innerCt has * a height of 100%, has padding, and has a child item with a percentage height, that * child item will be sized as a percentage of the parent's height plus padding height. * In other words, a child with height:50% would have its height caclulated thusly: * (parentHeight + parentPaddingHeight) * 0.5 * To fix this, we have to give the innerCt a pixel height. * * @protected * @param {Ext.layout.ContextItem} ownerContext * @param {Object} containerSize */ setCtSizeIfNeeded: function(ownerContext, containerSize) { var me = this, height = containerSize.height, padding = ownerContext.paddingContext.getPaddingInfo(), targetEl = me.getTarget(), overflowXStyle = me.getOverflowXStyle(ownerContext), canOverflowX = (overflowXStyle === 'auto' || overflowXStyle === 'scroll'), scrollbarSize = Ext.getScrollbarSize(), needsOuterHeight, needsInnerHeight; if (height && !ownerContext.heightModel.shrinkWrap) { if (Ext.supports.PercentageHeightOverflowBug) { // set a pixel height on the outerCt if the browser ignores horizontal // scrollbar when rendering percentage-height elements needsOuterHeight = true; } if (Ext.isIE8) { // When not shrink wrapping, we set a pixel height on the innerCt to // support percentage height children in IE8. needsInnerHeight = true; } if ((needsOuterHeight || needsInnerHeight) && canOverflowX && (targetEl.dom.scrollWidth > targetEl.dom.clientWidth)) { // adjust the height for scrollbar size since it's not accounted for // in the containerSize. // IE8 does not tolerate negative sizes height = Math.max(height - scrollbarSize.height, 0); } if (needsOuterHeight) { ownerContext.outerCtContext.setProp('height', height + padding.height); me.hasOuterCtPxHeight = true; } if (needsInnerHeight) { ownerContext.innerCtContext.setProp('height', height); me.hasInnerCtPxHeight = true; } } }, setupRenderTpl: function(renderTpl) { this.callParent(arguments); renderTpl.renderPadding = this.doRenderPadding; }, getContentTarget: function() { return this.innerCt; } }, function(Cls) { var v = Ext.chromeVersion; // This was likely fixed much earlier, on the bug tracker marked as fixed on 2014/04/01. // 34 was the most recently released version after this date. Google doesn't release older // versions to test on so it's not possible to say. However due to the auto update nature it's // highly unlikely anyone is running this range anyway. Cls.prototype.chromeCellMeasureBug = Ext.isChrome && v >= 26 && v <= 34;});