/**
* @private
* Common methods for both classic & modern containers
*/
Ext.define('Ext.mixin.Container', {
extend: 'Ext.Mixin',
mixinConfig: {
id: 'container'
},
/**
* @property {Boolean} isContainer
* `true` in this class to identify an object as an instantiated Container, or subclass thereof.
*/
isContainer: true,
/**
* @cfg {Boolean} nameHolder
* When `true` child components are tracked by their `name` property and can be
* retrieved using the `lookupName` method.
*/
nameHolder: false,
/**
* @cfg {Boolean} referenceHolder
* If `true`, this container will be marked as being a point in the hierarchy where
* references to items with a specified `reference` config will be held. The container
* will automatically become a referenceHolder if a {@link #controller} is specified.
*
* See the introductory docs for {@link Ext.container.Container} for more information
* about references & reference holders.
*/
referenceHolder: false,
/**
* Returns an object holding the descendants of this container keyed by their
* `name`. This object should not be held past the scope of the function calling this
* method. It will not be valid if items are added or removed from this or any
* sub-container.
*
* The intended usage is shown here (assume there are 3 components with names of
* "foo", "bar" and "baz" at some level below this container):
*
* onClick: function () {
* var items = this.getNamedItems();
*
* // using "items" we can access any descendant by its "name"
*
* items.foo.getValue() + items.bar.getValue() + items.baz.getValue();
* }
*
* If `this` component has a `name` assigned to it, it is **not** included in this
* object. That name is understood to belong to the ancestor container configured
* as the `nameHolder`.
*
* @return {Object} An object with each named child. This will be `null` if this
* container has no descendants with a `name` specified.
* @since 6.5.0
*/
getNamedItems: function() {
if (Ext.referencesDirty) {
Ext.fixReferences();
}
return this.nameRefs || null;
},
/**
* Returns an object holding the descendants of this view keyed by their
* `{@link Ext.Component#cfg-reference reference}`. This object should not be held
* past the scope of the function calling this method. It will not be valid if items
* are added or removed from this or any sub-container.
*
* The intended usage is shown here (assume there are 3 components with reference
* values of "foo", "bar" and "baz" at some level below this container):
*
* onClick: function () {
* var refs = this.getReferences();
*
* // using "refs" we can access any descendant by its "reference"
*
* refs.foo.getValue() + refs.bar.getValue() + refs.baz.getValue();
* }
*
* If `this` component has a `{@link Ext.Component#cfg-reference reference}` assigned
* to it, that is **not** included in this object. That reference is understood to
* belong to the ancestor container configured as the `referenceHolder`.
*
* @return {Object} An object with each child reference. This will be `null` if this
* container has no descendants with a `{@link Ext.Component#cfg-reference reference}`
* specified.
* @since 5.0.0
*/
getReferences: function() {
if (Ext.referencesDirty) {
Ext.fixReferences();
}
return this.refs || null;
},
/**
* Gets a reference to the component with the specified
* {@link Ext.Component#cfg-reference reference} value.
*
* The method is a short-hand for the {@link #lookupReference} method.
*
* @param {String} ref The value of the `reference` to lookup.
* @return {Ext.Component} The referenced component or `null` if it is not found.
* @since 6.0.1
*/
lookup: function(ref) {
var refs = this.getReferences();
return (refs && refs[ref]) || null;
},
/**
* Gets a reference to the component with the specified `name` property.
*
* @param {String} name The name of the descendant to lookup.
* @return {Ext.Component} The component or `null` if it is not found.
* @since 6.5.0
*/
lookupName: function(name) {
var items = this.getNamedItems();
return (items && items[name]) || null;
},
/**
* Gets a reference to the component with the specified {@link #reference} value.
*
* The {@link #lookup} method is a short-hand version of this method.
*
* @param {String} ref The name of the reference to lookup.
* @return {Ext.Component} The referenced component or `null` if it is not found.
* @since 5.0
*/
lookupReference: function(ref) {
return this.lookup(ref);
},
privates: {
/**
* Sets up a component name reference.
* @param {Ext.Component} component The component to reference.
* @private
*/
attachNameRef: function(component) {
var me = this,
key = component.name || component._name,
entry, nameRefs;
// Cleaning all this up later anyway
if (key && !me.destroying && !me.destroyed) {
nameRefs = me.nameRefs || (me.nameRefs = {});
entry = nameRefs[key];
if (!entry) {
entry = component.shareableName ? [component] : component;
}
else if (!entry.isInstance) {
// Else an existing entry is either a component (which will have false
// for shareableName) or an array (all elements of which have true
// for their shareableName).
entry.push(component);
}
//<debug>
else {
Ext.raise('Duplicate name: "' + key + '" on ' +
me.id + ' between ' + entry.id + ' and ' + component.id);
}
//</debug>
nameRefs[key] = entry;
}
},
/**
* Sets up a component reference.
* @param {Ext.Component} component The component to reference.
* @private
*/
attachReference: function(component) {
var me = this,
key, refs;
// Cleaning all this up later anyway
if (!me.destroying && !me.destroyed) {
refs = me.refs || (me.refs = {});
key = component.referenceKey;
//<debug>
if (refs[key] && refs[key] !== component) {
Ext.log.warn('Duplicate reference: "' + key + '" on ' + me.id);
}
//</debug>
refs[key] = component;
}
},
containerOnAdded: function(component, instanced) {
// We have been added to a container, we may have child references
// or be a reference ourselves. At this point we have no way of knowing if
// our references are correct, so trigger a fix.
Ext.ComponentManager.markReferencesDirty();
},
containerOnRemoved: function(destroying) {
// If we're destroying this will get cleaned up anyway
if (!destroying) {
// Clear any references here, they will be reset after the
// next call to lookupReference after being marked dirty.
// It's easier to wipe & re-establish them than attempt to
// track what changed and prune the collection
Ext.ComponentManager.markReferencesDirty();
}
},
initContainerInheritedState: function(inheritedState, inheritedStateInner) {
var me = this,
controller = me.getController(),
session = me.getSession(),
// Don't instantiate it here, we just want to know whether we
// were configured with a VM
viewModel = me.getConfig('viewModel', true),
reference = me.reference,
referenceHolder = me.referenceHolder;
if (me.nameHolder) {
inheritedState.nameHolder = me;
}
if (controller) {
inheritedState.referenceHolder = controller;
referenceHolder = true;
}
else if (referenceHolder) {
inheritedState.referenceHolder = me;
}
if (referenceHolder) {
inheritedState.referencePath = '';
}
else if (reference && me.isParentReference) {
inheritedState.referencePath = me.referenceKey + '.';
}
if (session) {
inheritedState.session = session;
}
if (viewModel) {
inheritedState.viewModelPath = '';
}
else if (reference && me.isParentReference) {
inheritedState.viewModelPath = me.viewModelKey + '.';
}
},
setupReference: function(reference) {
var len;
if (reference && reference.charAt(len = reference.length - 1) === '>') {
this.isParentReference = true;
reference = reference.substring(0, len);
}
//<debug>
if (reference && !Ext.validIdRe.test(reference)) {
Ext.Error.raise('Invalid reference "' + reference + '" for ' + this.getId() +
' - not a valid identifier');
}
//</debug>
return reference;
}
}
});