/** * @class Ext.form.FieldAncestor 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. * @markdown * @docauthor Jason Johnston <jason@sencha.com> */ Ext.define('Ext.form.FieldAncestor', { /** * @cfg {Object} fieldDefaults * <p>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 <tt>fieldDefaults</tt>.</p> * <p>Example:</p> * <pre><code>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' }] }] });</code></pre> * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>) * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p> */ /** * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method * of any components importing this mixin. */ initFieldAncestor: function() { var me = this, onSubtreeChange = me.onFieldAncestorSubtreeChange; me.addEvents( /** * @event fielderrorchange * 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} The Field instance whose validity changed * @param {String} isValid The field's new validity state */ 'fieldvaliditychange', /** * @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} The Labelable instance whose active error was changed * @param {String} error The active error message */ 'fielderrorchange' ); // Catch addition and removal of descendant fields me.on('add', onSubtreeChange, me); me.on('remove', onSubtreeChange, me); me.initFieldDefaults(); }, /** * @private Initialize the {@link #fieldDefaults} object */ initFieldDefaults: function() { if (!this.fieldDefaults) { this.fieldDefaults = {}; } }, /** * @private * Handle the addition and removal of components in the FieldAncestor component's child tree. */ onFieldAncestorSubtreeChange: function(parent, child) { var me = this, isAdding = !!child.ownerCt; function handleCmp(cmp) { var isLabelable = cmp.isFieldLabelable, isField = cmp.isFormField; if (isLabelable || isField) { if (isLabelable) { me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp); } if (isField) { me['onField' + (isAdding ? 'Added' : 'Removed')](cmp); } } else if (cmp.isContainer) { Ext.Array.forEach(cmp.getRefItems(), handleCmp); } } handleCmp(child); }, /** * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree. * @param {Ext.form.Labelable} labelable The instance that was added */ onLabelableAdded: function(labelable) { var me = this; // buffer slightly to avoid excessive firing while sub-fields are changing en masse me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10}); labelable.setFieldDefaults(me.fieldDefaults); }, /** * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree. * @param {Ext.form.field.Field} field The field which was added */ onFieldAdded: function(field) { var me = this; me.mon(field, 'validitychange', me.handleFieldValidityChange, me); }, /** * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree. * @param {Ext.form.Labelable} labelable The instance that was removed */ onLabelableRemoved: function(labelable) { var me = this; me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me); }, /** * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree. * @param {Ext.form.field.Field} field The field which was removed */ onFieldRemoved: function(field) { var me = this; me.mun(field, 'validitychange', me.handleFieldValidityChange, me); }, /** * @private Handle validitychange events on sub-fields; invoke the aggregated event and method */ handleFieldValidityChange: function(field, isValid) { var me = this; me.fireEvent('fieldvaliditychange', me, field, isValid); me.onFieldValidityChange(); }, /** * @private Handle errorchange events on sub-fields; invoke the aggregated event and method */ handleFieldErrorChange: function(labelable, activeError) { var me = this; me.fireEvent('fielderrorchange', me, labelable, activeError); me.onFieldErrorChange(); }, /** * @protected Fired when the validity of any field within the container changes. * @param {Ext.form.field.Field} The sub-field whose validity changed * @param {String} The new validity state */ onFieldValidityChange: Ext.emptyFn, /** * @protected Fired when the error message of any field within the container changes. * @param {Ext.form.Labelable} The sub-field whose active error changed * @param {String} The new active error message */ onFieldErrorChange: Ext.emptyFn });