/**
* A mixin for {@link Ext.container.Container} components that are likely to have form fields
* in their items subtree. Adds the following capabilities:
*
* - Methods for handling the addition and removal of {@link Ext.form.Labelable} and
* {@link Ext.form.field.Field} instances at any depth within the container.
* - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes
* to the state of individual fields at the container level.
* - Automatic application of {@link #fieldDefaults} config properties to each field added
* within the container, to facilitate uniform configuration of all fields.
*
* This mixin is primarily for internal use by {@link Ext.form.Panel} and
* {@link Ext.form.FieldContainer}, and should not normally need to be used directly.
*/
Ext.define('Ext.form.FieldAncestor', {
extend: 'Ext.Mixin',
requires: [
'Ext.container.Monitor'
],
mixinConfig: {
id: 'fieldAncestor',
after: {
initInheritedState: 'initFieldInheritedState'
},
before: {
doDestroy: 'onBeforeDestroy'
}
},
/**
* @cfg {Object} fieldDefaults
* If specified, the properties in this object are used as default config values for each
* {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or
* {@link Ext.form.FieldContainer}) that is added as a descendant of this container.
* Corresponding values specified in an individual field's own configuration, or from the
* {@link Ext.container.Container#defaults defaults config} of its parent container,
* will take precedence. See the documentation for {@link Ext.form.Labelable} to see
* what config options may be specified in the fieldDefaults.
*
* Example:
*
* new Ext.form.Panel({
* fieldDefaults: {
* labelAlign: 'left',
* labelWidth: 100
* },
* items: [{
* xtype: 'fieldset',
* defaults: {
* labelAlign: 'top'
* },
* items: [{
* name: 'field1'
* }, {
* name: 'field2'
* }]
* }, {
* xtype: 'fieldset',
* items: [{
* name: 'field3',
* labelWidth: 150
* }, {
* name: 'field4'
* }]
* }]
* });
*
* In this example, field1 and field2 will get labelAlign: 'top' (from the fieldset's defaults)
* and labelWidth: 100 (from fieldDefaults), field3 and field4 will both get labelAlign: 'left'
* (from fieldDefaults and field3 will use the labelWidth: 150 from its own config.
*/
/**
* @event fieldvaliditychange
* Fires when the validity state of any one of the {@link Ext.form.field.Field} instances
* within this container changes.
* @param {Ext.form.FieldAncestor} this
* @param {Ext.form.Labelable} field The Field instance whose validity changed
* @param {String} isValid The field's new validity state
*/
/**
* @event fielderrorchange
* Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
* instances within this container.
* @param {Ext.form.FieldAncestor} this
* @param {Ext.form.Labelable} field The Labelable instance whose active error was changed
* @param {String} error The active error message
*/
/**
* Initializes the FieldAncestor's state; this must be called from the initComponent method
* of any components importing this mixin.
* @protected
*/
initFieldAncestor: function() {
var me = this;
// We use the monitor here as opposed to event bubbling. The problem with bubbling
// is it doesn't let us react to items being added/remove at different places
// in the hierarchy which may have an impact on the error/valid state.
me.monitor = new Ext.container.Monitor({
scope: me,
selector: '[isFormField]:not([excludeForm])',
addHandler: me.onChildFieldAdd,
removeHandler: me.onChildFieldRemove
});
me.initFieldDefaults();
},
initMonitor: function() {
this.monitor.bind(this);
},
initFieldInheritedState: function(inheritedState) {
var inheritedFieldDefaults = inheritedState.fieldDefaults,
fieldDefaults = this.fieldDefaults;
if (fieldDefaults) {
if (inheritedFieldDefaults) {
inheritedState.fieldDefaults =
Ext.apply(Ext.Object.chain(inheritedFieldDefaults), fieldDefaults);
}
else {
inheritedState.fieldDefaults = fieldDefaults;
}
}
},
onChildFieldAdd: function(field) {
var me = this;
me.mon(field, 'errorchange', me.handleFieldErrorChange, me);
me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
},
onChildFieldRemove: function(field) {
var me = this;
me.mun(field, 'errorchange', me.handleFieldErrorChange, me);
me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
},
/**
* @private
* Initialize the {@link #fieldDefaults} object
*/
initFieldDefaults: function() {
if (!this.fieldDefaults) {
this.fieldDefaults = {};
}
},
/**
* @private
* Handle bubbled validitychange events from descendants; invoke the aggregated event and method
*/
handleFieldValidityChange: function(field, isValid) {
var me = this;
if (field !== me) {
me.fireEvent('fieldvaliditychange', me, field, isValid);
me.onFieldValidityChange(field, isValid);
}
},
/**
* @private
* Handle bubbled errorchange events from descendants; invoke the aggregated event and method
*/
handleFieldErrorChange: function(labelable, activeError) {
var me = this;
if (labelable !== me) {
me.fireEvent('fielderrorchange', me, labelable, activeError);
me.onFieldErrorChange(labelable, activeError);
}
},
/**
* @method
* Fired when the validity of any field within the container changes.
* @param {Ext.form.field.Field} field The sub-field whose validity changed
* @param {Boolean} valid The new validity state
* @protected
*/
onFieldValidityChange: Ext.emptyFn,
/**
* @method
* Fired when the error message of any field within the container changes.
* @param {Ext.form.Labelable} field The sub-field whose active error changed
* @param {String} error The new active error message
* @protected
*/
onFieldErrorChange: Ext.emptyFn,
onBeforeDestroy: function() {
this.monitor = Ext.destroy(this.monitor);
}
});