/**
 * @class Ext.AbstractContainer
 * @extends Ext.Component
 * <p>An abstract base class which provides shared methods for Containers across the Sencha product line.</p>
 * Please refer to sub class's documentation
 */
Ext.define('Ext.AbstractContainer', {

    /* Begin Definitions */

    extend: 'Ext.Component',

    requires: [
        'Ext.util.MixedCollection',
        'Ext.layout.Manager',
        'Ext.layout.container.Auto',
        'Ext.ComponentMgr',
        'Ext.ComponentQuery',
        'Ext.ZIndexManager'
    ],

    /* End Definitions */
    /**
     * @cfg {String/Object} layout
     * <p><b>*Important</b>: In order for child items to be correctly sized and
     * positioned, typically a layout manager <b>must</b> be specified through
     * the <code>layout</code> configuration option.</p>
     * <br><p>The sizing and positioning of child {@link #items} is the responsibility of
     * the Container's layout manager which creates and manages the type of layout
     * you have in mind.  For example:</p>
     * <p>If the {@link #layout} configuration is not explicitly specified for
     * a general purpose container (e.g. Container or Panel) the
     * {@link Ext.layout.container.Auto default layout manager} will be used
     * which does nothing but render child components sequentially into the
     * Container (no sizing or positioning will be performed in this situation).</p>
     * <br><p><b><code>layout</code></b> may be specified as either as an Object or
     * as a String:</p><div><ul class="mdetail-params">
     *
     * <li><u>Specify as an Object</u></li>
     * <div><ul class="mdetail-params">
     * <li>Example usage:</li>
     * 
**/layout: { type: 'vbox', align: 'left' }
/** * * <li><code><b>type</b></code></li> * <br/><p>The layout type to be used for this container. If not specified, * a default {@link Ext.layout.container.Auto} will be created and used.</p> * <br/><p>Valid layout <code>type</code> values are:</p> * <div class="sub-desc"><ul class="mdetail-params"> * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li> * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li> * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li> * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li> * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li> * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li> * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li> * </ul></div> * * <li>Layout specific configuration properties</li> * <br/><p>Additional layout specific configuration properties may also be * specified. For complete details regarding the valid config options for * each layout type, see the layout class corresponding to the <code>type</code> * specified.</p> * * </ul></div> * * <li><u>Specify as a String</u></li> * <div><ul class="mdetail-params"> * <li>Example usage:</li> *
**/layout: { type: 'vbox', padding: '5', align: 'left' }
/** * <li><code><b>layout</b></code></li> * <br/><p>The layout <code>type</code> to be used for this container (see list * of valid layout type values above).</p><br/> * <br/><p>Additional layout specific configuration properties. For complete * details regarding the valid config options for each layout type, see the * layout class corresponding to the <code>layout</code> specified.</p> * </ul></div></ul></div> */ /** * @cfg {String/Number} activeItem * A string component id or the numeric index of the component that should be initially activated within the * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first * item in the container's collection). activeItem only applies to layout styles that can display * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}). */ /** * @cfg {Object/Array} items * <p>A single item, or an array of child Components to be added to this container</p> * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into * its encapsulating element and performs no sizing or positioning upon them.</b><p> * <p>Example:</p> *
**/// specifying a single item items: {...}, layout: 'fit', // The single items is sized to fit // specifying multiple items items: [{...}, {...}], layout: 'hbox', // The items are arranged horizontally
/** * <p>Each item may be:</p> * <ul> * <li>A {@link Ext.Component Component}</li> * <li>A Component configuration object</li> * </ul> * <p>If a configuration object is specified, the actual type of Component to be * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p> * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p> * <p>If an {@link Ext.Component#xtype xtype} is not explicitly * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p> * <p><b>Notes</b>:</p> * <p>Ext uses lazy rendering. Child Components will only be rendered * should it become necessary. Items are automatically laid out when they are first * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p> * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p> */ /** * @cfg {Object|Function} defaults * <p>This option is a means of applying default settings to all added items whether added through the {@link #items} * config or via the {@link #add} or {@link #insert} methods.</p> * <p>If an added item is a config object, and <b>not</b> an instantiated Component, then the default properties are * unconditionally applied. If the added item <b>is</b> an instantiated Component, then the default properties are * applied conditionally so as not to override existing properties in the item.</p> * <p>If the defaults option is specified as a function, then the function will be called using this Container as the * scope (<code>this</code> reference) and passing the added item as the first parameter. Any resulting object * from that call is then applied to the item as default properties.</p> * <p>For example, to automatically apply padding to the body of each of a set of * contained {@link Ext.panel.Panel} items, you could pass: <code>defaults: {bodyStyle:'padding:15px'}</code>.</p> * <p>Usage:</p>
**/defaults: { // defaults are applied to items, not the container autoScroll:true }, items: [ { xtype: 'panel', // defaults <b>do not</b> have precedence over id: 'panel1', // options in config objects, so the defaults autoScroll: false // will not be applied here, panel1 will be autoScroll:false }, new Ext.panel.Panel({ // defaults <b>do</b> have precedence over options id: 'panel2', // options in components, so the defaults autoScroll: false // will be applied here, panel2 will be autoScroll:true. }) ]
/** */ /** @cfg {Boolean} suspendLayout * If true, suspend calls to doLayout. Usefule when batching multiple adds to a container and not passing them * as multiple arguments or an array. */ suspendLayout : false, /** @cfg {Boolean} autoDestroy * If true the container will automatically destroy any contained component that is removed from it, else * destruction must be handled manually. * Defaults to true. */ autoDestroy : true, /** @cfg {String} defaultType * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p> * <p>Defaults to <code>'panel'</code>.</p> */ defaultType: 'panel', isContainer : true, baseCls: Ext.baseCSSPrefix + 'container', /** * @cfg {Array} bubbleEvents * <p>An array of events that, when fired, should be bubbled to any parent container. * See {@link Ext.util.Observable#enableBubble}. * Defaults to <code>['add', 'remove']</code>. */ bubbleEvents: ['add', 'remove'], // @private initComponent : function(){ var me = this; me.addEvents( /** * @event afterlayout * Fires when the components in this container are arranged by the associated layout manager. * @param {Ext.container.Container} this * @param {ContainerLayout} layout The ContainerLayout implementation for this container */ 'afterlayout', /** * @event beforeadd * Fires before any {@link Ext.Component} is added or inserted into the container. * A handler can return false to cancel the add. * @param {Ext.container.Container} this * @param {Ext.Component} component The component being added * @param {Number} index The index at which the component will be added to the container's items collection */ 'beforeadd', /** * @event beforeremove * Fires before any {@link Ext.Component} is removed from the container. A handler can return * false to cancel the remove. * @param {Ext.container.Container} this * @param {Ext.Component} component The component being removed */ 'beforeremove', /** * @event add * @bubbles * Fires after any {@link Ext.Component} is added or inserted into the container. * @param {Ext.container.Container} this * @param {Ext.Component} component The component that was added * @param {Number} index The index at which the component was added to the container's items collection */ 'add', /** * @event remove * @bubbles * Fires after any {@link Ext.Component} is removed from the container. * @param {Ext.container.Container} this * @param {Ext.Component} component The component that was removed */ 'remove', /** * @event beforecardswitch * Fires before this container switches the active card. This event * is only available if this container uses a CardLayout. Note that * TabPanel and Carousel both get a CardLayout by default, so both * will have this event. * A handler can return false to cancel the card switch. * @param {Ext.container.Container} this * @param {Ext.Component} newCard The card that will be switched to * @param {Ext.Component} oldCard The card that will be switched from * @param {Number} index The index of the card that will be switched to * @param {Boolean} animated True if this cardswitch will be animated */ 'beforecardswitch', /** * @event cardswitch * Fires after this container switches the active card. If the card * is switched using an animation, this event will fire after the * animation has finished. This event is only available if this container * uses a CardLayout. Note that TabPanel and Carousel both get a CardLayout * by default, so both will have this event. * @param {Ext.container.Container} this * @param {Ext.Component} newCard The card that has been switched to * @param {Ext.Component} oldCard The card that has been switched from * @param {Number} index The index of the card that has been switched to * @param {Boolean} animated True if this cardswitch was animated */ 'cardswitch' ); // layoutOnShow stack me.layoutOnShow = new Ext.util.MixedCollection(); me.callParent(); me.initItems(); }, // @private initItems : function() { var me = this, items = me.items; /** * The MixedCollection containing all the child items of this container. * @property items * @type Ext.util.MixedCollection */ me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId); if (items) { if (!Ext.isArray(items)) { items = [items]; } me.add(items); } }, // @private afterRender : function() { this.getLayout(); this.callParent(); }, // @private setLayout : function(layout) { var currentLayout = this.layout; if (currentLayout && currentLayout.isLayout && currentLayout != layout) { currentLayout.setOwner(null); } this.layout = layout; layout.setOwner(this); }, /** * Returns the {@link Ext.layout.AbstractContainer layout} instance currently associated with this Container. * If a layout has not been instantiated yet, that is done first * @return {Ext.layout.AbstractContainer} The layout */ getLayout : function() { var me = this; if (!me.layout || !me.layout.isLayout) { me.setLayout(Ext.layout.Manager.create(me.layout, 'autocontainer')); } return me.layout; }, /** * Manually force this container's layout to be recalculated. The framwork uses this internally to refresh layouts * form most cases. * @return {Ext.container.Container} this */ doLayout : function() { var me = this, layout = me.getLayout(); if (me.rendered && layout && !me.suspendLayout) { if (!Ext.isNumber(me.width) || (!Ext.isNumber(me.height)) && me.componentLayout.layoutBusy !== true) { me.doComponentLayout(); } else { if (me.layout.layoutBusy !== true) { layout.layout(); } } } return me; }, // @private afterLayout : function(layout) { this.fireEvent('afterlayout', this, layout); }, // @private prepareItems : function(items, applyDefaults) { if (!Ext.isArray(items)) { items = [items]; } // Make sure defaults are applied and item is initialized var i = 0, len = items.length, item; for (; i < len; i++) { item = items[i]; if (applyDefaults) { item = this.applyDefaults(item); } items[i] = this.lookupComponent(item); } return items; }, // @private applyDefaults : function(config) { var defaults = this.defaults; if (defaults) { if (Ext.isFunction(defaults)) { defaults = defaults.call(this, config); } if (Ext.isString(config)) { config = Ext.ComponentMgr.get(config); Ext.applyIf(config, defaults); } else if (!config.isComponent) { Ext.applyIf(config, defaults); } else { Ext.applyIf(config, defaults); } } return config; }, // @private lookupComponent : function(comp) { if (Ext.isString(comp)) { return Ext.ComponentMgr.get(comp); } else { return this.createComponent(comp); } return comp; }, // @private createComponent : function(config, defaultType) { // // add in ownerCt at creation time but then immediately // // remove so that onBeforeAdd can handle it // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType); // // delete component.initialConfig.ownerCt; // delete component.ownerCt; return Ext.ComponentMgr.create(config, defaultType || this.defaultType); }, // @private - used as the key lookup function for the items collection getComponentId : function(comp) { return comp.getItemId(); }, /** Adds {@link Ext.Component Component}(s) to this Container. ##Description:## - Fires the {@link #beforeadd} event before adding. - The Container's {@link #defaults default config values} will be applied accordingly (see `{@link #defaults}` for details). - Fires the `{@link #add}` event after the component has been added. ##Notes:## If the Container is __already rendered__ when `add` is called, you may need to call {@link #doLayout} to refresh the view which causes any unrendered child Components to be rendered. This is required so that you can `add` multiple child components if needed while only refreshing the layout once. For example: r tb = new {@link Ext.toolbar.Toolbar}(); .render(document.body); // toolbar is rendered .add({text:'Button 1'}); // add multiple items ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button') .add({text:'Button 2'}); ##Warning:## Containers directly managed by the BorderLayout layout manager may not be removed or added. See the Notes for {@link Ext.layout.container.Border BorderLayout} for more details. * @param {...Object/Array} component * Either one or more Components to add or an Array of Components to add. * See `{@link #items}` for additional information. * * @return {Ext.Component/Array} The Components that were added. * @markdown */ add : function() { var me = this, args = Array.prototype.slice.call(arguments), hasMultipleArgs, items, results = [], i, ln, item, index = -1, cmp; if (typeof args[0] == 'number') { index = args.shift(); } hasMultipleArgs = args.length > 1; if (hasMultipleArgs || Ext.isArray(args[0])) { items = hasMultipleArgs ? args : args[0]; // Suspend Layouts while we add multiple items to the container me.suspendLayout = true; for (i = 0, ln = items.length; i < ln; i++) { item = items[i]; if (!item) { throw "Trying to add a null item as a child of Container with itemId/id: " + me.getItemId(); } if (index != -1) { item = me.add(index + i, item); } else { item = me.add(item); } results.push(item); } // Resume Layouts now that all items have been added and do a single layout for all the items just added me.suspendLayout = false; me.doLayout(); return results; } cmp = me.prepareItems(args[0], true)[0]; // Floating Components are not added into the items collection // But they do get an upward ownerCt link so that they can traverse // up to their z-index parent. if (cmp.floating) { cmp.onAdded(me, index); } else { index = (index !== -1) ? index : me.items.length; if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) { me.items.insert(index, cmp); cmp.onAdded(me, index); me.onAdd(cmp, index); me.fireEvent('add', me, cmp, index); } me.doLayout(); } return cmp; }, /** * @private * <p>Called by Component#doAutoRender</p> * <p>Register a Container configured <code>floating: true</code> with this Container's {@link Ext.ZIndexManager ZIndexManager}.</p> * <p>Components added in ths way will not participate in the layout, but will be rendered * upon first show in the way that {@link Ext.window.Window Window}s are.</p> * <p></p> */ registerFloatingItem: function(cmp) { var me = this; if (!me.floatingItems) { me.floatingItems = new Ext.ZIndexManager(me); } me.floatingItems.register(cmp); }, onAdd : Ext.emptyFn, onRemove : Ext.emptyFn, /** * Inserts a Component into this Container at a specified index. Fires the * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the * Component has been inserted. * @param {Number} index The index at which the Component will be inserted * into the Container's items collection * @param {Ext.Component} component The child Component to insert.<br><br> * Ext uses lazy rendering, and will only render the inserted Component should * it become necessary.<br><br> * A Component config object may be passed in order to avoid the overhead of * constructing a real Component object if lazy rendering might mean that the * inserted Component will not be rendered immediately. To take advantage of * this 'lazy instantiation', set the {@link Ext.Component#xtype} config * property to the registered type of the Component wanted.<br><br> * For a list of all available xtypes, see {@link Ext.Component}. * @return {Ext.Component} component The Component (or config object) that was * inserted with the Container's default config values applied. */ insert : function(index, comp) { return this.add(index, comp); }, /** * Moves a Component within the Container * @param {Number} fromIdx The index the Component you wish to move is currently at. * @param {Number} toIdx The new index for the Component. * @return {Ext.Component} component The Component (or config object) that was moved. */ move : function(fromIdx, toIdx) { var items = this.items, item; item = items.removeAt(fromIdx); if (item === false) { return false; } items.insert(toIdx, item); this.doLayout(); return item; }, // @private onBeforeAdd : function(item) { if (item.ownerCt) { item.ownerCt.remove(item, false); } if (this.hideBorders === true){ item.border = (item.border === true); } }, /** * Removes a component from this container. Fires the {@link #beforeremove} event before removing, then fires * the {@link #remove} event after the component has been removed. * @param {Component/String} component The component reference or id to remove. * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function. * Defaults to the value of this Container's {@link #autoDestroy} config. * @return {Ext.Component} component The Component that was removed. */ remove : function(comp, autoDestroy) { var me = this, c = me.getComponent(comp); //<debug> if (Ext.isDefined(Ext.global.console) && !c) { console.warn("Attempted to remove a component that does not exist. Ext.container.Container: remove takes an argument of the component to remove. cmp.remove() is incorrect usage."); } //</debug> if (c && me.fireEvent('beforeremove', me, c) !== false) { me.doRemove(c, autoDestroy); me.fireEvent('remove', me, c); } return c; }, // @private doRemove : function(component, autoDestroy) { var me = this, layout = me.layout, hasLayout = layout && me.rendered; me.items.remove(component); component.onRemoved(); if (hasLayout) { layout.onRemove(component); } me.onRemove(component, autoDestroy); if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) { component.destroy(); } if (hasLayout && !autoDestroy) { layout.afterRemove(component); } if (!me.destroying) { me.doLayout(); } }, /** * Removes all components from this container. * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function. * Defaults to the value of this Container's {@link #autoDestroy} config. * @return {Array} Array of the destroyed components */ removeAll : function(autoDestroy) { var me = this, removeItems = me.items.items.slice(), items = [], i = 0, len = removeItems.length, item; // Suspend Layouts while we remove multiple items from the container me.suspendLayout = true; for (; i < len; i++) { item = removeItems[i]; me.remove(item, autoDestroy); if (item.ownerCt !== me) { items.push(item); } } // Resume Layouts now that all items have been removed and do a single layout me.suspendLayout = false; me.doLayout(); return items; }, // Used by ComponentQuery to retrieve all of the items // which can potentially be considered a child of this Container. // This should be overriden by components which have child items // that are not contained in items. For example dockedItems, menu, etc getRefItems : function(deep) { var items = this.items.items.slice(), len = items.length, i = 0, item; // Include floating items in the list. // These will only be present after they are rendered. if (this.floatingItems) { items = items.concat(this.floatingItems.accessList); len = items.length; } if (deep) { for (; i < len; i++) { item = items[i]; if (item.getRefItems) { items = items.concat(item.getRefItems(true)); } } } return items; }, /** * Examines this container's <code>{@link #items}</code> <b>property</b> * and gets a direct child component of this container. * @param {String/Number} comp This parameter may be any of the following: * <div><ul class="mdetail-params"> * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code> * or <code>{@link Ext.Component#id id}</code> of the child component </li> * <li>a <b><code>Number</code></b> : representing the position of the child component * within the <code>{@link #items}</code> <b>property</b></li> * </ul></div> * <p>For additional information see {@link Ext.util.MixedCollection#get}. * @return Ext.Component The component (if found). */ getComponent : function(comp) { if (Ext.isObject(comp)) { comp = comp.getItemId(); } return this.items.get(comp); }, /** * Retrieves all descendant components which match the passed selector. * Executes an Ext.ComponentQuery.query using this container as its root. * @param {String} selector Selector complying to an Ext.ComponentQuery selector * @return {Array} Ext.Component's which matched the selector */ query : function(selector) { return Ext.ComponentQuery.query(selector, this); }, /** * Retrieves the first direct child of this container which matches the passed selector. * The passed in selector must comply with an Ext.ComponentQuery selector. * @param {String} selector An Ext.ComponentQuery selector * @return Ext.Component */ child : function(selector) { return this.query('> ' + selector)[0] || null; }, /** * Retrieves the first descendant of this container which matches the passed selector. * The passed in selector must comply with an Ext.ComponentQuery selector. * @param {String} selector An Ext.ComponentQuery selector * @return Ext.Component */ down : function(selector) { return this.query(selector)[0] || null; }, // inherit docs show : function() { this.callParent(arguments); this.performDeferredLayouts(); }, // Lay out any descendant containers who queued a layout operation during the time this was hidden // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops. performDeferredLayouts: function() { var layoutCollection = this.layoutOnShow, ln = layoutCollection.getCount(), i = 0, needsLayout, item; for (; i < ln; i++) { item = layoutCollection.get(i); needsLayout = item.needsLayout; if (Ext.isObject(needsLayout)) { item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt); } } layoutCollection.clear(); }, // @private beforeDestroy : function() { var me = this, items = me.items, c; if (items) { while ((c = items.first())) { me.doRemove(c, true); } } Ext.destroy( me.layout, me.floatingItems ); me.callParent(); } });￿