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

});