    /**
     * @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 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} 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

    });