/** * 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', { /* Begin Definitions */ alias: ['layout.container'], extend: 'Ext.layout.Layout', alternateClassName: 'Ext.layout.ContainerLayout', mixins: { elementCt: 'Ext.util.ElementContainer' }, requires: [ 'Ext.XTemplate' ], type: 'container', /* End Definitions */ /** * @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, childEls: [ /** * @property {Ext.Element} overflowPadderEl * The element used to correct body padding during overflow. */ 'overflowPadderEl' ], 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.callParent(); this.mixins.elementCt.destroy.call(this); }, /** * 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 context = ownerContext.context, childItems = [], items = this.getVisibleItems(), length = items.length, i; ownerContext.childItems = childItems; ownerContext.visibleItems = items; for (i = 0; i < length; ++i) { childItems.push(context.getCmp(items[i])); } }, cacheElements: function () { var owner = this.owner; this.applyChildEls(owner.el, owner.id); // from ElementContainer mixin }, /** * Adds layout's itemCls and owning Container's itemCls * @protected */ configureItem: function(item) { var me = this, itemCls = me.itemCls, ownerItemCls = me.owner.itemCls, addClasses; // Effectively callParent but without the function overhead item.ownerLayout = me; if (itemCls) { // itemCls can be a single clas or an array addClasses = typeof itemCls === 'string' ? [itemCls] : itemCls; } if (ownerItemCls) { 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); } }, finishRender: function () { var me = this, target, items; me.callParent(); me.cacheElements(); target = me.getRenderTarget(); items = me.getLayoutItems(); //<debug> if (me.targetCls && !me.getTarget().hasCls(me.targetCls)) { Ext.log.warn('targetCls is missing. This may mean that getTargetEl() is being overridden but not applyTargetCls(). ' + me.owner.id); } //</debug> me.finishRenderItems(target, items); }, /** * @private * Called for every layout in the layout context after all the layouts have been finally flushed */ notifyOwner: function() { 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) { if (!this.createsInnerCt) { var 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, item; for (i = 0; i < ln; i++) { item = items[i]; if (item.rendered && me.isValidParent(item, target, i)) { 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.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.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; }, 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.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, item; for (i = 0; i < ln; i++) { item = items[i]; if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) { visibleItems.push(item); } } return visibleItems; }, setupRenderTpl: function (renderTpl) { var me = this; renderTpl.renderBody = me.doRenderBody; renderTpl.renderContainer = me.doRenderContainer; renderTpl.renderItems = me.doRenderItems; }, getContentTarget: function(){ return this.owner.getDefaultContentTarget(); } });