/** * This class is intended to be extended or created via the * {@link Ext.container.Container#layout layout} configuration property. * See {@link Ext.container.Container#layout} for additional details. */Ext.define('Ext.layout.container.Container', { extend: 'Ext.layout.Layout', alternateClassName: 'Ext.layout.ContainerLayout', alias: 'layout.container', mixins: [ 'Ext.util.ElementContainer' ], requires: [ 'Ext.XTemplate' ], type: 'container', /** * @cfg {String} itemCls * An optional extra CSS class that will be added to the container. This can be useful for * adding customized styles to the container or any of its children using standard CSS * rules. See {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also. */ /** * @private * Called by an owning Panel before the Panel begins its collapse process. * Most layouts will not need to override the default Ext.emptyFn implementation. */ beginCollapse: Ext.emptyFn, /** * @private * Called by an owning Panel before the Panel begins its expand process. * Most layouts will not need to override the default Ext.emptyFn implementation. */ beginExpand: Ext.emptyFn, /** * An object which contains boolean properties specifying which properties are to be * animated upon flush of child Component ContextItems. For example, Accordion would * have: * * { * y: true, * height: true * } * * @private */ animatePolicy: null, /** * @private * tracks the number of child items that do not use "liquid" CSS layout */ activeItemCount: 0, renderTpl: [ '{%this.renderBody(out,values)%}' ], usesContainerHeight: true, usesContainerWidth: true, usesHeight: true, usesWidth: true, constructor: function() { this.callParent(arguments); this.mixins.elementCt.constructor.call(this); }, destroy: function() { this.mixins.elementCt.destroy.call(this); this.callParent(); }, /** * In addition to work done by our base classes, containers benefit from some extra * cached data. The following properties are added to the ownerContext: * * - visibleItems: the result of {@link #getVisibleItems} * - childItems: the ContextItem[] for each visible item * - targetContext: the ContextItem for the {@link #getTarget} element */ beginLayout: function(ownerContext) { this.callParent(arguments); ownerContext.targetContext = ownerContext.paddingContext = ownerContext.getEl('getTarget', this); this.cacheChildItems(ownerContext); }, beginLayoutCycle: function(ownerContext, firstCycle) { var me = this; me.callParent(arguments); if (firstCycle) { if (me.usesContainerHeight) { ++ownerContext.consumersContainerHeight; } if (me.usesContainerWidth) { ++ownerContext.consumersContainerWidth; } } }, cacheChildItems: function(ownerContext) { var me = this, context, childItems, items, length, i; // if we neither read nor set the size of our items, we can skip creation of // the childItems array if (me.needsItemSize || me.setsItemSize) { context = ownerContext.context; childItems = ownerContext.childItems = []; items = ownerContext.visibleItems = me.getVisibleItems(); length = items.length; for (i = 0; i < length; ++i) { childItems.push(context.getCmp(items[i])); } } }, cacheElements: function() { var owner = this.owner; this.attachChildEls(owner.el, owner); // from ElementContainer mixin }, calculate: function(ownerContext) { var props = ownerContext.props, el = ownerContext.el; if (ownerContext.widthModel.shrinkWrap && isNaN(props.width)) { ownerContext.setContentWidth(el.getWidth()); } if (ownerContext.heightModel.shrinkWrap && isNaN(props.height)) { ownerContext.setContentHeight(el.getHeight()); } }, /** * Adds layout's itemCls and owning Container's itemCls * @protected */ configureItem: function(item) { var me = this, itemCls = me.itemCls, ownerItemCls = me.owner.itemCls, needsCopy, addClasses; // Effectively callParent but without the function overhead item.ownerLayout = me; if (itemCls) { // itemCls can be a single class or an array if (typeof itemCls === 'string') { addClasses = [itemCls]; } else { addClasses = itemCls; needsCopy = !!addClasses; } } if (ownerItemCls) { // Add some extra logic so we don't clone the array unnecessarily if (needsCopy) { addClasses = Ext.Array.clone(addClasses); } addClasses = Ext.Array.push(addClasses || [], ownerItemCls); } if (addClasses) { item.addCls(addClasses); } }, 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! this.renderItems(out, renderData); this.renderContent(out, renderData); }, doRenderContainer: 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.$comp.layout, tpl = me.getRenderTpl(), data = me.getRenderData(); tpl.applyOut(data, out); }, doRenderItems: 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, tree = me.getRenderTree(); if (tree) { Ext.DomHelper.generateMarkup(tree, out); } }, doRenderTabGuard: function(out, renderData, position) { // 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 cmp = renderData.$comp, tabGuardTpl; // Due to framing, we will be called in two different ways: in the frameTpl or in // the renderTpl. The frameTpl version enters via doRenderFramingTabGuard which // sets "$skipTabGuards" on the renderTpl's renderData. // if (cmp.tabGuard && !renderData.$skipTabGuards) { tabGuardTpl = cmp.lookupTpl('tabGuardTpl'); if (tabGuardTpl) { renderData.tabGuard = position; renderData.tabGuardEl = cmp.tabGuardElements[position]; cmp.addChildEl(renderData.tabGuardEl); tabGuardTpl.applyOut(renderData, out); delete renderData.tabGuard; delete renderData.tabGuardEl; } } }, finishRender: function() { var me = this, target, items; me.callParent(); me.cacheElements(); target = me.getRenderTarget(); items = me.getLayoutItems(); me.finishRenderItems(target, items); }, /** * @private * Called for every layout in the layout context after all the layouts have been finally flushed */ notifyOwner: function() { //<debug> if (!this._hasTargetWarning && this.targetCls && !this.getTarget().hasCls(this.targetCls)) { this._hasTargetWarning = true; Ext.log.warn('targetCls is missing. This may mean that getTargetEl() ' + 'is being overridden but not applyTargetCls(). ' + this.owner.id); } //</debug> this.owner.afterLayout(this); }, /** * 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. * * @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 targetContext = ownerContext.targetContext, frameInfo = targetContext.getFrameInfo(), padding = ownerContext.paddingContext.getPaddingInfo(), got = 0, needed = 0, gotWidth, gotHeight, width, height; // In an shrinkWrap width/height case, we must not ask for any of these dimensions // because they will be determined by contentWidth/Height which is calculated by // this layout... // Fit/Card layouts are able to set just the width of children, allowing child's // resulting height to autosize the Container. // See examples/tabs/tabs.html for an example of this. if (!ownerContext.widthModel.shrinkWrap) { ++needed; width = inDom ? targetContext.getDomProp('width') : targetContext.getProp('width'); gotWidth = (typeof width === 'number'); if (gotWidth) { ++got; width -= frameInfo.width + padding.width; if (width < 0) { width = 0; } } } if (!ownerContext.heightModel.shrinkWrap) { ++needed; height = inDom ? targetContext.getDomProp('height') : targetContext.getProp('height'); gotHeight = (typeof height === 'number'); if (gotHeight) { ++got; height -= frameInfo.height + padding.height; if (height < 0) { height = 0; } } } return { width: width, height: height, needed: needed, got: got, gotAll: got === needed, gotWidth: gotWidth, gotHeight: gotHeight }; }, // This method is used to offset the DOM position when checking // whether the element is a certain child of the target. This is // required in cases where the extra elements prepended to the target // before any of the items. An example of this is when using labelAlign: 'top' // on a field. The label appears first in the DOM before any child items are // created, so when we check the position we need to add an extra offset. // Containers that create an innerCt are exempt because this new element // preserves the order getPositionOffset: function(position) { var offset; if (!this.createsInnerCt) { offset = this.owner.itemNodeOffset; if (offset) { position += offset; } } return position; }, /** * Returns an array of child components either for a render phase (Performed in the beforeLayout * method of the layout's base class), or the layout phase (onLayout). * @return {Ext.Component[]} of child components */ getLayoutItems: function() { var owner = this.owner, items = owner && owner.items; return (items && items.items) || []; }, getRenderData: function() { var comp = this.owner; return { $comp: comp, $layout: this, ownerId: comp.id }; }, /** * @protected * Returns all items that are rendered * @return {Array} All matching items */ getRenderedItems: function() { var me = this, target = me.getRenderTarget(), items = me.getLayoutItems(), ln = items.length, renderedItems = [], i, pos, item; for (i = 0, pos = 0; i < ln; i++, pos++) { item = items[i]; if (item.rendered) { if (item.ignoreDomPosition) { --pos; } else if (!this.isValidParent(item, target, pos)) { continue; } renderedItems.push(item); } } return renderedItems; }, /** * Returns the element into which rendering must take place. Defaults to the owner Container's * target element. * * May be overridden in layout managers which implement an inner element. * * @return {Ext.dom.Element} */ getRenderTarget: function() { return this.owner.getTargetEl(); }, /** * Returns the element into which extra functional DOM elements can be inserted. * Defaults to the owner Component's encapsulating element. * * May be overridden in Component layout managers which implement a * {@link #getRenderTarget component render target} which must only contain child components. * @return {Ext.dom.Element} */ getElementTarget: function() { return this.getRenderTarget(); }, getRenderTpl: function() { var me = this, renderTpl = Ext.XTemplate.getTpl(this, 'renderTpl'); // Make sure all standard callout methods for the owner component are placed on the // XTemplate instance (but only once please): if (!renderTpl.renderContent) { me.owner.setupRenderTpl(renderTpl); } return renderTpl; }, getRenderTree: function() { var result, items = this.owner.items, itemsGen, renderCfgs = {}; do { itemsGen = items.generation; result = this.getItemsRenderTree(this.getLayoutItems(), renderCfgs); } while (items.generation !== itemsGen); return result; }, renderChildren: function() { var me = this, ownerItems = me.owner.items, target = me.getRenderTarget(), itemsGen, items; // During the render phase, new items may be added. Specifically, a panel will // create a placeholder component during render if required, so we need to catch // it here so we can render it. do { itemsGen = ownerItems.generation; items = me.getLayoutItems(); me.renderItems(items, target); } while (ownerItems.generation !== itemsGen); }, getScrollbarsNeeded: function(width, height, contentWidth, contentHeight) { var scrollbarSize = Ext.getScrollbarSize(), hasWidth = typeof width === 'number', hasHeight = typeof height === 'number', needHorz = 0, needVert = 0; // No space-consuming scrollbars. if (!scrollbarSize.width) { return 0; } if (hasHeight && height < contentHeight) { needVert = 2; width -= scrollbarSize.width; } if (hasWidth && width < contentWidth) { needHorz = 1; if (!needVert && hasHeight) { height -= scrollbarSize.height; if (height < contentHeight) { needVert = 2; } } } return needVert + needHorz; }, /** * Returns the owner component's resize element. * @return {Ext.dom.Element} */ getTarget: function() { return this.owner.getTargetEl(); }, /** * @protected * Returns all items that are both rendered and visible * @return {Array} All matching items */ getVisibleItems: function() { var target = this.getRenderTarget(), items = this.getLayoutItems(), ln = items.length, visibleItems = [], i, pos, item; for (i = 0, pos = 0; i < ln; i++, pos++) { item = items[i]; if (item.rendered && item.hidden !== true && !item.floated) { if (item.ignoreDomPosition) { --pos; } else if (!this.isValidParent(item, target, pos)) { continue; } visibleItems.push(item); } } return visibleItems; }, getMoveAfterIndex: function(after) { return this.owner.items.indexOf(after) + 1; }, moveItemBefore: function(item, before) { var owner = this.owner, items = owner.items, index = items.indexOf(item), toIndex; if (item === before) { return item; } if (before) { toIndex = items.indexOf(before); if (index > -1 && index < toIndex) { --toIndex; } } else { toIndex = items.length; } return owner.insert(toIndex, item); }, setupRenderTpl: function(renderTpl) { renderTpl.renderBody = this.doRenderBody; renderTpl.renderContainer = this.doRenderContainer; renderTpl.renderItems = this.doRenderItems; renderTpl.renderTabGuard = this.doRenderTabGuard; }, getContentTarget: function() { return this.owner.getDefaultContentTarget(); }, onAdd: function(item) { if (!item.liquidLayout) { ++this.activeItemCount; } this.callParent([item]); }, onRemove: function(item, isDestroying) { if (!item.liquidLayout) { --this.activeItemCount; } this.callParent([item, isDestroying]); }});