/**
* Base class for any Ext.Component that may contain other Components. Containers handle the basic
* behavior of containing items, namely adding, inserting and removing items.
*
* The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
* Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can
* create a lightweight Container to be encapsulated by an HTML element to your specifications
* by using the {@link Ext.Component#autoEl autoEl} config option.
*
* The code below illustrates how to explicitly create a Container:
*
* @example
* // Explicitly create a Container
* Ext.create('Ext.container.Container', {
* layout: {
* type: 'hbox'
* },
* width: 400,
* renderTo: Ext.getBody(),
* border: 1,
* style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
* defaults: {
* labelWidth: 80,
* // implicitly create Container by specifying xtype
* xtype: 'datefield',
* flex: 1,
* style: {
* padding: '10px'
* }
* },
* items: [{
* xtype: 'datefield',
* name: 'startDate',
* fieldLabel: 'Start date'
* },{
* xtype: 'datefield',
* name: 'endDate',
* fieldLabel: 'End date'
* }]
* });
*
* ## Layout
*
* Container classes delegate the rendering of child Components to a layout manager class which must
* be configured into the Container using the `{@link #layout}` configuration property.
*
* When either specifying child `{@link #cfg-items}` of a Container, or dynamically
* {@link #method-add adding} Components to a Container, remember to consider how you wish
* the Container to arrange those child elements, and whether those child elements need to be sized
* using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
* {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them
* one after the other inside the Container, and **does not apply any sizing** at all.
*
* A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. GridPanels or
* TreePanels are added to Containers for which no `{@link #layout}` has been specified).
* If a Container is left to use the default {@link Ext.layout.container.Auto Auto} scheme,
* none of its child components will be resized, or changed in any way when the Container
* is resized.
*
* Certain layout managers allow dynamic addition of child components. Those that do include
* Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
* Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
*
* // Create the GridPanel.
* var myNewGrid = Ext.create('Ext.grid.Panel', {
* store: myStore,
* headers: myHeaders,
* title: 'Results', // the title becomes the title of the tab
* });
*
* myTabPanel.add(myNewGrid); // Ext.tab.Panel implicitly uses Ext.layout.container.Card
* myTabPanel.setActiveTab(myNewGrid);
*
* The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses
* {@link Ext.layout.container.Card} as its layout manager which means all its child items are sized
* to {@link Ext.layout.container.Fit fit} exactly into its client area.
*
* **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added
* to a TabPanel by wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}`
* specified) and then add that wrapping Panel to the TabPanel. The point to realize is that
* a GridPanel **is** a Component which can be added directly to a Container. If the wrapping Panel
* has no `{@link #layout}` configuration, then the overnested GridPanel will not be sized
* as expected.
*
* ## {@link Ext.Component#reference References} and {@link #referenceHolder Reference Holders}
*
* Reference holders are used to keep references to child components inside a hierarchy.
*
* This functionality allows the connection of encapsulated references between containers
* and their child components declaratively. Simple usage:
*
* Ext.define('Login', {
* extend: 'Ext.window.Window',
*
* // This config is not compatible with the more common "controller" config
* // used to specify a ViewController for the view. When a ViewController is
* // specified it effectively acts as the "reference holder" for the view. In
* // this example we simply mark this container as the reference holder for
* // demonstration purposes.
* referenceHolder: true,
*
* title: 'Login',
* items: [{
* xtype: 'form',
* items: [{
* xtype: 'textfield',
* reference: 'username', // A named reference to be held on the referenceHolder
* name: 'username',
* fieldLabel: 'Username'
* }, {
* xtype: 'textfield',
* reference: 'password', // A named reference to be held on the referenceHolder
* name: 'password',
* fieldLabel: 'Password'
* }]
* }]
* });
* var w = new Login();
* console.log(w.lookupReference('password')); // The password field
*
* Reference holders are also encapsulated, so a reference will only be put on the closest
* reference holder above it in the component hierarchy:
*
* var ct = new Ext.container.Container({
* referenceHolder: true,
* items: [{
* xtype: 'container',
* referenceHolder: true,
* reference: 'innerCt1',
* items: [{
* xtype: 'component',
* reference: 'a',
* id: 'a1'
* }, {
* xtype: 'component',
* reference: 'b',
* id: 'b1'
* }]
* }, {
* xtype: 'container',
* referenceHolder: true,
* reference: 'innerCt2',
* items: [{
* xtype: 'component',
* reference: 'a',
* id: 'a2'
* }, {
* xtype: 'component',
* reference: 'b',
* id: 'b2'
* }]
* }]
* });
* // The main container will not have references to a/b, each innerCt will
* console.log(ct.lookupReference('a'), ct.lookupReference('b'));
* var inner1 = ct.lookupReference('innerCt1');
* var inner2 = ct.lookupReference('innerCt2');
*
* console.log(inner1.lookupReference('a').id, inner1.lookupReference('b').id);
* console.log(inner2.lookupReference('a').id, inner2.lookupReference('b').id);
*
* If the view has a controller attached, it will automatically become a {@link #referenceHolder}.
* References will be available in both the view and the controller:
*
* Ext.define('ProfileController', {
* extend: 'Ext.app.ViewController',
* alias: 'controller.profile',
*
* init: function() {
* console.log(this.lookupReference('firstName'));
* }
* });
*
* Ext.define('Profile', {
* extend: 'Ext.form.Panel',
* controller: 'profile',
* items: [{
* xtype: 'textfield',
* reference: 'firstName',
* fieldLabel: 'First Name'
* }]
* });
*
* new Profile();
*
* ## Events & {@link #defaultListenerScope} ##
*
* Events can use the default listener scope to determine at runtime the appropriate place
* to fire. This allows for declarative binding of events in a useful way:
*
* Ext.define('MyView', {
* extend: 'Ext.container.Container',
* defaultListenerScope: true,
* referenceHolder: true,
* items: [{
* xtype: 'textfield',
* reference: 'myfield'
* }, {
* xtype: 'button',
* text: 'Set to A',
* listeners: {
* click: 'onButtonAClick'
* }
* }, {
* xtype: 'button',
* text: 'Set to B',
* listeners: {
* click: 'onButtonBClick'
* }
* }],
*
* onButtonAClick: function() {
* this.lookupReference('myfield').setValue('A');
* },
*
* onButtonBClick: function() {
* this.lookupReference('myfield').setValue('B');
* }
* });
*
* Like {@link #referenceHolder}, the {@link #defaultListenerScope} is encapsulated, the scope will
* be resolved at the closest {@link #defaultListenerScope} above it in the component hierarchy:
*
* var ct = new Ext.container.Container({
* defaultListenerScope: true,
* onCustomEvent: function() {
* console.log('Outer called'); // Will NOT be called
* },
* items: [{
* xtype: 'container',
* defaultListenerScope: true,
* onCustomEvent: function() {
* console.log('Inner called'); // Will be called
* },
* items: [{
* xtype: 'component',
* itemId: 'child',
* listeners: {
* customevent: 'onCustomEvent'
* }
* }]
* }]
* });
* // The main container will not have references to a/b, each innerCt will
* console.log(ct.lookupReference('a'), ct.lookupReference('b'));
* var inner1 = ct.lookupReference('innerCt1');
* var inner2 = ct.lookupReference('innerCt2');
*
* console.log(inner1.lookupReference('a').id, inner1.lookupReference('b').id);
* console.log(inner2.lookupReference('a').id, inner2.lookupReference('b').id);
*
* Similar to references, if a {@link Ext.app.ViewController} is attached to this view, it becomes
* the {@link #defaultListenerScope}, which means un-scoped, late bound events will be directed
* to the controller. This is powerful as it allows views to be totally declarative:
*
* Ext.define('MyApp.controller.Login', {
* extend : 'Ext.app.ViewController',
* alias : 'controller.login',
*
* init: function() {
* this.sendCount = 0;
* },
*
* onLoginClick : function(btn) {
* this.login();
* },
*
* onFieldSpecialKey : function(field, e) {
* if (e.getKey() === e.ENTER) {
* this.login();
* }
* },
*
* login : function() {
* var form = this.lookupReference('form');
* this.lookupReference('error').hide();
* if (form.isValid()) {
* console.log('Do the login!');
* // Server responded...
* if (++this.sendCount % 2 === 0) {
* this.onServerSuccess();
* } else {
* this.onServerFailure();
* }
* }
* },
*
* onServerSuccess: function() {
* // Proceed
* console.log('All good');
* },
*
* onServerFailure: function() {
* var error = this.lookupReference('error');
* error.update('Invalid username/password');
* error.show();
* }
* });
*
* Ext.define('MyApp.view.Login', {
* extend : 'Ext.window.Window',
* controller : 'login',
* referenceHolder: true,
*
* title : 'Login',
* width : 400,
*
* items : [{
* xtype : 'form',
* reference : 'form',
* border : false,
* bodyPadding : 10,
* defaultType : 'textfield',
* defaults : {
* anchor : '90%',
* allowBlank : false,
* enableKeyEvents : true
* },
* items : [{
* xtype: 'component',
* reference: 'error',
* hidden: true,
* margin: '0 0 10 0',
* style: 'color: red;'
* }, {
* name : 'username',
* fieldLabel : 'Username',
* reference : 'username',
* listeners : {
* specialkey : 'onFieldSpecialKey'
* }
* }, {
* name : 'password',
* fieldLabel : 'Password',
* reference : 'password',
* inputType : 'password',
* listeners : {
* specialkey : 'onFieldSpecialKey'
* }
* }]
* }],
* buttons : ['->', {
* text : 'Login',
* listeners : {
* click : 'onLoginClick'
* }
* }]
* });
*
* ## Adding via remote configuration
*
* A server side script can be used to add Components which are generated dynamically on the server.
* An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server
* based on certain parameters:
*
* // execute an Ajax request to invoke server side script:
* Ext.Ajax.request({
* url: 'gen-invoice-grid.php',
* // send additional parameters to instruct server script
* params: {
* startDate: Ext.getCmp('start-date').getValue(),
* endDate: Ext.getCmp('end-date').getValue()
* },
* // process the response object to add it to the TabPanel:
* success: function(xhr) {
* var newComponent = eval(xhr.responseText); // see discussion below
* myTabPanel.add(newComponent); // add the component to the TabPanel
* myTabPanel.setActiveTab(newComponent);
* },
* failure: function() {
* Ext.Msg.alert("Grid create failed", "Server communication failure");
* }
* });
*
* The server script needs to return a JSON representation of a configuration object, which,
* when decoded will return a config object with an {@link Ext.Component#xtype xtype}. The server
* might return the following JSON:
*
* {
* "xtype": 'grid',
* "title": 'Invoice Report',
* "store": {
* "model": 'Invoice',
* "proxy": {
* "type": 'ajax',
* "url": 'get-invoice-data.php',
* "reader": {
* "type": 'json'
* "record": 'transaction',
* "idProperty": 'id',
* "totalRecords": 'total'
* })
* },
* "autoLoad": {
* "params": {
* "startDate": '01/01/2008',
* "endDate": '01/31/2008'
* }
* }
* },
* "headers": [
* {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
* {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
* {"header": "Invoice Date", "width": 100, "dataIndex": 'date',
* "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
* {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney',
* "sortable": true}
* ]
* }
*
* When the above code fragment is passed through the `eval` function in the success handler
* of the Ajax request, the result will be a config object which, when added to a Container,
* will cause instantiation of a GridPanel. **Be sure that the Container is configured with a layout
* which sizes and positions the child items to your requirements.**
*
* **Note:** since the code above is _generated_ by a server script, the `autoLoad` params
* for the Store, the user's preferred date format, the metadata to allow generation of the Model
* layout, and the ColumnModel can all be generated into the code since these are all known
* on the server.
*/
Ext.define('Ext.container.Container', {
extend: 'Ext.Component',
xtype: 'container',
alternateClassName: [
'Ext.Container',
'Ext.AbstractContainer'
],
requires: [
'Ext.Action',
'Ext.util.MixedCollection',
'Ext.layout.container.Auto',
'Ext.ZIndexManager',
'Ext.util.ItemCollection'
],
mixins: [
'Ext.mixin.Queryable',
'Ext.mixin.Container',
'Ext.mixin.FocusableContainer'
],
/**
* @cfg renderTpl
* @inheritdoc
*/
renderTpl:
'<tpl if="hasTabGuard">{% this.renderTabGuard(out, values, \'before\'); %}</tpl>' +
'{% this.renderContainer(out,values) %}' +
'<tpl if="hasTabGuard">{% this.renderTabGuard(out, values, \'after\'); %}</tpl>',
// <editor-fold desc="Config">
// ***********************************************************************************
// Begin Config
// ***********************************************************************************
config: {
/**
* @cfg {Object} actions
* An object containing properties which define named {@link Ext.Action actions}
* for this container and any descendant components.
*
* An Action encapsulates a shareable, reusable set of properties which define a
* "clickable" UI component such as a {@link Ext.button.Button button} or
* {@link Ext.menu.Item menu item}, or {@link Ext.panel.Panel#tools panel header tool},
* or an {@link Ext.grid.column.Action ActionColumn item}
*
* An Action, or more conveniently, the *name* of an action prefixed with `'@'`
* may be used as a config object for creating child components which use a `handler`
* config property to reference a Controller method to invoke when the component is
* clicked.
*
* The property name is the action name, which may then be used as a child item
* configuration in an {@link Ext.container.Container#cfg!items items} configuration in
* any descendant component such as a toolbar or a menu, or in a
* {@link Ext.panel.Panel#tools tools} configuration of a Panel.
*
* The property value is a configuration object for any clickable component.
*
* See the {@link Ext.Action} class for an example of reusable Actions.
* @since 6.2.0
*/
actions: null
},
/**
* @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}).
*
* @since 2.3.0
*/
/**
* @cfg {Boolean} autoDestroy
* If true the container will automatically destroy any contained component that is removed
* from it, else destruction must be handled manually.
* @since 2.3.0
*/
autoDestroy: true,
/**
* @cfg {String[]} bubbleEvents
* An array of events that, when fired, should be bubbled to any parent container.
* See {@link Ext.util.Observable#enableBubble}.
* @since 3.4.0
*/
/**
* @cfg {Object/Function} defaults
* This option is a means of applying default settings to all added items whether added
* through the {@link #cfg-items} config or via the {@link #method-add} or {@link #insert}
* methods.
*
* Defaults are applied to both config objects and instantiated components conditionally
* so as not to override existing properties in the item (see {@link Ext#applyIf}).
*
* If the defaults option is specified as a function, then the function will be called
* using this Container as the scope (`this` 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.
*
* For example, to automatically apply padding to the body of each of a set of
* contained {@link Ext.panel.Panel} items, you could pass:
* `defaults: {bodyStyle:'padding:15px'}`.
*
* Usage:
*
* defaults: { // defaults are applied to items, not the container
* scrollable: true
* },
* items: [
* // default will not be applied here, panel1 will be scrollable: false
* {
* xtype: 'panel',
* id: 'panel1',
* scrollable: false
* },
* // this component will have scrollable: true
* new Ext.panel.Panel({
* id: 'panel2'
* })
* ]
*
* @since 2.3.0
*/
/**
* @cfg {String} defaultType
* 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.
* @since 2.3.0
*/
defaultType: 'panel',
/**
* @cfg {Boolean} detachOnRemove
* True to move any component to the {@link Ext#getDetachedBody detachedBody} when the component
* is removed from this container. This option is only applicable when the component
* is not destroyed while being removed, see {@link #autoDestroy} and {@link #method-remove}.
* If this option is set to false, the DOM of the component will remain in the current place
* until it is explicitly moved.
*/
detachOnRemove: true,
// eslint-disable-next-line max-len
// @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultTypeProperty: "defaultType", defaultsProperty: "defaults"}
/**
* @cfg {Object/Object[]} items
* A single item, or an array of child Components to be added to this container
*
* **Unless configured with a {@link #cfg!layout}, a Container simply renders child
* Components serially into its encapsulating element and performs no sizing or
* positioning upon them.**
*
* Example:
*
* // 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
*
* Each item may be:
*
* - A {@link Ext.Component Component}
* - A Component configuration object
*
* 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.
*
* Every Component class has its own {@link Ext.Component#xtype xtype}.
*
* If an {@link Ext.Component#xtype xtype} is not explicitly specified, the
* {@link #cfg-defaultType} for the Container is used, which by default is usually `panel`.
*
* # Notes:
*
* 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 #method-updateLayout}
* call.
*
* Do not specify {@link Ext.panel.Panel#contentEl contentEl} or
* {@link Ext.panel.Panel#html html} with `items`.
*
* @since 2.3.0
*/
items: undefined, // Not "null" so that Ext.applyIf(this, {items: []}) works
// @cmd-auto-dependency { aliasPrefix : "layout." }
/**
* @cfg {Ext.enums.Layout/Object} layout
* **Important**: In order for child items to be correctly sized and
* positioned, typically a layout manager **must** be specified through
* the `layout` configuration option.
*
* The sizing and positioning of child {@link #cfg-items} is the responsibility of
* the Container's layout manager which creates and manages the type of layout
* you have in mind. For example:
*
* If the 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).
*
* **layout** may be specified as either as an Object or as a String:
*
* ## Specify as an Object
*
* Example usage:
*
* layout: {
* type: 'vbox',
* align: 'left'
* }
*
* - **type**
*
* The layout type to be used for this container. If not specified,
* a default {@link Ext.layout.container.Auto} will be created and used.
*
* Valid layout <code>type</code> values are listed in {@link Ext.enums.Layout}.
*
* - Layout specific configuration properties
*
* 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 `type`
* specified.
*
* ## Specify as a String
*
* Example usage:
*
* layout: 'vbox'
*
* - **layout**
*
* The layout `type` to be used for this container (see {@link Ext.enums.Layout}
* for list of valid values).
*
* Additional layout specific configuration properties. For complete
* details regarding the valid config options for each layout type, see the
* layout class corresponding to the `layout` specified.
*
* ## Configuring the default layout type
*
* If a certain Container class has a default layout (For example a
* {@link Ext.toolbar.Toolbar Toolbar} with a default `Box` layout), then to simply configure
* the default layout, use an object, but without the `type` property:
*
*
* xtype: 'toolbar',
* layout: {
* pack: 'center'
* }
*
* @since 2.3.0
*
*/
layout: 'auto',
/**
* @cfg {Boolean} suspendLayout
* If true, suspend calls to updateLayout. Useful when batching multiple adds to a container
* and not passing them as multiple arguments or an array.
*/
suspendLayout: false,
/**
* @cfg {String} defaultFocus
*
* Specifies a child Component to receive focus when this Container's {@link #method-focus}
* method is called. Should be a valid {@link Ext.ComponentQuery query} selector.
*/
/**
* When set to `true`, two elements are added to the container's element. These are the
* `{@link #tabGuardBeforeEl}` and `{@link #tabGuardAfterEl}`.
* @cfg {Boolean} tabGuard
* @private
* @since 6.0.0
*/
// ***********************************************************************************
// End Config
// ***********************************************************************************
// </editor-fold>
// <editor-fold desc="Properties">
// ***********************************************************************************
// Begin Properties
// ***********************************************************************************
/* eslint-disable indent, max-len */
/**
* @property {String/String[]/Ext.XTemplate} tabGuardTpl
* This template is used to generate the `tabGuard` elements. It is used once per
* element (see `{@link #tabGuardBeforeEl}` and `{@link #tabGuardAfterEl}`).
* @private
* @since 6.0.0
*/
tabGuardTpl:
// We use span instead of div because of IE bug/misfeature: it will focus
// block elements upon clicking or calling node.focus() regardless of
// tabIndex attribute. It doesn't do that with inline elements, hence span.
'<span id="{id}-{tabGuardEl}" data-ref="{tabGuardEl}" aria-hidden="true"' +
' class="' + Ext.baseCSSPrefix + 'tab-guard ' +
Ext.baseCSSPrefix + 'tab-guard-{tabGuardPosition}"' +
' style="width:0px;height:0px;">' +
'</span>',
/* eslint-enable indent, max-len */
/**
* @property {Object} tabGuardElements
* Read only object containing property names for tab guard elements, keyed by position.
* @private
* @since 6.2.0
*/
tabGuardElements: {
before: 'tabGuardBeforeEl',
after: 'tabGuardAfterEl'
},
/**
* @property {Number} tabGuardBeforeIndex The tabIndex attribute value to assign
* to the "before" tab guard element. Default is `undefined` for automatic detection
* from the DOM.
* @private
* @since 6.2.0
*/
/**
* @property {Number} tabGuardAfterIndex The tabIndex attribute value to assign
* to the "after" tab guard element. Default is `undefined` for automatic detection
* from the DOM.
* @private
* @since 6.2.0
*/
/**
* This element reference is generated when `{@link #tabGuard}` is `true`. This element
* is generated before all `dockedItems` in the DOM.
* @property {Ext.dom.Element} tabGuardBeforeEl
* @private
* @since 6.0.0
*/
/**
* This element reference is generated when `{@link #tabGuard}` is `true`. This element
* is generated after all `dockedItems` in the DOM.
* @property {Ext.dom.Element} tabGuardAfterEl
* @private
* @since 6.0.0
*/
/**
* @private
*/
_applyDefaultsOptions: {
defaults: true,
strict: false
},
/**
* @property ariaRole
* @inheritdoc
*/
ariaRole: 'presentation',
/**
* @cfg baseCls
* @inheritdoc
*/
baseCls: Ext.baseCSSPrefix + 'container',
/**
* @property {Number} layoutCounter
* The number of container layout calls made on this object.
* @private
*/
layoutCounter: 0,
// ***********************************************************************************
// End Properties
// ***********************************************************************************
// </editor-fold>
// <editor-fold desc="Events">
// ***********************************************************************************
// Begin Events
// ***********************************************************************************
/**
* @event add
* 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
* @since 2.3.0
*/
/**
* @event afterlayout
* Fires when the components in this container are arranged by the associated layout manager.
* @param {Ext.container.Container} this
* @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this
* container
* @since 2.3.0
*/
/**
* @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
* @since 2.3.0
*/
/**
* @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
* @since 2.3.0
*/
/**
* @event remove
* 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
* @since 2.3.0
*/
/**
* @event childmove
* Fires after any {@link Ext.Component} has changed its ordinal position within the container.
* @param {Ext.container.Container} this
* @param {Ext.Component} component The component that was moved
* @param {Number} prevIndex The previous ordinal position of the Component
* @param {Number} newIndex The new ordinal position of the Component
*/
// ***********************************************************************************
// End Events
// ***********************************************************************************
// </editor-fold>
// <editor-fold desc="Methods">
// ***********************************************************************************
// Begin Methods
// ***********************************************************************************
/**
* 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 #event-add}` event after the component has been added.
*
* ## Notes:
*
* If the Container is __already rendered__ when `add`
* is called, it will render the newly added Component into its content area.
*
* **If** the Container was configured with a size-managing {@link #cfg!layout} manager,
* the Container will recalculate its internal layout at this time too.
*
* Note that the default layout manager simply renders child Components sequentially
* into the content area and thereafter performs no sizing.
*
* If adding multiple new child Components, pass them as an array to the `add` method,
* so that only one layout recalculation is performed.
*
* tb = new Ext.toolbar.Toolbar({
* renderTo: document.body
* }); // toolbar is rendered
* // add multiple items.
* // default type for Toolbar is 'button')
* tb.add([{text:'Button 1'}, {text:'Button 2'}]);
*
* To inject components between existing ones, use the {@link #insert} method.
*
* ## Warning:
*
* Components 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 {Ext.Component[]|Object[]/Ext.Component.../Object...} component
* Either one or more Components to add or an Array of Components to add.
* See `{@link #cfg-items}` for additional information.
*
* @return {Ext.Component[]/Ext.Component} The Components that were added.
*
* @since 2.3.0
*/
add: function() {
var me = this,
args = Ext.Array.slice(arguments),
index = (typeof args[0] === 'number') ? args.shift() : -1,
layout = me.getLayout(),
needsLayout = false,
addingArray, items, i, length, item, pos, ret, instanced;
if (args.length === 1 && Ext.isArray(args[0])) {
items = args[0];
addingArray = true;
}
else {
items = args;
}
if (me.rendered) {
Ext.suspendLayouts(); // suspend layouts while adding items...
}
ret = items = me.prepareItems(items, true);
length = items.length;
if (!addingArray && length === 1) { // an array of 1 should still return an array...
ret = items[0];
}
// loop
for (i = 0; i < length; i++) {
item = items[i];
//<debug>
if (!item) {
Ext.raise("Cannot add null item to Container with itemId/id: " + me.getItemId());
}
if (item.destroyed) {
Ext.raise("Cannot add destroyed item '" + item.getId() + "' to Container '" +
me.getId() + "'");
}
//</debug>
pos = (index < 0) ? me.items.length : (index + i);
instanced = !!item.instancedCmp;
delete item.instancedCmp;
// Floating Components are not added into the items collection,
// but to a separate floatingItems collection
if (item.floating) {
(me.floatingItems || (me.floatingItems = new Ext.util.ItemCollection())).add(item);
item.onAdded(me, pos, instanced);
delete item.$initParent;
if (me.hasListeners.add) {
me.fireEvent('add', me, item, pos);
}
}
// eslint-disable-next-line max-len
else if ((!me.hasListeners.beforeadd || me.fireEvent('beforeadd', me, item, pos) !== false) && me.onBeforeAdd(item) !== false) {
me.items.insert(pos, item);
item.onAdded(me, pos, instanced);
delete item.$initParent;
if (me.focusableContainer) {
me.onFocusableChildAdd(item);
}
me.onAdd(item, pos);
layout.onAdd(item, pos);
needsLayout = true;
if (me.hasListeners.add) {
me.fireEvent('add', me, item, pos);
}
}
// This flag may be set by onBeforeAdd to tell the layout system that any remove
// is temporary and that focus should not be reverted because Ext.layout.Layout#moveItem
// will be moving things into place soon, and that will handle keeping focus stable.
item.isLayoutMoving = false;
}
// We need to update our layout after adding all passed items
// Unless we only added floating items.
if (needsLayout) {
me.updateLayout();
}
if (me.rendered) {
if (length && me.focusableContainer) {
me.$initFocusableContainerAfterLayout = true;
}
Ext.resumeLayouts(true);
}
return ret;
},
onAdded: function(container, pos, instanced) {
this.callParent([container, pos, instanced]);
this.containerOnAdded(container, instanced);
},
/**
* @method onRemoved
* @inheritdoc
*/
onRemoved: function(destroying) {
this.containerOnRemoved(destroying);
this.callParent(arguments);
},
afterComponentLayout: function() {
var floaters = this.floatingItems,
floaterCount,
i, floater;
this.callParent(arguments);
// Contained, unrendered, autoShow items must be shown upon next layout of the Container
if (floaters) {
floaters = floaters.items;
floaterCount = floaters.length;
for (i = 0; i < floaterCount; i++) {
floater = floaters[i];
if (!floater.rendered && floater.autoShow) {
floater.show();
}
}
}
},
/**
* Invoked after the Container has laid out (and rendered if necessary)
* its child Components.
*
* @param {Ext.layout.container.Container} layout
*
* @template
* @protected
*/
afterLayout: function(layout) {
var me = this;
++me.layoutCounter;
if (me.hasListeners.afterlayout) {
me.fireEvent('afterlayout', me, layout);
}
// focusableContainer could have changed between setting the flag in add()
// and actual layout, so check again
if (me.focusableContainer && me.$initFocusableContainerAfterLayout) {
me.initFocusableContainer();
}
delete me.$initFocusableContainerAfterLayout;
},
doDestroy: function() {
var me = this,
items = me.items,
floatingItems = me.floatingItems,
c;
if (me.focusableContainer) {
me.destroyFocusableContainer();
}
if (items) {
while ((c = items.first())) {
me.doRemove(c, true);
}
items.destroy();
me.items = null;
}
if (floatingItems) {
while ((c = floatingItems.first())) {
me.doRemove(c, true);
}
floatingItems.destroy();
me.floatingItems = null;
}
Ext.destroy(me.layout);
me.callParent();
},
beforeRender: function() {
var me = this,
layout = me.getLayout(),
targetCls;
// In beforeRender in the parent we call disable to allow onDisable to be applied here.
me.preventChildDisable = true;
me.callParent();
me.preventChildDisable = false;
if (!layout.initialized) {
layout.initLayout();
}
targetCls = layout.targetCls;
if (targetCls) {
me.applyTargetCls(targetCls);
}
},
/**
* Cascades down the component/container heirarchy from this component (passed in
* the first call), calling the specified function with each component. The scope
* (this reference) of the function call will be the scope provided or the current
* component. The arguments to the function will be the args provided or the current
* component. If the function returns false at any point, the cascade is stopped on
* that branch.
* @param {Function} fn The function to call
* @param {Object} [scope] The scope of the function(defaults to current component)
* @param {Array} [origArgs] The args to call the function with. The current component
* always passed as the last argument.
* @return {Ext.Container} this
* @since 2.3.0
*/
cascade: function(fn, scope, origArgs) {
var me = this,
cs = me.items ? me.items.items : [],
len = cs.length,
i = 0,
c,
args = origArgs ? origArgs.concat(me) : [me],
componentIndex = args.length - 1;
if (fn.apply(scope || me, args) !== false) {
for (; i < len; i++) {
c = cs[i];
if (c.cascade) {
c.cascade(fn, scope, origArgs);
}
else {
args[componentIndex] = c;
fn.apply(scope || c, args);
}
}
}
return this;
},
/**
* Determines whether the passed Component is either an immediate child of this Container,
* or whether it is a descendant.
*
* @param {Ext.Component} comp The Component to test.
* @param {Boolean} [deep=false] Pass `true` to test for the Component being a descendant
* at any level.
* @return {Boolean} `true` if the passed Component is contained at the specified level.
*/
contains: function(comp, deep) {
var result = false;
if (deep) {
this.cascade(function(c) {
// Only test if the item is a container
if (c.contains && c.contains(comp)) {
result = true;
return false;
}
});
return result;
}
else {
return this.items.contains(comp) ||
(this.floatingItems && this.floatingItems.contains(comp));
}
},
/**
* Disables all child input fields and buttons.
* @param silent
* @param fromParent (private)
*/
disable: function(silent, fromParent) {
var me = this,
wasDisabled = me.disabled,
itemsToDisable, len, i;
me.callParent([silent, fromParent]);
if (!fromParent && !me.preventChildDisable && !wasDisabled) {
itemsToDisable = me.getChildItemsToDisable();
len = itemsToDisable.length;
for (i = 0; i < len; i++) {
itemsToDisable[i].disable(silent, true);
}
}
if (me.focusableContainer) {
me.activateFocusableContainer(false);
}
return me;
},
/**
* Enables all child input fields and buttons.
* @param silent
* @param fromParent (private)
*/
enable: function(silent, fromParent) {
var me = this,
wasDisabled = me.disabled,
itemsToDisable, len, i;
me.callParent([silent, fromParent]);
if (wasDisabled) {
itemsToDisable = me.getChildItemsToDisable();
len = itemsToDisable.length;
for (i = 0; i < len; i++) {
itemsToDisable[i].enable(silent, true);
}
}
if (me.focusableContainer) {
me.activateFocusableContainer(true);
}
return me;
},
/**
* Return the immediate child Component in which the passed element is located.
* @param {Ext.dom.Element/HTMLElement/String} el The element to test (or ID of element).
* @param {Boolean} deep If `true`, returns the deepest descendant Component which contains
* the passed element.
* @return {Ext.Component} The child item which contains the passed element.
*/
getChildByElement: function(el, deep) {
var item,
itemEl,
i = 0,
it = this.getRefItems(),
ln = it.length;
el = Ext.getDom(el);
for (; i < ln; i++) {
item = it[i];
itemEl = item.getEl();
if (itemEl && ((itemEl.dom === el) || itemEl.contains(el))) {
return (deep && item.getChildByElement) ? item.getChildByElement(el, deep) : item;
}
}
return null;
},
/**
* Examines this container's {@link #property-items} **property** and gets a direct child
* component of this container.
*
* @param {String/Number} comp This parameter may be any of the following:
*
* - a **String** : representing the {@link Ext.Component#itemId itemId}
* or {@link Ext.Component#id id} of the child component.
* - a **Number** : representing the position of the child component
* within the {@link #property-items} **property**
*
* For additional information see {@link Ext.util.MixedCollection#get}.
*
* @return {Ext.Component} The component (if found).
*
* @since 2.3.0
*/
getComponent: function(comp) {
if (Ext.isObject(comp)) {
comp = comp.getItemId();
}
// eslint-disable-next-line vars-on-top
var c = this.items.get(comp),
floaters = this.floatingItems;
// Only allow finding by index on the main items container
if (!c && floaters && typeof comp !== 'number') {
c = floaters.get(comp);
}
return c;
},
/**
* @protected
* Returns the focus holder element associated with this Container.
* By default, this is the Container's target element; however if {@link #defaultFocus}
* is defined, the child component referenced by that property will be found
* and returned instead.
*
* @return {Ext.dom.Element} the focus holding element.
*/
getFocusEl: function() {
var delegate = this.getDefaultFocus();
if (delegate) {
// DO NOT drill down to delegate's focusEl or return its main el here.
// Container's getFocusEl() is supposed to return delegates as components,
// otherwise things break elsewhere.
return delegate;
}
else if (this.focusable) {
return this.getTargetEl();
}
// Containers that are not focusable should not return a focusEl
return undefined;
},
/**
* Returns the {@link Ext.layout.container.Container layout} instance currently associated
* with this Container. If a layout has not been instantiated yet, that is done first
* @return {Ext.layout.container.Container} The layout
*/
getLayout: function() {
var me = this,
layout = me.layout;
if (!layout || !layout.isLayout) {
me.setLayout(layout);
}
return me.layout;
},
/**
* @protected
* Used by {@link Ext.ComponentQuery ComponentQuery}, {@link #child} and {@link #down}
* to retrieve all of the items which can potentially be considered a child of this Container.
*
* This may be overriden by Components which have ownership of Components
* that are not contained in the {@link #property-items} collection.
*
* NOTE: IMPORTANT note for maintainers:
* Items are returned in tree traversal order. Each item is appended to the result array
* followed by the results of that child's getRefItems call.
* Floating child items are appended after internal child items.
*/
getRefItems: function(deep) {
var me = this,
items = me.items.items,
len = items.length,
i = 0,
item,
result = [];
for (; i < len; i++) {
item = items[i];
result[result.length] = item;
if (deep && item.getRefItems) {
result.push.apply(result, item.getRefItems(true));
}
}
// Append floating items to the list.
if (me.floatingItems) {
items = me.floatingItems.items;
len = items.length;
for (i = 0; i < len; i++) {
item = items[i];
result[result.length] = item;
if (deep && item.getRefItems) {
result.push.apply(result, item.getRefItems(true));
}
}
}
return result;
},
/**
* Finds the configured default focus item. See {@link #defaultFocus}.
*/
getDefaultFocus: function() {
var defaultFocus = this.defaultFocus,
result;
// This might not work during initConfig
if (defaultFocus && !this.isConfiguring) {
result = this.down(defaultFocus);
}
// Returning undefined is ok
return result;
},
setDefaultFocus: function(value) {
this.defaultFocus = value;
},
initComponent: function() {
var me = this;
me.callParent();
me.getLayout();
// Set a flag to say we're constructing children, can be useful
// to know during construction time to save work
me.constructing = true;
me.initItems();
if (me.disabled) {
me.disabled = false;
me.disable(true);
}
me.reference = me.setupReference(me.reference);
delete me.constructing;
},
/**
* This method is called to initialize the `items` collection. A derived class can
* override this method to do any last minute manipulation of `items` and then call
* this method using `callParent`. Upon return, the `items` will no longer be a simple
* array.
* @protected
*/
initItems: function() {
var me = this,
items = me.items;
if (!items || !items.isMixedCollection) {
// allow the items collection to be pre-initialized.
// (used by Ext.draw.ComponentBase)
/**
* The Collection containing all the child items of this container.
* @property {Ext.util.ItemCollection} items
* @since 2.3.0
*/
me.items = new Ext.util.ItemCollection();
/**
* The MixedCollection containing all the floating child items of this container.
* Will be `undefined` if there are no floating child items.
* @property {Ext.util.MixedCollection} floatingItems
* @since 4.1.0
*/
if (items) {
if (!Ext.isArray(items)) {
items = [items];
}
me.$initingItems = true;
me.add(items);
delete me.$initingItems;
}
}
},
/**
* Called by `getInherited` to initialize the inheritedState the first time it is
* requested.
* @protected
*/
initInheritedState: function(inheritedState, inheritedStateInner) {
var me = this,
layout = me.layout;
me.callParent([inheritedState, inheritedStateInner]);
if (me.collapsed) {
inheritedState.collapsed = true;
}
me.initContainerInheritedState(inheritedState, inheritedStateInner);
if (layout && layout.initInheritedState) {
layout.initInheritedState(inheritedState, inheritedStateInner);
}
},
/**
* Inserts a Component into this Container at a specified index. Fires the
* {@link #beforeadd} event before inserting, then fires the {@link #event-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/Object/Ext.Component[]/Object[]} component The child Component
* or config object to insert.
*
* Ext uses lazy rendering, and will only render the inserted Component should
* it become necessary.
*
* 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.
*
* You can pass an array of Component instances and config objects.
*
* For a list of all available xtypes, see {@link Ext.enums.Widget}.
*
* @return {Ext.Component} component The Component (or config object) that was
* inserted with the Container's default config values applied.
*
* @since 2.3.0
*/
insert: function(index, component) {
var compIdx;
if (component && component.isComponent) {
compIdx = this.items.indexOf(component);
if (compIdx !== -1) {
return this.move(compIdx, index);
}
}
return this.add(index, component);
},
/**
* @protected
* Called when a raw config object is added to this container either during initialization
* of the {@link #cfg-items} config, or when new items are {@link #method-add added},
* or {@link #method-insert inserted}.
*
* This method converts the passed object into an instanced child component.
*
* This may be overridden in subclasses when special processing needs to be applied to child
* creation.
*
* @param {Object} comp The config object being added.
* @return {Ext.Component} The component to be added.
*/
lookupComponent: function(comp) {
var me = this,
defaultType = me.defaultType,
wasAction;
if (!comp.isComponent) {
if (typeof comp === 'string') {
// First char '@' means its an Action name.
// Search this and all ancestors for a matching named Action
// with which to create the Component.
if (!(wasAction = (comp[0] === '@'))) {
// String used as a global component ID. Deprecated practice!
return Ext.ComponentManager.get(comp);
}
comp = me.getAction(comp.substr(1));
defaultType = me.defaultActionType || defaultType;
}
comp = Ext.ComponentManager.create(comp, defaultType);
// We need the inherited state to be invalidated upon add
// because the create will have been performed outside of the
// ownership hierarchy. The Action has no owner view (Actions are shareable).
// This flag indicates to the new item's onAdded->onInheritedAdded machinery
// that the inherited state must be invalidated.
if (wasAction) {
comp.instancedCmp = true;
}
}
return comp;
},
/**
* Moves a Component within the Container. This method does **not** account for things
* like splitter components added by a layout. To better handle these situations, it
* is recommended to use `{@link #moveBefore}` or `{@link #moveAfter}` instead.
*
* @param {Number/Ext.Component} fromIdx The index/component to move.
* @param {Number} toIdx The new index for the Component.
* @return {Ext.Component} component The Component that was moved.
* @deprecated 5.0 Use `{@link #moveBefore}` or `{@link #moveAfter}` instead.
*/
move: function(fromIdx, toIdx) {
var me = this,
items = me.items,
item;
if (fromIdx.isComponent) {
fromIdx = items.indexOf(fromIdx);
}
item = items.getAt(fromIdx);
if (fromIdx !== toIdx) {
item = items.removeAt(fromIdx);
if (item === false) {
return false;
}
toIdx = Math.min(toIdx, items.getCount());
items.insert(toIdx, item);
me.onMove(item, fromIdx, toIdx);
if (me.hasListeners.childmove) {
me.fireEvent('childmove', me, item, fromIdx, toIdx);
}
me.updateLayout();
}
return item;
},
/**
* Moves the given `item(s)` into this container in front of `before`. This method
* will account for layout-generated components like splitters and should be used
* instead of index based `{@link #method-move}`. If `before` is `null` then the
* `item` will be the last item in this container.
*
* var tb = Ext.create({
* xtype: 'toolbar',
* renderTo: Ext.getBody(),
* items: [{
* text: 'one'
* }, {
* text: 'two'
* }]
* });
*
* // moves the 'two' button before the 'one' button
* tb.moveBefore(tb.getComponent(1), tb.getComponent(0));
*
* @param {Ext.Component/Ext.Component[]} item The item to move. May be a component,
* component configuration object, or an array of either.
* @param {Ext.Component} before The reference component. May be `null`.
* @return {Ext.Component/Ext.Component[]} The moved item(s).
* @since 5.0.0
*/
moveBefore: function(item, before) {
var activeEl,
refocusEl;
if (item !== before) {
activeEl = Ext.Element.getActiveElement(true);
// Do not disturb application focus state when moving a focused component.
// Must test whether we contain the activeEl, not the containsFocus flag
// because of asynchronous focus events.
if (item.el && item.el.contains(activeEl)) {
refocusEl = activeEl;
refocusEl.suspendFocusEvents();
item.isLayoutMoving = true;
}
item = this.layout.moveItemBefore(item, before);
if (refocusEl) {
item.isLayoutMoving = false;
refocusEl.focus();
refocusEl.resumeFocusEvents();
}
}
return item;
},
/**
* Moves the given `item(s)` into this container following `after`. This method will
* account for layout-generated components like splitters and should be used instead
* of index based `{@link #method-move}`. If `after` is `null` then the `item` will be the
* first item in this container.
*
* var tb = Ext.create({
* xtype: 'toolbar',
* renderTo: Ext.getBody(),
* items: [{
* text: 'one'
* }, {
* text: 'two'
* }]
* });
*
* // moves the 'one' button after the 'two' button
* tb.moveAfter(tb.getComponent(0), tb.getComponent(1));
*
* @param {Ext.Component/Ext.Component[]} item The item to move. May be a component,
* component configuration object, or an array of either.
* @param {Ext.Component} after The reference component. May be `null`.
* @return {Ext.Component/Ext.Component[]} The moved item(s).
* @since 5.0.0
*/
moveAfter: function(item, after) {
var layout = this.layout,
index;
if (item !== after) {
index = after ? layout.getMoveAfterIndex(after) : 0;
item = this.moveBefore(item, this.items.getAt(index));
}
return item;
},
/**
* A method to find a child component after the passed child parameter. If a selector is also
* provided, the first child component matching the selector will be returned.
*
* @param {Ext.Component} child The child to use as a starting point to find the next child.
* @param {String} [selector] A {@link Ext.ComponentQuery} selector to find the next child.
* This will return the next child matching this selector. This parameter is optional.
* @return {Ext.Component} The next child found, `null` if no child found.
*/
nextChild: function(child, selector) {
var me = this,
items = me.items,
childIndex = items.indexOf(child),
i = 0,
len = items.length,
result;
if (childIndex !== -1) {
if (selector) {
for (; i < len; i++) {
result = items.getAt(childIndex + i);
if (!result || Ext.ComponentQuery.is(result, selector)) {
break;
}
}
}
else {
result = items.getAt(childIndex + 1);
}
}
return result || null;
},
/**
* @method
* This method is invoked after a new Component has been added. It
* is passed the Component which has been added. This method may
* be used to update any internal structure which may depend upon
* the state of the child items.
*
* @param {Ext.Component} component
* @param {Number} position
*
* @template
* @protected
*/
onAdd: Ext.emptyFn,
/**
* This method is invoked before adding a new child Component. It
* is passed the new Component, and may be used to modify the
* Component, or prepare the Container in some way. Returning
* false aborts the add operation.
*
* @param {Ext.Component} item
*
* @template
* @protected
*/
onBeforeAdd: function(item) {
// Remove from current container without detaching it from the DOM if it's not us.
var owner = item.ownerCt;
if (item.isDetached) {
item.reattachToBody();
}
if (owner && owner !== this) {
item.isLayoutMoving = true;
owner.remove(item, {
destroy: false,
detach: false
});
}
},
onMove: Ext.emptyFn,
/**
* @method
* This method is invoked after a new Component has been
* removed. It is passed the Component which has been
* removed. This method may be used to update any internal
* structure which may depend upon the state of the child items.
*
* @param {Ext.Component} component The removed component
* @param {Boolean} isDestroying `true` if the the component is being destroyed in
* the remove action
*
* @template
* @protected
*/
onRemove: Ext.emptyFn,
onPosition: function() {
this.callParent(arguments);
this.repositionFloatingItems();
},
/**
* @method onResize
* @inheritdoc
*/
onResize: function() {
this.callParent(arguments);
this.repositionFloatingItems();
},
/**
* A method to find a child component before the passed child parameter. If a selector is also
* provided, the first child component matching the selector will be returned.
*
* @param {Ext.Component} child The child to use as a starting point to find the previous child.
* @param {String} [selector] A {@link Ext.ComponentQuery} selector to find the previous child.
* This will return the first child matching this selector. This parameter is optional.
* @return {Ext.Component} The previous child found, `null` if no child found.
*/
prevChild: function(child, selector) {
var me = this,
items = me.items,
childIndex = items.indexOf(child),
i = 0,
len = items.length,
result;
if (childIndex !== -1) {
if (selector) {
for (; i < len; i++) {
result = items.getAt(childIndex - i);
if (!result || Ext.ComponentQuery.is(result, selector)) {
break;
}
}
}
else {
result = items.getAt(childIndex - 1);
}
}
return result || null;
},
/**
* Removes a component from this container. Fires the {@link #beforeremove} event
* before removing, then fires the {@link #event-remove} event after the component has
* been removed.
*
* @param {Ext.Component/String} component The component instance or id to remove.
*
* @param {Object} [autoDestroy] Flags to determine what to do with the removed component.
* (May also be specified as a boolean `autoDestroy` flag for backward compatibility).
* @param {Boolean} [autoDestroy.destroy] Defaults to this Container's {@link #autoDestroy}
* config. Specifies whether to destroy the component being removed.
* @param [autoDestroy.detach] Defaults to the {@link #detachOnRemove} configuration
* Specifies whether to remove the component's DOM from the container and into
* the {@link Ext#getDetachedBody detached body element}
*
* @return {Ext.Component} component The Component that was removed.
* @since 2.3.0
*/
remove: function(component, autoDestroy) {
var me = this,
c;
// After destroying, items is nulled so we can't proceed
if (me.destroyed || me.destroying) {
return;
}
c = me.getComponent(component);
//<debug>
if (!arguments.length) {
Ext.log.warn("Ext.container.Container: remove takes an argument of the component " +
"to remove. cmp.remove() is incorrect usage.");
}
//</debug>
if (c && (!me.hasListeners.beforeremove || me.fireEvent('beforeremove', me, c) !== false)) {
me.doRemove(c, autoDestroy);
if (me.hasListeners.remove) {
me.fireEvent('remove', me, c);
}
if (!me.destroying && !me.destroyAfterRemoving && !c.floating) {
me.updateLayout();
}
if (me.destroyAfterRemoving) {
me.destroy();
}
}
return c;
},
/**
* Removes all components from this container.
* @param {Boolean} [autoDestroy] True to automatically invoke the removed
* Component's {@link Ext.Component#method-destroy} function.
* Defaults to the value of this Container's {@link #autoDestroy} config.
* @return {Ext.Component[]} Array of the removed components
* @since 2.3.0
*/
removeAll: function(autoDestroy) {
var me = this,
removeItems,
floaters = me.floatingItems,
items = [],
i = 0,
len,
item;
if (floaters) {
removeItems = me.items.items.concat(floaters.items);
}
else {
removeItems = me.items.items.slice();
}
len = removeItems.length;
// Suspend Layouts while we remove multiple items from the container
Ext.suspendLayouts();
me.removingAll = true;
for (; i < len; i++) {
item = removeItems[i];
me.remove(item, autoDestroy);
if (item.ownerCt !== me) {
items.push(item);
}
}
me.removingAll = false;
// Resume Layouts now that all items have been removed and do a single layout
// (if we removed anything!)
Ext.resumeLayouts(!!len);
return items;
},
/**
* Reconfigures the initially configured {@link #cfg!layout}.
*
* NOTE: this method cannot be used to change the "type" of layout after the component
* has been rendered to the DOM. After rendering, this method can only modify the
* existing layout's configuration properties. The reason for this restriction is that
* many container layouts insert special wrapping elements into the dom, and the
* framework does not currently support dynamically changing these elements once
* rendered.
* @param {Object} configuration object for the layout
*/
setLayout: function(configuration) {
var me = this,
oldLayout = me.layout,
type;
if (configuration) {
if (typeof configuration === 'string') {
configuration = {
type: configuration
};
}
type = configuration.type;
if (oldLayout) {
if (oldLayout.isLayout) {
// Same layout type requested, or just a reconfigure object.
// Either way, we reconfigure the current layout.
if (!type || (type === oldLayout.type)) {
oldLayout.setConfig(configuration);
configuration = oldLayout;
}
// Different layout type requested (not allowed after render)
// just detach from the current layout.
else {
oldLayout.setOwner(null);
}
}
// Old layout has not yet been instantiated; merge new in.
else {
if (typeof oldLayout === 'string') {
oldLayout = {
type: oldLayout
};
}
configuration = Ext.merge({}, oldLayout, configuration);
}
}
if (!(configuration && configuration.isLayout)) {
configuration.owner = this;
configuration = Ext.Factory.layout(configuration);
}
configuration.setOwner(this);
}
me.layout = configuration;
if (me.rendered) {
me.updateLayout();
}
},
/**
* Sets a component as the active layout item. This only applies when using
* a {@link Ext.layout.container.Card} layout.
*
* var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
* var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
* var panel = Ext.create('Ext.panel.Panel', {
* layout: 'card',
* items: [card1, card2]
* });
* // These are all equivalent
* panel.getLayout().setActiveItem(card2);
* panel.getLayout().setActiveItem('card-2');
* panel.getLayout().setActiveItem(1);
*
* @param {Ext.Component/Number/String} item The component, component
* {@link Ext.Component#id id}, {@link Ext.Component#itemId itemId}, or index of component.
* @return {Ext.Component} the activated component or false when nothing activated.
* False is returned also when trying to activate an already active item.
*/
setActiveItem: function(item) {
return this.getLayout().setActiveItem(item);
},
updateActions: function(actions) {
var actionName;
// Convert action configs into Ext.Action instances.
for (actionName in actions) {
if (!actions[actionName].isAction) {
actions[actionName] = new Ext.Action(actions[actionName]);
}
}
},
/**
* Retrieves the named {@link Ext.Action Action} from this view or any ancestor which
* has that named Action. See {@link #actions}
*/
getAction: function(name) {
var owner = this;
for (owner = this; owner; owner = owner.getRefOwner()) {
if (owner.actions && owner.actions[name]) {
return owner.actions[name];
}
}
},
onShowComplete: function(cb, scope) {
var me = this;
me.callParent([cb, scope]);
if (me.focusableContainer && me.activateFocusableContainer) {
me.activateFocusableContainer();
}
},
onFocusEnter: function(e) {
var me = this;
me.callParent([e]);
// We DO NOT check if `me` is focusable here. The reason is that
// non-focusable containers need to track focus entering their
// children so that revertFocus would work if these children
// become unavailable.
if (me.focusableContainer && !me.destroying && !me.destroyed) {
me.mixins.focusablecontainer.onFocusEnter.call(me, e);
}
},
onFocusLeave: function(e) {
var me = this;
me.callParent([e]);
// Ditto
if (me.focusableContainer && !me.destroying && !me.destroyed) {
me.mixins.focusablecontainer.onFocusLeave.call(me, e);
}
},
// ***********************************************************************************
// End Methods
// ***********************************************************************************
// </editor-fold>
privates: {
/**
* @private
*/
applyDefaults: function(config) {
var me = this,
defaults = me.defaults;
if (defaults) {
if (Ext.isFunction(defaults)) {
defaults = defaults.call(me, config);
}
if (Ext.isString(config)) {
config = Ext.ComponentManager.get(config);
}
if (config.isComponent) {
// If we already have a component instance the best we can do is
// conditionally set all the configs in defaults if they have not
// yet been set on the component instance.
config.setConfig(defaults, null, me._applyDefaultsOptions);
}
else {
// If we have a config object (not a component instance) we can do a
// full (deep) merge of the config with the defaults object.
// Fork the defaults object first so that we don't modify the original
config = me.self.getConfigurator().merge(me, Ext.Object.fork(defaults), config);
}
}
return config;
},
// The targetCls is a CSS class that the layout needs added to the targetEl. The targetEl
// is where the container's children are rendered and is usually just the main el.
// Some containers (e.g. panels) use a body instead.
//
// In general, if a class overrides getTargetEl it will also need to override this method.
// This is necessary to avoid a post-render step to add the targetCls.
applyTargetCls: function(targetCls) {
this.layoutTargetCls = targetCls;
},
/**
* @private
*/
doRemove: function(component, flags) {
var me = this,
layout = me.layout,
hasLayout = layout && me.rendered,
floating = component.floating,
doDetach = me.detachOnRemove,
doDestroy = me.autoDestroy,
isDestroying;
// Ensure the flags are set correctly
if (typeof flags === 'boolean') {
doDestroy = flags;
}
else if (typeof flags === 'object') {
if (flags.destroy != null) {
doDestroy = flags.destroy;
}
if (flags.detach != null) {
doDetach = flags.detach;
}
}
// isDestroying flag is true if the removal is taking place as part of destruction,
// OR if removal is intended to *cause* destruction
isDestroying = component.destroying || doDestroy;
if (floating) {
me.floatingItems.remove(component);
}
else {
me.items.remove(component);
}
// Inform ownerLayout of removal before deleting the ownerLayout & ownerCt references
// in the onRemoved call
if (hasLayout && !floating) {
// Removing a component from a running layout has to cancel the layout
if (layout.running) {
Ext.Component.cancelLayout(component, isDestroying);
}
layout.onRemove(component, isDestroying);
}
// Can be already destroyed!
if (!component.destroyed) {
component.onRemoved(isDestroying);
}
if (me.focusableContainer && !me.destroying && !me.destroyed) {
me.onFocusableChildRemove(component, isDestroying);
}
me.onRemove(component, isDestroying);
// Destroy if we were explicitly told to, or we're defaulting to our autoDestroy
// configuration. If the component is already destroyed, calling destroy() again
// won't blow up.
if (doDestroy) {
component.destroy();
}
// Only have the layout perform remove postprocessing if the Component is not
// being destroyed, and this container is yet alive (could be destroyed too)
else if (!me.destroyed) {
if (hasLayout && !floating) {
layout.afterRemove(component);
}
if (doDetach && component.rendered) {
component.detachFromBody();
}
}
},
finishRenderChildren: function() {
var layout;
this.callParent();
layout = this.getLayout();
if (layout) {
layout.finishRender();
}
},
/**
* Gets a list of child components to enable/disable when the container is
* enabled/disabled
* @private
* @return {Ext.Component[]} Items to be enabled/disabled
*/
getChildItemsToDisable: function() {
return this.query('[isLabelable],[isFocusableContainer],button');
},
/**
* @private
*/
getContentTarget: function() {
return this.getLayout().getContentTarget();
},
/**
* @private
*/
getDefaultContentTarget: function() {
return this.el;
},
/**
* @private
*/
prepareItems: function(items, applyDefaults) {
// Create an Array which does not refer to the passed array.
// The passed array is a reference to a user's config object and MUST NOT be mutated.
if (Ext.isArray(items)) {
items = items.slice();
}
else {
items = [items];
}
// Make sure defaults are applied and item is initialized
// eslint-disable-next-line vars-on-top
var me = this,
i = 0,
len = items.length,
item;
for (; i < len; i++) {
item = items[i];
if (item == null) {
Ext.Array.erase(items, i, 1);
--i;
--len;
}
else {
if (applyDefaults) {
item = this.applyDefaults(item);
}
// Tell the item we're in a container during construction
item.$initParent = me;
if (item.isComponent) {
// When this was passed to us, it's an already constructed component
// This is useful to know because we can make decisions regarding the
// state of the component if it's newly created
item.instancedCmp = true;
}
items[i] = me.lookupComponent(item);
// delete here because item may have been a config, so we don't
// want to mutate it
delete item.$initParent;
}
}
return items;
},
repositionFloatingItems: function() {
var floaters = this.floatingItems,
floaterCount,
i, floater;
// Ensure correct positioning of floated children before calling superclass
if (floaters) {
floaters = floaters.items;
floaterCount = floaters.length;
for (i = 0; i < floaterCount; i++) {
floater = floaters[i];
if (floater.el && !floater.hidden) {
floater.setPosition(floater.x, floater.y);
}
}
}
},
initTabGuards: function(activate) {
var me = this,
beforeGuard = me.tabGuardBeforeEl,
afterGuard = me.tabGuardAfterEl,
minTabIndex = me.tabGuardBeforeIndex || 0,
maxTabIndex = me.tabGuardAfterIndex || 0,
i, tabIndex, nodes;
if (!me.rendered || !me.tabGuard) {
return;
}
nodes = me.el.findTabbableElements({
skipSelf: true
});
// Both tab guards may be in the list, disregard them
if (nodes[0] === beforeGuard.dom) {
nodes.shift();
}
if (nodes[nodes.length - 1] === afterGuard.dom) {
nodes.pop();
}
if (nodes && nodes.length) {
// In some cases it might be desirable to configure before and after
// guard elements' tabIndex explicitly but if it is missing we try to
// infer it from the DOM. If we don't and there are elements with
// tabIndex > 0 within the container then tab order will be very
// unintuitive.
if (minTabIndex == null || maxTabIndex == null) {
for (i = 0; i < nodes.length; i++) {
// Can't use node.tabIndex property here, IE8 will report 0
// even if tabIndex attribute is missing.
tabIndex = +nodes[i].getAttribute('tabIndex');
if (tabIndex > 0) {
minTabIndex = Math.min(minTabIndex, tabIndex);
maxTabIndex = Math.max(maxTabIndex, tabIndex);
}
}
}
beforeGuard.dom.setAttribute('tabIndex', minTabIndex);
afterGuard.dom.setAttribute('tabIndex', maxTabIndex);
}
else {
// We don't want the guards to participate in tab flow
// if there are no tabbable children in the container
beforeGuard.dom.removeAttribute('tabIndex');
afterGuard.dom.removeAttribute('tabIndex');
}
if (me.onTabGuardFocusEnter) {
if (!beforeGuard.hasListeners.focusenter) {
beforeGuard.on('focusenter', me.onTabGuardFocusEnter, me);
}
if (!afterGuard.hasListeners.focusenter) {
afterGuard.on('focusenter', me.onTabGuardFocusEnter, me);
}
}
},
_noMargin: {
'margin-top': '',
'margin-right': '',
'margin-bottom': '',
'margin-left': ''
},
// Removes inline margins set by the layout system (see ContextItem#getMarginInfo)
// TODO: fix EXTJS-13359 and remove this method
resetItemMargins: function() {
var items = this.items.items,
i = items.length,
noMargin = this._noMargin,
item;
while (i--) {
item = items[i];
item.margin$ = null;
item.el.setStyle(noMargin);
}
},
setupRenderTpl: function(renderTpl) {
this.callParent(arguments);
this.getLayout().setupRenderTpl(renderTpl);
}
} // private
});