/**
 * @class Ext.form.FieldContainer
 * @extends Ext.container.Container

FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
{@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
This is useful for arranging a group of fields or other components within a single item in a form, so
that it lines up nicely with other fields. A common use is for grouping a set of related fields under
a single label in a form.

The container's configured {@link #items} will be layed out within the field body area according to the
configured {@link #layout} type. The default layout is `'autocontainer'`.

Like regular fields, FieldContainer can inherit its decoration configuration from the
{@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
it may itself contain.

If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
{@img Ext.form.FieldContainer/Ext.form.FieldContainer1.png Ext.form.FieldContainer component}
__Example usage:__

    Ext.create('Ext.form.Panel', {
        title: 'FieldContainer Example',
        width: 550,
        bodyPadding: 10,
    
        items: [{
            xtype: 'fieldcontainer',
            fieldLabel: 'Last Three Jobs',
            labelWidth: 100,
    
            // The body area will contain three text fields, arranged
            // horizontally, separated by draggable splitters.
            layout: 'hbox',
            items: [{
                xtype: 'textfield',
                flex: 1
            }, {
                xtype: 'splitter'
            }, {
                xtype: 'textfield',
                flex: 1
            }, {
                xtype: 'splitter'
            }, {
                xtype: 'textfield',
                flex: 1
            }]
        }],
        renderTo: Ext.getBody()
    });

__Usage of {@link #fieldDefaults}:__
{@img Ext.form.FieldContainer/Ext.form.FieldContainer2.png Ext.form.FieldContainer component}

    Ext.create('Ext.form.Panel', {
        title: 'FieldContainer Example',
        width: 350,
        bodyPadding: 10,
    
        items: [{
            xtype: 'fieldcontainer',
            fieldLabel: 'Your Name',
            labelWidth: 75,
            defaultType: 'textfield',
    
            // Arrange fields vertically, stretched to full width
            layout: 'anchor',
            defaults: {
                layout: '100%'
            },
    
            // These config values will be applied to both sub-fields, except
            // for Last Name which will use its own msgTarget.
            fieldDefaults: {
                msgTarget: 'under',
                labelAlign: 'top'
            },
    
            items: [{
                fieldLabel: 'First Name',
                name: 'firstName'
            }, {
                fieldLabel: 'Last Name',
                name: 'lastName',
                msgTarget: 'under'
            }]
        }],
        renderTo: Ext.getBody()
    });


 * @constructor
 * Creates a new Ext.form.FieldContainer instance.
 * @param {Object} config The component configuration.
 *
 * @xtype fieldcontainer
 * @markdown
 * @docauthor Jason Johnston <jason@sencha.com>
 */
Ext.define('Ext.form.FieldContainer', {
    extend: 'Ext.container.Container',
    mixins: {
        labelable: 'Ext.form.Labelable',
        fieldAncestor: 'Ext.form.FieldAncestor'
    },
    alias: 'widget.fieldcontainer',

    componentLayout: 'field',

    /**
     * @cfg {Boolean} combineLabels
     * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
     * generate its label by combining the labels of all the fields it contains. Defaults to false.
     */
    combineLabels: false,

    /**
     * @cfg {String} labelConnector
     * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
     * set to true. Defaults to ', '.
     */
    labelConnector: ', ',

    /**
     * @cfg {Boolean} combineErrors
     * If set to true, the field container will automatically combine and display the validation errors from
     * all the fields it contains as a single error on the container, according to the configured
     * {@link #msgTarget}. Defaults to false.
     */
    combineErrors: false,
    
    maskOnDisable: false,

    initComponent: function() {
        var me = this,
            onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;

        // Init mixins
        me.initLabelable();
        me.initFieldAncestor();

        me.callParent();
    },

    /**
     * @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;
        me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
        me.updateLabel();
    },

    /**
     * @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.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
        me.updateLabel();
    },

    onRender: function() {
        var me = this,
            renderSelectors = me.renderSelectors,
            applyIf = Ext.applyIf;

        applyIf(renderSelectors, me.getLabelableSelectors());

        me.callParent(arguments);
    },

    initRenderTpl: function() {
        var me = this;
        if (!me.hasOwnProperty('renderTpl')) {
            me.renderTpl = me.getTpl('labelableRenderTpl');
        }
        return me.callParent();
    },

    initRenderData: function() {
        return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
    },

    /**
     * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
     * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
     * this method to provide a custom generated label.
     */
    getFieldLabel: function() {
        var label = this.fieldLabel || '';
        if (!label && this.combineLabels) {
            label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
                return field.getFieldLabel();
            }).join(this.labelConnector);
        }
        return label;
    },

    /**
     * @private Updates the content of the labelEl if it is rendered
     */
    updateLabel: function() {
        var me = this,
            label = me.labelEl;
        if (label) {
            label.update(me.getFieldLabel());
        }
    },


    /**
     * @private Fired when the error message of any field within the container changes, and updates the
     * combined error message to match.
     */
    onFieldErrorChange: function(field, activeError) {
        if (this.combineErrors) {
            var me = this,
                oldError = me.getActiveError(),
                invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
                    return field.hasActiveError();
                }),
                newErrors = me.getCombinedErrors(invalidFields);

            if (newErrors) {
                me.setActiveErrors(newErrors);
            } else {
                me.unsetActiveError();
            }

            if (oldError !== me.getActiveError()) {
                me.doComponentLayout();
            }
        }
    },

    /**
     * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
     * messages from them. Defaults to prepending each message by the field name and a colon. This
     * can be overridden to provide custom combined error message handling, for instance changing
     * the format of each message or sorting the array (it is sorted in order of appearance by default).
     * @param {Array} invalidFields An Array of the sub-fields which are currently invalid.
     * @return {Array} The combined list of error messages
     */
    getCombinedErrors: function(invalidFields) {
        var forEach = Ext.Array.forEach,
            errors = [];
        forEach(invalidFields, function(field) {
            forEach(field.getActiveErrors(), function(error) {
                var label = field.getFieldLabel();
                errors.push((label ? label + ': ' : '') + error);
            });
        });
        return errors;
    },

    getTargetEl: function() {
        return this.bodyEl || this.callParent();
    }
});