3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.container.AbstractContainer
17 * @extends Ext.Component
18 * An abstract base class which provides shared methods for Containers across the Sencha product line.
21 Ext.define('Ext.container.AbstractContainer', {
23 /* Begin Definitions */
25 extend: 'Ext.Component',
28 'Ext.util.MixedCollection',
29 'Ext.layout.container.Auto',
35 * @cfg {String/Object} layout
36 * <p><b>Important</b>: In order for child items to be correctly sized and
37 * positioned, typically a layout manager <b>must</b> be specified through
38 * the <code>layout</code> configuration option.</p>
39 * <p>The sizing and positioning of child {@link #items} is the responsibility of
40 * the Container's layout manager which creates and manages the type of layout
41 * you have in mind. For example:</p>
42 * <p>If the {@link #layout} configuration is not explicitly specified for
43 * a general purpose container (e.g. Container or Panel) the
44 * {@link Ext.layout.container.Auto default layout manager} will be used
45 * which does nothing but render child components sequentially into the
46 * Container (no sizing or positioning will be performed in this situation).</p>
47 * <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
48 * <div><ul class="mdetail-params">
49 * <li><u>Specify as an Object</u></li>
50 * <div><ul class="mdetail-params">
51 * <li>Example usage:</li>
59 * <li><code><b>type</b></code></li>
60 * <br/><p>The layout type to be used for this container. If not specified,
61 * a default {@link Ext.layout.container.Auto} will be created and used.</p>
62 * <p>Valid layout <code>type</code> values are:</p>
63 * <div class="sub-desc"><ul class="mdetail-params">
64 * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> <b>Default</b></li>
65 * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
66 * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
67 * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
68 * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
69 * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
70 * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
73 * <li>Layout specific configuration properties</li>
74 * <p>Additional layout specific configuration properties may also be
75 * specified. For complete details regarding the valid config options for
76 * each layout type, see the layout class corresponding to the <code>type</code>
81 * <li><u>Specify as a String</u></li>
82 * <div><ul class="mdetail-params">
83 * <li>Example usage:</li>
87 * <li><code><b>layout</b></code></li>
88 * <p>The layout <code>type</code> to be used for this container (see list
89 * of valid layout type values above).</p>
90 * <p>Additional layout specific configuration properties. For complete
91 * details regarding the valid config options for each layout type, see the
92 * layout class corresponding to the <code>layout</code> specified.</p>
93 * </ul></div></ul></div>
97 * @cfg {String/Number} activeItem
98 * A string component id or the numeric index of the component that should be initially activated within the
99 * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
100 * item in the container's collection). activeItem only applies to layout styles that can display
101 * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
104 * @cfg {Object/Object[]} items
105 * <p>A single item, or an array of child Components to be added to this container</p>
106 * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
107 * its encapsulating element and performs no sizing or positioning upon them.</b><p>
110 // specifying a single item
112 layout: 'fit', // The single items is sized to fit
114 // specifying multiple items
115 items: [{...}, {...}],
116 layout: 'hbox', // The items are arranged horizontally
118 * <p>Each item may be:</p>
120 * <li>A {@link Ext.Component Component}</li>
121 * <li>A Component configuration object</li>
123 * <p>If a configuration object is specified, the actual type of Component to be
124 * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
125 * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
126 * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
127 * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
128 * <p><b>Notes</b>:</p>
129 * <p>Ext uses lazy rendering. Child Components will only be rendered
130 * should it become necessary. Items are automatically laid out when they are first
131 * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
132 * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
133 * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
136 * @cfg {Object/Function} defaults
137 * This option is a means of applying default settings to all added items whether added through the {@link #items}
138 * config or via the {@link #add} or {@link #insert} methods.
140 * Defaults are applied to both config objects and instantiated components conditionally so as not to override
141 * existing properties in the item (see {@link Ext#applyIf}).
143 * If the defaults option is specified as a function, then the function will be called using this Container as the
144 * scope (`this` reference) and passing the added item as the first parameter. Any resulting object
145 * from that call is then applied to the item as default properties.
147 * For example, to automatically apply padding to the body of each of a set of
148 * contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
152 * defaults: { // defaults are applied to items, not the container
156 * // default will not be applied here, panel1 will be autoScroll: false
162 * // this component will have autoScroll: true
163 * new Ext.panel.Panel({
169 /** @cfg {Boolean} suspendLayout
170 * If true, suspend calls to doLayout. Useful when batching multiple adds to a container and not passing them
171 * as multiple arguments or an array.
173 suspendLayout : false,
175 /** @cfg {Boolean} autoDestroy
176 * If true the container will automatically destroy any contained component that is removed from it, else
177 * destruction must be handled manually.
182 /** @cfg {String} defaultType
183 * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
184 * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
185 * <p>Defaults to <code>'panel'</code>.</p>
187 defaultType: 'panel',
192 * The number of container layout calls made on this object.
193 * @property layoutCounter
199 baseCls: Ext.baseCSSPrefix + 'container',
202 * @cfg {String[]} bubbleEvents
203 * <p>An array of events that, when fired, should be bubbled to any parent container.
204 * See {@link Ext.util.Observable#enableBubble}.
205 * Defaults to <code>['add', 'remove']</code>.
207 bubbleEvents: ['add', 'remove'],
210 initComponent : function(){
215 * Fires when the components in this container are arranged by the associated layout manager.
216 * @param {Ext.container.Container} this
217 * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
222 * Fires before any {@link Ext.Component} is added or inserted into the container.
223 * A handler can return false to cancel the add.
224 * @param {Ext.container.Container} this
225 * @param {Ext.Component} component The component being added
226 * @param {Number} index The index at which the component will be added to the container's items collection
230 * @event beforeremove
231 * Fires before any {@link Ext.Component} is removed from the container. A handler can return
232 * false to cancel the remove.
233 * @param {Ext.container.Container} this
234 * @param {Ext.Component} component The component being removed
240 * Fires after any {@link Ext.Component} is added or inserted into the container.
241 * @param {Ext.container.Container} this
242 * @param {Ext.Component} component The component that was added
243 * @param {Number} index The index at which the component was added to the container's items collection
249 * Fires after any {@link Ext.Component} is removed from the container.
250 * @param {Ext.container.Container} this
251 * @param {Ext.Component} component The component that was removed
256 // layoutOnShow stack
257 me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
263 initItems : function() {
268 * The MixedCollection containing all the child items of this container.
270 * @type Ext.util.MixedCollection
272 me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
275 if (!Ext.isArray(items)) {
284 afterRender : function() {
289 renderChildren: function () {
291 layout = me.getLayout();
294 // this component's elements exist, so now create the child components' elements
297 me.suspendLayout = true;
298 layout.renderChildren();
299 delete me.suspendLayout;
304 setLayout : function(layout) {
305 var currentLayout = this.layout;
307 if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
308 currentLayout.setOwner(null);
311 this.layout = layout;
312 layout.setOwner(this);
316 * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
317 * If a layout has not been instantiated yet, that is done first
318 * @return {Ext.layout.container.AbstractContainer} The layout
320 getLayout : function() {
322 if (!me.layout || !me.layout.isLayout) {
323 me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
330 * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
332 * @return {Ext.container.Container} this
334 doLayout : function() {
336 layout = me.getLayout();
338 if (me.rendered && layout && !me.suspendLayout) {
339 // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
340 if (!me.isFixedWidth() || !me.isFixedHeight()) {
341 // Only run the ComponentLayout if it is not already in progress
342 if (me.componentLayout.layoutBusy !== true) {
343 me.doComponentLayout();
344 if (me.componentLayout.layoutCancelled === true) {
349 // Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
351 // Only run the ContainerLayout if it is not already in progress
352 if (layout.layoutBusy !== true) {
362 afterLayout : function(layout) {
363 ++this.layoutCounter;
364 this.fireEvent('afterlayout', this, layout);
368 prepareItems : function(items, applyDefaults) {
369 if (!Ext.isArray(items)) {
373 // Make sure defaults are applied and item is initialized
378 for (; i < len; i++) {
381 item = this.applyDefaults(item);
383 items[i] = this.lookupComponent(item);
389 applyDefaults : function(config) {
390 var defaults = this.defaults;
393 if (Ext.isFunction(defaults)) {
394 defaults = defaults.call(this, config);
397 if (Ext.isString(config)) {
398 config = Ext.ComponentManager.get(config);
400 Ext.applyIf(config, defaults);
407 lookupComponent : function(comp) {
408 return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
412 createComponent : function(config, defaultType) {
413 // // add in ownerCt at creation time but then immediately
414 // // remove so that onBeforeAdd can handle it
415 // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
417 // delete component.initialConfig.ownerCt;
418 // delete component.ownerCt;
420 return Ext.ComponentManager.create(config, defaultType || this.defaultType);
423 // @private - used as the key lookup function for the items collection
424 getComponentId : function(comp) {
425 return comp.getItemId();
430 Adds {@link Ext.Component Component}(s) to this Container.
434 - Fires the {@link #beforeadd} event before adding.
435 - The Container's {@link #defaults default config values} will be applied
436 accordingly (see `{@link #defaults}` for details).
437 - Fires the `{@link #add}` event after the component has been added.
441 If the Container is __already rendered__ when `add`
442 is called, it will render the newly added Component into its content area.
444 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
445 will recalculate its internal layout at this time too.
447 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
449 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
451 tb = new {@link Ext.toolbar.Toolbar}({
452 renderTo: document.body
453 }); // toolbar is rendered
454 tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
458 Components directly managed by the BorderLayout layout manager
459 may not be removed or added. See the Notes for {@link Ext.layout.container.Border BorderLayout}
462 * @param {Ext.Component[]/Ext.Component...} component
463 * Either one or more Components to add or an Array of Components to add.
464 * See `{@link #items}` for additional information.
466 * @return {Ext.Component[]/Ext.Component} The Components that were added.
471 args = Array.prototype.slice.call(arguments),
481 if (typeof args[0] == 'number') {
482 index = args.shift();
485 hasMultipleArgs = args.length > 1;
486 if (hasMultipleArgs || Ext.isArray(args[0])) {
488 items = hasMultipleArgs ? args : args[0];
489 // Suspend Layouts while we add multiple items to the container
490 me.suspendLayout = true;
491 for (i = 0, ln = items.length; i < ln; i++) {
496 Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
501 item = me.add(index + i, item);
507 // Resume Layouts now that all items have been added and do a single layout for all the items just added
508 me.suspendLayout = false;
513 cmp = me.prepareItems(args[0], true)[0];
515 // Floating Components are not added into the items collection
516 // But they do get an upward ownerCt link so that they can traverse
517 // up to their z-index parent.
519 cmp.onAdded(me, index);
521 index = (index !== -1) ? index : me.items.length;
522 if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
523 me.items.insert(index, cmp);
524 cmp.onAdded(me, index);
525 me.onAdd(cmp, index);
526 me.fireEvent('add', me, cmp, index);
534 onRemove : Ext.emptyFn,
537 * Inserts a Component into this Container at a specified index. Fires the
538 * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
539 * Component has been inserted.
540 * @param {Number} index The index at which the Component will be inserted
541 * into the Container's items collection
542 * @param {Ext.Component} component The child Component to insert.<br><br>
543 * Ext uses lazy rendering, and will only render the inserted Component should
544 * it become necessary.<br><br>
545 * A Component config object may be passed in order to avoid the overhead of
546 * constructing a real Component object if lazy rendering might mean that the
547 * inserted Component will not be rendered immediately. To take advantage of
548 * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
549 * property to the registered type of the Component wanted.<br><br>
550 * For a list of all available xtypes, see {@link Ext.Component}.
551 * @return {Ext.Component} component The Component (or config object) that was
552 * inserted with the Container's default config values applied.
554 insert : function(index, comp) {
555 return this.add(index, comp);
559 * Moves a Component within the Container
560 * @param {Number} fromIdx The index the Component you wish to move is currently at.
561 * @param {Number} toIdx The new index for the Component.
562 * @return {Ext.Component} component The Component (or config object) that was moved.
564 move : function(fromIdx, toIdx) {
565 var items = this.items,
567 item = items.removeAt(fromIdx);
568 if (item === false) {
571 items.insert(toIdx, item);
577 onBeforeAdd : function(item) {
581 item.ownerCt.remove(item, false);
584 if (me.border === false || me.border === 0) {
585 item.border = (item.border === true);
590 * Removes a component from this container. Fires the {@link #beforeremove} event before removing, then fires
591 * the {@link #remove} event after the component has been removed.
592 * @param {Ext.Component/String} component The component reference or id to remove.
593 * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
594 * Defaults to the value of this Container's {@link #autoDestroy} config.
595 * @return {Ext.Component} component The Component that was removed.
597 remove : function(comp, autoDestroy) {
599 c = me.getComponent(comp);
601 if (Ext.isDefined(Ext.global.console) && !c) {
602 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.");
606 if (c && me.fireEvent('beforeremove', me, c) !== false) {
607 me.doRemove(c, autoDestroy);
608 me.fireEvent('remove', me, c);
615 doRemove : function(component, autoDestroy) {
618 hasLayout = layout && me.rendered;
620 me.items.remove(component);
621 component.onRemoved();
624 layout.onRemove(component);
627 me.onRemove(component, autoDestroy);
629 if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
633 if (hasLayout && !autoDestroy) {
634 layout.afterRemove(component);
637 if (!me.destroying) {
643 * Removes all components from this container.
644 * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
645 * Defaults to the value of this Container's {@link #autoDestroy} config.
646 * @return {Ext.Component[]} Array of the destroyed components
648 removeAll : function(autoDestroy) {
650 removeItems = me.items.items.slice(),
653 len = removeItems.length,
656 // Suspend Layouts while we remove multiple items from the container
657 me.suspendLayout = true;
658 for (; i < len; i++) {
659 item = removeItems[i];
660 me.remove(item, autoDestroy);
662 if (item.ownerCt !== me) {
667 // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
668 me.suspendLayout = false;
675 // Used by ComponentQuery to retrieve all of the items
676 // which can potentially be considered a child of this Container.
677 // This should be overriden by components which have child items
678 // that are not contained in items. For example dockedItems, menu, etc
679 // IMPORTANT note for maintainers:
680 // Items are returned in tree traversal order. Each item is appended to the result array
681 // followed by the results of that child's getRefItems call.
682 // Floating child items are appended after internal child items.
683 getRefItems : function(deep) {
685 items = me.items.items,
691 for (; i < len; i++) {
694 if (deep && item.getRefItems) {
695 result.push.apply(result, item.getRefItems(true));
699 // Append floating items to the list.
700 // These will only be present after they are rendered.
701 if (me.floatingItems && me.floatingItems.accessList) {
702 result.push.apply(result, me.floatingItems.accessList);
709 * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
710 * each component. The scope (<code>this</code> reference) of the
711 * function call will be the scope provided or the current component. The arguments to the function
712 * will be the args provided or the current component. If the function returns false at any point,
713 * the cascade is stopped on that branch.
714 * @param {Function} fn The function to call
715 * @param {Object} [scope] The scope of the function (defaults to current component)
716 * @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
717 * @return {Ext.Container} this
719 cascade : function(fn, scope, origArgs){
721 cs = me.items ? me.items.items : [],
725 args = origArgs ? origArgs.concat(me) : [me],
726 componentIndex = args.length - 1;
728 if (fn.apply(scope || me, args) !== false) {
732 c.cascade(fn, scope, origArgs);
734 args[componentIndex] = c;
735 fn.apply(scope || cs, args);
743 * Examines this container's <code>{@link #items}</code> <b>property</b>
744 * and gets a direct child component of this container.
745 * @param {String/Number} comp This parameter may be any of the following:
746 * <div><ul class="mdetail-params">
747 * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
748 * or <code>{@link Ext.Component#id id}</code> of the child component </li>
749 * <li>a <b><code>Number</code></b> : representing the position of the child component
750 * within the <code>{@link #items}</code> <b>property</b></li>
752 * <p>For additional information see {@link Ext.util.MixedCollection#get}.
753 * @return Ext.Component The component (if found).
755 getComponent : function(comp) {
756 if (Ext.isObject(comp)) {
757 comp = comp.getItemId();
760 return this.items.get(comp);
764 * Retrieves all descendant components which match the passed selector.
765 * Executes an Ext.ComponentQuery.query using this container as its root.
766 * @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
767 * If no selector is specified all items will be returned.
768 * @return {Ext.Component[]} Components which matched the selector
770 query : function(selector) {
771 selector = selector || '*';
772 return Ext.ComponentQuery.query(selector, this);
776 * Retrieves the first direct child of this container which matches the passed selector.
777 * The passed in selector must comply with an Ext.ComponentQuery selector.
778 * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
779 * specified, the first child will be returned.
780 * @return Ext.Component
782 child : function(selector) {
783 selector = selector || '';
784 return this.query('> ' + selector)[0] || null;
788 * Retrieves the first descendant of this container which matches the passed selector.
789 * The passed in selector must comply with an Ext.ComponentQuery selector.
790 * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
791 * specified, the first child will be returned.
792 * @return Ext.Component
794 down : function(selector) {
795 return this.query(selector)[0] || null;
800 this.callParent(arguments);
801 this.performDeferredLayouts();
805 // Lay out any descendant containers who queued a layout operation during the time this was hidden
806 // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
807 performDeferredLayouts: function() {
808 var layoutCollection = this.layoutOnShow,
809 ln = layoutCollection.getCount(),
814 for (; i < ln; i++) {
815 item = layoutCollection.get(i);
816 needsLayout = item.needsLayout;
818 if (Ext.isObject(needsLayout)) {
819 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
822 layoutCollection.clear();
826 // Enable all immediate children that was previously disabled
827 onEnable: function() {
828 Ext.Array.each(this.query('[isFormField]'), function(item) {
829 if (item.resetDisable) {
831 delete item.resetDisable;
838 // Disable all immediate children that was previously disabled
839 onDisable: function() {
840 Ext.Array.each(this.query('[isFormField]'), function(item) {
841 if (item.resetDisable !== false && !item.disabled) {
843 item.resetDisable = true;
850 * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
851 * from being executed.
853 beforeLayout: function() {
858 beforeDestroy : function() {
864 while ((c = items.first())) {
865 me.doRemove(c, true);