/**
 * A mixin which allows a component to be configured and decorated with a label and/or error message
 * as is common for form fields. This is used by e.g. Ext.form.field.Base and
 * Ext.form.FieldContainer to let them be managed by the Field layout.
 *
 * NOTE: This mixin is mainly for internal library use and most users should not need to use it
 * directly. It is more likely you will want to use one of the component classes that import
 * this mixin, such as Ext.form.field.Base or Ext.form.FieldContainer.
 *
 * Use of this mixin does not make a component a field in the logical sense, meaning it does not
 * provide any logic or state related to values or validation; that is handled by the related
 * Ext.form.field.Field mixin. These two mixins may be used separately
 * (for example Ext.form.FieldContainer is Labelable but not a Field), or in combination
 * (for example Ext.form.field.Base implements both and has logic for connecting the two.)
 *
 * Component classes which use this mixin should use the Field layout
 * or a derivation thereof to properly size and position the label and message according to the
 * component config. They must also call the {@link #initLabelable} method during component
 * initialization to ensure the mixin gets set up correctly.
 */
Ext.define("Ext.form.Labelable", {
    extend: 'Ext.Mixin',
 
    requires: [
        'Ext.XTemplate',
        'Ext.overrides.dom.Element'
    ],
 
    isLabelable: true,
 
    mixinConfig: {
        id: 'labelable',
 
        on: {
            beforeRender: 'beforeLabelRender',
            onRender: 'onLabelRender'
        }
    },
 
    config: {
        childEls: [
            /**
             * @property {Ext.dom.Element} labelEl
             * The label Element for this component. Only available after the component
             * has been rendered.
             */
            'labelEl',
 
            /**
             * @property {Ext.dom.Element} bodyEl
             * The div Element wrapping the component's contents. Only available after
             * the component has been rendered.
             */
            'bodyEl',
 
            /**
             * @property {Ext.dom.Element} errorEl
             * The div Element that will contain the component's error message(s). Note that
             * depending on the configured {@link #msgTarget}, this element may be hidden
             * in favor of some other form of presentation, but will always be present in the DOM
             * for use by assistive technologies.
             */
            'errorEl',
 
            'errorWrapEl',
            'ariaErrorEl',
            'ariaStatusEl',
            'ariaHelpEl',
            'labelTextEl'
        ]
    },
 
    /* eslint-disable indent, max-len */
    /**
     * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl
     * The rendering template for the field decorations. Component classes using this mixin
     * should include logic to use this as their {@link Ext.Component#renderTpl renderTpl},
     * and implement the {@link #getSubTplMarkup} method to generate the field body content.
     * @private
     */
    labelableRenderTpl: [
        '{beforeLabelTpl}',
        '<label id="{id}-labelEl" data-ref="labelEl" class="{labelCls} {labelCls}-{ui} {labelClsExtra} ',
                '{childElCls} {unselectableCls}" style="{labelStyle}"',
                '<tpl if="inputId && !skipLabelForAttribute"> for="{inputId}"</tpl>',
                ' {labelAttrTpl}>',
            '<span class="{labelInnerCls} {labelInnerCls}-{ui}" style="{labelInnerStyle}">',
            '{beforeLabelTextTpl}',
            '<span id="{id}-labelTextEl" data-ref="labelTextEl" class="{labelTextCls}">',
                '<tpl if="fieldLabel">{fieldLabel}',
                    '<tpl if="labelSeparator">{labelSeparator}</tpl>',
                '</tpl>',
            '</span>',
            '{afterLabelTextTpl}',
            '</span>',
        '</label>',
        '{afterLabelTpl}',
        '<div id="{id}-bodyEl" data-ref="bodyEl" role="presentation"',
            ' class="{baseBodyCls} {baseBodyCls}-{ui}<tpl if="fieldBodyCls">',
            ' {fieldBodyCls} {fieldBodyCls}-{ui}</tpl> {growCls} {extraFieldBodyCls}"',
            '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
            '{beforeBodyEl}',
            '{beforeSubTpl}',
            '{[values.$comp.getSubTplMarkup(values)]}',
            '{afterSubTpl}',
            '{afterBodyEl}',
            // ARIA elements serve different purposes:
            // - ariaHelpEl may contain optional hints about the field, such as
            //   expected format. This text is static and usually does not change
            //   once rendered. It is also optional.
            // - ariaStatusEl is used to convey status of the field. Validation errors
            //   are rendered here, as well as other information that might be helpful
            //   to Assistive Technology users exploring the app in browse mode.
            // - ariaErrorEl is used for announcing dynamic changes in the field state,
            //   so that AT users receive updates while in forms mode.
            //
            // Both ariaHelpEl and ariaStatusEl are referenced by the field's input element
            // via aria-describedby.
            '<tpl if="renderAriaElements">',
                '<tpl if="ariaHelp">',
                    '<span id="{id}-ariaHelpEl" data-ref="ariaHelpEl"',
                        ' class="' + Ext.baseCSSPrefix + 'hidden-offsets">',
                        '{ariaHelp}',
                    '</span>',
                '</tpl>',
                '<span id="{id}-ariaStatusEl" data-ref="ariaStatusEl" aria-hidden="true"',
                    ' class="' + Ext.baseCSSPrefix + 'hidden-offsets">',
                    '{ariaStatus}',
                '</span>',
                '<span id="{id}-ariaErrorEl" data-ref="ariaErrorEl" aria-hidden="true" aria-live="assertive"',
                    ' class="' + Ext.baseCSSPrefix + 'hidden-clip">',
                '</span>',
            '</tpl>',
        '</div>',
        '<tpl if="renderError">',
            '<div id="{id}-errorWrapEl" data-ref="errorWrapEl" class="{errorWrapCls} {errorWrapCls}-{ui}',
                ' {errorWrapExtraCls}" style="{errorWrapStyle}">',
                '<div role="presentation" id="{id}-errorEl" data-ref="errorEl" ',
                    'class="{errorMsgCls} {invalidMsgCls} {invalidMsgCls}-{ui}" ',
                    'data-anchorTarget="{tipAnchorTarget}">',
                '</div>',
            '</div>',
        '</tpl>',
        {
            disableFormats: true
        }
    ],
 
    /**
     * @cfg {String/String[]/Ext.XTemplate} activeErrorsTpl
     * The template used to format the Array of error messages passed to {@link #setActiveErrors}
     * into a single HTML string. if the {@link #msgTarget} is title, it defaults to a list
     * separated by new lines. Otherwise, it renders each message as an item in an unordered list.
     */
    activeErrorsTpl: undefined,
 
    htmlActiveErrorsTpl: [
        '<tpl if="errors && errors.length">',
            '<ul class="{listCls}">',
                '<tpl for="errors"><li>{.}</li></tpl>',
            '</ul>',
        '</tpl>'
    ],
 
    plaintextActiveErrorsTpl: [
        '<tpl if="errors && errors.length">',
            '<tpl for="errors"><tpl if="xindex &gt; 1">\n</tpl>{.}</tpl>',
        '</tpl>'
    ],
    
    ariaActiveErrorsTpl: [
        '<tpl if="errors && errors.length">',
            '<tpl for="errors" between=". ">{.}</tpl>',
        '</tpl>'
    ],
    /* eslint-enable indent, max-len */
 
    /**
     * @property {Boolean} isFieldLabelable
     * Flag denoting that this object is labelable as a field. Always true.
     */
    isFieldLabelable: true,
 
    /**
     * @cfg {String} formItemCls
     * A CSS class to be applied to the outermost element to denote that it is participating
     * in the form field layout.
     */
    formItemCls: Ext.baseCSSPrefix + 'form-item',
 
    /**
     * @cfg {String} labelCls
     * The CSS class to be applied to the label element. This (single) CSS class is used
     * to formulate the renderSelector and drives the field layout where it is concatenated
     * with a hyphen ('-') and {@link #labelAlign}. To add additional classes, use
     * {@link #labelClsExtra}.
     */
    labelCls: Ext.baseCSSPrefix + 'form-item-label',
 
    /**
     * @private
     */
    topLabelCls: Ext.baseCSSPrefix + 'form-item-label-top',
    rightLabelCls: Ext.baseCSSPrefix + 'form-item-label-right',
    labelInnerCls: Ext.baseCSSPrefix + 'form-item-label-inner',
    labelTextCls: Ext.baseCSSPrefix + 'form-item-label-text',
    topLabelSideErrorCls: Ext.baseCSSPrefix + 'form-item-label-top-side-error',
 
    /**
     * @cfg {String} labelClsExtra
     * An optional string of one or more additional CSS classes to add to the label element.
     * Defaults to empty.
     */
 
    /**
     * @cfg {String} errorMsgCls
     * The CSS class to be applied to the error message element.
     */
    errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
 
    errorWrapCls: Ext.baseCSSPrefix + 'form-error-wrap',
    errorWrapSideCls: Ext.baseCSSPrefix + 'form-error-wrap-side',
    errorWrapUnderCls: Ext.baseCSSPrefix + 'form-error-wrap-under',
    errorWrapUnderSideLabelCls: Ext.baseCSSPrefix + 'form-error-wrap-under-side-label',
 
    /**
     * @cfg {String} baseBodyCls
     * The CSS class to be applied to the body content element.
     */
    baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
 
    invalidIconCls: Ext.baseCSSPrefix + 'form-invalid-icon',
 
    invalidUnderCls: Ext.baseCSSPrefix + 'form-invalid-under',
 
    noLabelCls: Ext.baseCSSPrefix + 'form-item-no-label',
 
    /**
     * @cfg {String} fieldBodyCls
     * An extra CSS class to be applied to the body content element in addition to
     * {@link #baseBodyCls}.
     */
    fieldBodyCls: '',
 
    extraFieldBodyCls: '',
 
    /**
     * @cfg {String} invalidCls
     * The CSS class to use when marking the component invalid.
     */
    invalidCls: Ext.baseCSSPrefix + 'form-invalid',
 
    /**
     * @cfg {String} fieldLabel
     * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
     * and sizing is determined by the {@link #labelAlign} and {@link #labelWidth} configs.
     */
    fieldLabel: undefined,
 
    /**
     * @cfg {String} labelAlign
     * Controls the position and alignment of the {@link #fieldLabel}. Valid values are:
     *
     *   - "left" (the default) - The label is positioned to the left of the field, with its text
     *     aligned to the left. Its width is determined by the {@link #labelWidth} config.
     *   - "top" - The label is positioned above the field.
     *   - "right" - The label is positioned to the left of the field, with its text aligned
     *     to the right. Its width is determined by the {@link #labelWidth} config.
     */
    labelAlign: 'left',
 
    /**
     * @cfg {Number} labelWidth
     * The width of the {@link #fieldLabel} in pixels. Only applicable if {@link #labelAlign}
     * is set to "left" or "right".
     */
    labelWidth: 100,
 
    /**
     * @cfg {Number} labelPad
     * The amount of space in pixels between the {@link #fieldLabel} and the field body.
     * This defaults to `5` for compatibility with Ext JS 4, however, as of Ext JS 5
     * the space between the label and the body can optionally be determined by the theme
     * using the {@link #$form-label-horizontal-spacing} (for side-aligned labels) and
     * {@link #$form-label-vertical-spacing} (for top-aligned labels) SASS variables.
     * In order for the stylesheet values as to take effect, you must use a labelPad value
     * of `null`.
     */
    labelPad: 5,
 
    /**
     * @cfg {String} labelSeparator
     * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
     *
     * Set to empty string to hide the separator completely.
     * @locale
     */
    labelSeparator: ':',
 
    /**
     * @cfg {String} labelStyle
     * A CSS style specification string to apply directly to this field's label.
     */
    
    /**
     * @cfg {Boolean} hideLabel
     * Set to true to completely hide the label element ({@link #fieldLabel} and
     * {@link #labelSeparator}). Also see {@link #hideEmptyLabel}, which controls whether space
     * will be reserved for an empty fieldLabel.
     */
    hideLabel: false,
 
    /**
     * @cfg {Boolean} hideEmptyLabel
     * When set to true, the label element ({@link #fieldLabel} and {@link #labelSeparator})
     * will be automatically hidden if the {@link #fieldLabel} is empty. Setting this to false
     * will cause the empty label element to be rendered and space to be reserved for it;
     * this is useful if you want a field without a label to line up with other labeled fields
     * in the same form.
     *
     * If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured,
     * then set the {@link #hideLabel} config to true.
     */
    hideEmptyLabel: true,
 
    /**
     * @cfg {Boolean} preventMark
     * true to disable displaying any {@link #setActiveError error message} set on this object.
     */
    preventMark: false,
 
    /**
     * @cfg {Boolean} autoFitErrors
     * Whether to adjust the component's body width to make room for 'side'
     * {@link #msgTarget error messages}.
     */
    autoFitErrors: true,
 
    /**
     * @cfg {String} msgTarget
     * The location where the error message text should display. Must be one of the following
     * values:
     *
     *   - `qtip` Display a quick tip containing the message when the user hovers over the field.
     *     This is the default.
     *
     *     **{@link Ext.tip.QuickTipManager#init} must have been called for this setting to work.**
     *
     *   - `title` Display the message in a default browser title attribute popup.
     *   - `under` Add a block div beneath the field containing the error message.
     *   - `side` Add an error icon to the right of the field, displaying the message in a popup
     *     on hover.
     *   - `none` Don't display any error message. This might be useful if you are implementing
     *     custom error display.
     *   - `[element id]` Add the error message directly to the innerHTML of the specified element.
     */
    msgTarget: 'qtip',
 
    /**
     * @private
     * Map for msg target lookup, if target is not in this map it is assumed
     * to be an element id
     */
    msgTargets: {
        qtip: 1,
        title: 1,
        under: 1,
        side: 1,
        none: 1
    },
 
    /**
     * @cfg {String} activeError
     * If specified, then the component will be displayed with this value as its active error
     * when first rendered. Use {@link #setActiveError} or {@link #unsetActiveError} to change it
     * after component creation.
     */
 
    /**
     * @private
     * Tells the layout system that the height can be measured immediately because the width
     * does not need setting.
     */
    noWrap: true,
    
    /**
     * @cfg {String} [ariaHelp] Optional text description for this object. This text will be
     * announced to Assistive Technology users when the object is focused.
     * @locale
     */
    ariaHelp: undefined,
    
    /**
     * @cfg {String} ariaErrorText Localized announcement text for validation errors. This text
     * will be used by Assistive Technologies such as screen readers to alert the users when
     * field validation fails.
     *
     * This config is used with {@link Ext.String#format}. '{0}' will be replaced with the actual
     * error message(s), '{1}' will be replaced with field label.
     * @locale
     */
    ariaErrorText: 'Input error. {0}.',
 
    labelableInsertions: [
        /**
         * @cfg {String/Array/Ext.XTemplate} beforeBodyEl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * at the beginning of the input containing element. If an `XTemplate` is used,
         * the component's {@link Ext.Component#renderData render data} serves as the context.
         */
        'beforeBodyEl',
 
        /**
         * @cfg {String/Array/Ext.XTemplate} afterBodyEl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * at the end of the input containing element. If an `XTemplate` is used, the component's
         * {@link Ext.Component#renderData render data} serves as the context.
         */
        'afterBodyEl',
 
        /**
         * @cfg {String/Array/Ext.XTemplate} beforeLabelTpl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * before the label element. If an `XTemplate` is used, the component's
         * {@link Ext.Component#renderData render data} serves as the context.
         */
        'beforeLabelTpl',
 
        /**
         * @cfg {String/Array/Ext.XTemplate} afterLabelTpl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * after the label element. If an `XTemplate` is used, the component's
         * {@link Ext.Component#renderData render data} serves as the context.
         */
        'afterLabelTpl',
 
        /**
         * @cfg {String/Array/Ext.XTemplate} beforeSubTpl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * before the {@link #getSubTplMarkup subTpl markup}. If an `XTemplate` is used, the
         * component's {@link Ext.Component#renderData render data} serves as the context.
         */
        'beforeSubTpl',
 
        /**
         * @cfg {String/Array/Ext.XTemplate} afterSubTpl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * after the {@link #getSubTplMarkup subTpl markup}. If an `XTemplate` is used, the
         * component's {@link Ext.Component#renderData render data} serves as the context.
         */
        'afterSubTpl',
 
        /**
         * @cfg {String/Array/Ext.XTemplate} beforeLabelTextTpl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * before the label text. If an `XTemplate` is used, the component's
         * {@link Ext.Component#renderData render data} serves as the context.
         */
        'beforeLabelTextTpl',
 
        /**
         * @cfg {String/Array/Ext.XTemplate} afterLabelTextTpl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * after the label text. If an `XTemplate` is used, the component's
         * {@link Ext.Component#renderData render data} serves as the context.
         */
        'afterLabelTextTpl',
 
        /**
         * @cfg {String/Array/Ext.XTemplate} labelAttrTpl
         * An optional string or `XTemplate` configuration to insert in the field markup
         * inside the label element (as attributes). If an `XTemplate` is used, the component's
         * {@link Ext.Component#renderData render data} serves as the context.
         */
        'labelAttrTpl'
    ],
 
    statics: {
        /**
         * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
         * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
         * @static
         * @private
         */
        initTip: function() {
            var tip = this.tip,
                cfg, copy;
 
            if (tip) {
                return;
            }
 
            cfg = {
                id: 'ext-form-error-tip',
                //<debug>
                // tell the spec runner to ignore this element when checking if the dom is clean
                sticky: true,
                //</debug>
                ui: 'form-invalid'
            };
 
            // On Touch devices, tapping the target shows the qtip
            if (Ext.supports.Touch) {
                cfg.dismissDelay = 0;
                cfg.anchor = 'top';
                cfg.showDelay = 0;
                cfg.showOnTap = true;
                cfg.listeners = {
                    beforeshow: function() {
                        this.minWidth = Ext.fly(this.activeTarget.el).getWidth();
                    }
                };
            }
            
            tip = this.tip = Ext.create('Ext.tip.QuickTip', cfg);
            copy = Ext.apply({}, tip.tagConfig);
            copy.attribute = 'errorqtip';
            
            tip.setTagConfig(copy);
        },
 
        /**
         * Destroy the error tip instance.
         * @static
         */
        destroyTip: function() {
            this.tip = Ext.destroy(this.tip);
        }
    },
 
    /**
     * @event errorchange
     * Fires when the active error message is changed via {@link #setActiveError}.
     * @param {Ext.form.Labelable} this 
     * @param {String} error The active error message
     */
 
    /**
     * Performs initialization of this mixin. Component classes using this mixin should call
     * this method during their own initialization.
     */
    initLabelable: function() {
        var me = this,
            padding = me.padding;
 
        // This Component is rendered as a table. Padding doesn't work on tables
        // Before padding can be applied to the encapsulating table element, copy the padding into
        // an extraMargins property which is to be added to all computed margins post render :(
        if (padding) {
            me.padding = undefined;
            me.extraMargins = Ext.Element.parseBox(padding);
        }
 
        // IE8 hack for https://sencha.jira.com/browse/EXTJS-17536.
        // Need to force a relayout of the display:table form item.
        // TODO: Remove when IE8 retires.
        if (Ext.isIE8) {
            me.restoreDisplay = Ext.Function.createDelayed(me.doRestoreDisplay, 0, me);
        }
 
        if (!me.activeErrorsTpl) {
            if (me.msgTarget === 'title') {
                me.activeErrorsTpl = me.plaintextActiveErrorsTpl;
            }
            else {
                me.activeErrorsTpl = me.htmlActiveErrorsTpl;
            }
        }
 
        me.addCls([me.formItemCls, me.formItemCls + '-' + me.ui]);
 
        // Prevent first render of active error, at Field render time from signalling a change
        // from undefined to "
        me.lastActiveError = '';
 
        // bubbleEvents on the prototype of a mixin won't work, so call enableBubble
        me.enableBubble('errorchange');
    },
 
    /**
     * Returns the trimmed label by slicing off the label separator character. Can be overridden.
     * @return {String} The trimmed field label, or empty string if not defined
     */
    trimLabelSeparator: function() {
        var me = this,
            separator = me.labelSeparator,
            label = me.fieldLabel || '',
            lastChar = label.substr(label.length - 1);
 
        // if the last char is the same as the label separator then slice it off
        // otherwise just return label value
        return lastChar === separator ? label.slice(0, -1) : label;
    },
 
    /**
     * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config.
     * Can be overridden to provide a custom generated label.
     * @template
     * @return {String} The configured field label, or empty string if not defined
     */
    getFieldLabel: function() {
        return this.trimLabelSeparator();
    },
 
    /**
     * Set the label of this field.
     * @param {String} label The new label. The {@link #labelSeparator} will be automatically
     * appended to the label string.
     */
    setFieldLabel: function(label) {
        var me = this,
            separator = me.labelSeparator,
            errorWrapEl = me.errorWrapEl,
            sideLabel = (me.labelAlign !== 'top'),
            noLabelCls = me.noLabelCls,
            errorWrapUnderSideLabelCls = me.errorWrapUnderSideLabelCls;
 
        label = label || '';
        me.fieldLabel = label;
        
        if (me.rendered) {
            if (Ext.isEmpty(label) && me.hideEmptyLabel) {
                me.addCls(noLabelCls);
                
                if (sideLabel && errorWrapEl) {
                    errorWrapEl.removeCls(errorWrapUnderSideLabelCls);
                }
            }
            else {
                if (separator) {
                    label = me.trimLabelSeparator() + separator;
                }
 
                me.labelTextEl.dom.innerHTML = label;
                me.removeCls(noLabelCls);
                
                if (sideLabel && errorWrapEl) {
                    errorWrapEl.addCls(errorWrapUnderSideLabelCls);
                }
            }
            
            me.updateLayout();
        }
    },
 
    setHideLabel: function(hideLabel) {
        var me = this;
 
        if (hideLabel !== me.hideLabel) {
            me.hideLabel = hideLabel;
            
            if (me.rendered) {
                me[hideLabel ? 'addCls' : 'removeCls'](me.noLabelCls);
                me.updateLayout();
            }
        }
    },
 
    setHideEmptyLabel: function(hideEmptyLabel) {
        var me = this,
            hide;
 
        if (hideEmptyLabel !== me.hideEmptyLabel) {
            me.hideEmptyLabel = hideEmptyLabel;
            
            if (me.rendered && !me.hideLabel) {
                hide = hideEmptyLabel && !me.getFieldLabel();
                me[hide ? 'addCls' : 'removeCls'](me.noLabelCls);
                me.updateLayout();
            }
        }
    },
 
    getInsertionRenderData: function(data, names) {
        var i = names.length,
            name, value;
 
        while (i--) {
            name = names[i];
            value = this[name];
 
            if (value) {
                if (typeof value !== 'string') {
                    if (!value.isTemplate) {
                        value = Ext.XTemplate.getTpl(this, name);
                    }
                    
                    value = value.apply(data);
                }
            }
 
            data[name] = value || '';
        }
 
        return data;
    },
 
    /**
     * Generates the arguments for the field decorations {@link #labelableRenderTpl
     * rendering template}.
     * @param {Object} data optional object to use as the base data object.  If provided,
     * this method will add properties to the base object instead of creating a new one.
     * @return {Object} The template arguments
     * @protected
     */
    getLabelableRenderData: function() {
        var me = this,
            labelAlign = me.labelAlign,
            topLabel = (labelAlign === 'top'),
            rightLabel = (labelAlign === 'right'),
            sideError = (me.msgTarget === 'side'),
            underError = (me.msgTarget === 'under'),
            errorMsgCls = me.errorMsgCls,
            labelPad = me.labelPad,
            labelWidth = me.labelWidth,
            labelClsExtra = me.labelClsExtra || '',
            errorWrapExtraCls = sideError ? me.errorWrapSideCls : me.errorWrapUnderCls,
            labelStyle = '',
            labelInnerStyle = '',
            labelVisible = me.hasVisibleLabel(),
            autoFitErrors = me.autoFitErrors,
            defaultBodyWidth = me.defaultBodyWidth,
            bodyStyle, data;
 
        if (topLabel) {
            labelClsExtra += ' ' + me.topLabelCls;
            
            if (labelPad) {
                labelInnerStyle = 'padding-bottom:' + labelPad + 'px;';
            }
            
            if (sideError && !autoFitErrors) {
                labelClsExtra += ' ' + me.topLabelSideErrorCls;
            }
        }
        else {
            if (rightLabel) {
                labelClsExtra += ' ' + me.rightLabelCls;
            }
            
            if (labelPad) {
                labelStyle += me.getHorizontalPaddingStyle() + labelPad + 'px;';
            }
            
            labelStyle += 'width:' + (labelWidth + (labelPad ? labelPad : 0)) + 'px;';
            
            // inner label needs width as well so that setting width on the outside
            // that is smaller than the natural width, will be ensured to take width
            // away from the body, and not the label.
            labelInnerStyle = 'width:' + labelWidth + 'px';
        }
 
        if (labelVisible) {
            if (!topLabel && underError) {
                errorWrapExtraCls += ' ' + me.errorWrapUnderSideLabelCls;
            }
        }
 
        if (defaultBodyWidth) {
            // This is here to support textfield's deprecated "size" config
            bodyStyle = 'min-width:' + defaultBodyWidth + 'px;max-width:' +
                        defaultBodyWidth + 'px;';
        }
 
        data = {
            id: me.id,
            inputId: me.getInputId(),
            labelCls: me.labelCls,
            labelClsExtra: labelClsExtra,
            labelStyle: labelStyle + (me.labelStyle || ''),
            labelInnerStyle: labelInnerStyle,
            labelInnerCls: me.labelInnerCls,
            labelTextCls: me.labelTextCls,
            skipLabelForAttribute: !!me.skipLabelForAttribute,
            unselectableCls: Ext.Element.unselectableCls,
            bodyStyle: bodyStyle,
            baseBodyCls: me.baseBodyCls,
            fieldBodyCls: me.fieldBodyCls,
            extraFieldBodyCls: me.extraFieldBodyCls,
            errorWrapCls: me.errorWrapCls,
            errorWrapExtraCls: errorWrapExtraCls,
            renderError: sideError || underError,
            invalidMsgCls: sideError ? me.invalidIconCls : underError ? me.invalidUnderCls : '',
            errorMsgCls: errorMsgCls,
            growCls: me.grow ? me.growCls : '',
            tipAnchorTarget: me.id + '-inputEl',
            errorWrapStyle: (sideError && !autoFitErrors) ? 'visibility:hidden' : 'display:none',
            fieldLabel: me.getFieldLabel(),
            labelSeparator: me.labelSeparator,
            renderAriaElements: !!me.renderAriaElements,
            ariaStatus: ''
        };
        
        if (me.ariaHelp) {
            data.ariaHelp = Ext.String.htmlEncode(me.ariaHelp);
        }
 
        me.getInsertionRenderData(data, me.labelableInsertions);
 
        return data;
    },
 
    // hook for rtl
    getHorizontalPaddingStyle: function() {
        return 'padding-right:';
    },
 
    beforeLabelRender: function() {
        var me = this;
        
        me.setFieldDefaults(me.getInherited().fieldDefaults);
        
        if (me.ownerLayout) {
            me.addCls(Ext.baseCSSPrefix + me.ownerLayout.type + '-form-item');
        }
        
        if (!me.hasVisibleLabel()) {
            me.addCls(me.noLabelCls);
        }
    },
 
    onLabelRender: function() {
        var me = this,
            style = {},
            ExtElement = Ext.Element,
            errorWrapEl = me.errorWrapEl,
            margins, side;
 
        if (errorWrapEl) {
            errorWrapEl.setVisibilityMode(
                (me.msgTarget === 'side' && !me.autoFitErrors)
                    ? ExtElement.VISIBILITY
                    : ExtElement.DISPLAY
            );
        }
 
        if (me.extraMargins) {
            margins = me.el.getMargin();
            
            for (side in margins) {
                if (margins.hasOwnProperty(side)) {
                    style['margin-' + side] = (margins[side] + me.extraMargins[side]) + 'px';
                }
            }
            
            me.el.setStyle(style);
        }
    },
 
    /**
     * Checks if the field has a visible label
     * @return {Boolean} True if the field has a visible label
     */
    hasVisibleLabel: function() {
        if (this.hideLabel) {
            return false;
        }
        
        return !(this.hideEmptyLabel && !this.getFieldLabel());
    },
 
    /**
     * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string,
     * should be implemented by classes including this mixin as needed.
     * @return {String} The markup to be inserted
     * @protected
     */
    getSubTplMarkup: function() {
        return '';
    },
 
    /**
     * Get the input id, if any, for this component. This is used as the "for" attribute on the
     * label element. Implementing subclasses may also use this as e.g. the id for their own
     * input element.
     * @return {String} The input id
     */
    getInputId: function() {
        return '';
    },
 
    /**
     * Gets the active error message for this component, if any. This does not trigger validation
     * on its own, it merely returns any message that the component may already hold.
     * @return {String} The active error message on the component; if there is no error,
     * an empty string is returned.
     */
    getActiveError: function() {
        return this.activeError || '';
    },
 
    /**
     * Tells whether the field currently has an active error message. This does not trigger
     * validation on its own, it merely looks for any message that the component may already hold.
     * @return {Boolean} 
     */
    hasActiveError: function() {
        return !!this.getActiveError();
    },
 
    /**
     * Sets the active error message to the given string. This replaces the entire error message
     * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array
     * of messages and formats them according to the {@link #activeErrorsTpl}. Note that this only
     * updates the error message element's text and attributes, you'll have to call
     * doComponentLayout to actually update the field's layout to match. If the field extends
     * {@link Ext.form.field.Base} you should call
     * {@link Ext.form.field.Base#markInvalid markInvalid} instead.
     * @param {String} msg The error message
     */
    setActiveError: function(msg) {
        this.setActiveErrors(msg);
    },
 
    /**
     * Gets an Array of any active error messages currently applied to the field. This does not
     * trigger validation on its own, it merely returns any messages that the component
     * may already hold.
     * @return {String[]} The active error messages on the component; if there are no errors,
     * an empty Array is returned.
     */
    getActiveErrors: function() {
        return this.activeErrors || [];
    },
 
    /**
     * Set the active error message to an Array of error messages. The messages are formatted
     * into a single message string using the {@link #activeErrorsTpl}. Also see
     * {@link #setActiveError} which allows setting the entire error contents with a single string.
     * Note that this only updates the error message element's text and attributes, you'll have to
     * call doComponentLayout to actually update the field's layout to match. If the field extends
     * {@link Ext.form.field.Base} you should call
     * {@link Ext.form.field.Base#markInvalid markInvalid} instead.
     * @param {String[]} errors The error messages
     */
    setActiveErrors: function(errors) {
        var me = this,
            errorWrapEl = me.errorWrapEl,
            msgTarget = me.msgTarget,
            isSide = msgTarget === 'side',
            isQtip = msgTarget === 'qtip',
            ariaErrorEl = me.ariaErrorEl,
            actionEl, activeError, tpl, targetEl, ariaTpl, errStr, errText;
 
        errors = Ext.Array.from(errors);
        tpl = me.lookupTpl('activeErrorsTpl');
 
        me.activeErrors = errors;
        
        activeError = me.activeError = tpl.apply({
            fieldLabel: me.fieldLabel,
            errors: errors,
            listCls: Ext.baseCSSPrefix + 'list-plain'
        });
 
        me.renderActiveError();
 
        if (me.rendered) {
            actionEl = me.getActionEl();
            
            if (isSide) {
                me.errorEl.dom.setAttribute('data-errorqtip', activeError);
            }
            else if (isQtip) {
                actionEl.dom.setAttribute('data-errorqtip', activeError);
            }
            else if (msgTarget === 'title') {
                actionEl.dom.setAttribute('title', activeError);
            }
 
            // If msgTarget is title, setting an alert is redundant for ARIA purposes
            if (msgTarget !== 'title' && ariaErrorEl) {
                ariaTpl = me.lookupTpl('ariaActiveErrorsTpl');
                errStr = ariaTpl.apply({ errors: errors });
                
                // Setting innerHTML on aria-live element will replace inner text node,
                // and the browser will fire DOM change event even if the text is the same.
                // We don't want the announcement to repeat if the text hasn't changed.
                errText = Ext.String.formatEncode(me.ariaErrorText, errStr, me.fieldLabel);
                
                if (ariaErrorEl.dom.innerHTML !== errText) {
                    ariaErrorEl.dom.innerHTML = errText;
                }
                
                // ariaStatusEl is not aria-live so it's OK to change it every time.
                // Contents will be announced only upon focusing the field.
                me.ariaStatusEl.dom.innerHTML = Ext.String.htmlEncode(errStr);
            }
            
            if (isSide || isQtip) {
                Ext.form.Labelable.initTip();
            }
 
            if (!me.msgTargets[msgTarget]) {
                targetEl = Ext.get(msgTarget);
 
                if (targetEl) {
                    targetEl.dom.innerHTML = activeError;
                }
            }
        }
 
        if (errorWrapEl) {
            errorWrapEl.setVisible(errors.length > 0);
            
            if (isSide && me.autoFitErrors) {
                me.labelEl.addCls(me.topLabelSideErrorCls);
            }
            
            me.updateLayout();
        }
    },
 
    /**
     * Clears the active error message(s). Note that this only clears the error message element's
     * text and attributes, you'll have to call doComponentLayout to actually update the field's
     * layout to match. If the field extends {@link Ext.form.field.Base} you should call
     * {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.
     */
    unsetActiveError: function() {
        var me = this,
            errorWrapEl = me.errorWrapEl,
            ariaErrorEl = me.ariaErrorEl,
            msgTarget = me.msgTarget,
            restoreDisplay = me.restoreDisplay,
            actionEl, targetEl;
 
        if (me.hasActiveError()) {
            delete me.activeError;
            delete me.activeErrors;
            me.renderActiveError();
 
            if (me.rendered) {
                actionEl = me.getActionEl();
                
                if (msgTarget === 'qtip') {
                    actionEl.dom.removeAttribute('data-errorqtip');
                }
                else if (msgTarget === 'title') {
                    actionEl.dom.removeAttribute('title');
                }
                
                if (msgTarget !== 'title' && ariaErrorEl) {
                    ariaErrorEl.dom.innerHTML = me.ariaStatusEl.dom.innerHTML = '';
                }
 
                if (!me.msgTargets[msgTarget]) {
                    targetEl = Ext.get(msgTarget);
 
                    if (targetEl) {
                        targetEl.dom.innerHTML = '';
                    }
                }
 
                if (errorWrapEl) {
                    errorWrapEl.hide();
                    
                    if (msgTarget === 'side' && me.autoFitErrors) {
                        me.labelEl.removeCls(me.topLabelSideErrorCls);
                    }
                    
                    me.updateLayout();
 
                    // IE8 hack for https://sencha.jira.com/browse/EXTJS-17536.
                    // Need to force a relayout of the display:table form item.
                    // TODO: Remove when IE8 retires.
                    if (restoreDisplay) {
                        me.el.dom.style.display = 'block';
                        me.restoreDisplay();
                    }
                }
            }
        }
    },
 
    doRestoreDisplay: function() {
        // IE8 hack for https://sencha.jira.com/browse/EXTJS-17536.
        // Need to force a relayout of the display:table form item.
        // TODO: Remove this method when IE8 retires.
        var el = this.el;
        
        if (el && el.dom) {
            el.dom.style.display = '';
        }
    },
 
    /**
     * @private
     * Updates the rendered DOM to match the current activeError. This only updates the content and
     * attributes, you'll have to call doComponentLayout to actually update the display.
     */
    renderActiveError: function() {
        var me = this,
            activeError = me.getActiveError(),
            hasError = !!activeError;
 
        if (activeError !== me.lastActiveError) {
            me.lastActiveError = activeError;
            me.fireEvent('errorchange', me, activeError);
        }
 
        if (me.rendered && !me.destroyed && !me.preventMark) {
            me.toggleInvalidCls(hasError);
            
            // Update the errorEl (There will only be one if msgTarget is 'side' or 'under')
            // with the error message text
            if (me.errorEl) {
                me.errorEl.dom.innerHTML = activeError;
            }
        }
    },
 
    /**
     * @private
     * Add/remove invalid class(es)
     * @param {Boolean} hasError 
     */
    toggleInvalidCls: function(hasError) {
        this.el[hasError ? 'addCls' : 'removeCls'](this.invalidCls);
    },
 
    /**
     * Applies a set of default configuration values to this Labelable instance. For each
     * of the properties in the given object, check if this component hasOwnProperty that config;
     * if not then it's inheriting a default value from its prototype and we should apply
     * the default value.
     * @param {Object} defaults The defaults to apply to the object.
     */
    setFieldDefaults: function(defaults) {
        var key;
 
        for (key in defaults) {
            if (!this.hasOwnProperty(key)) {
                this[key] = defaults[key];
            }
        }
    }
}, function() {
    if (Ext.supports.Touch) {
        this.prototype.msgTarget = 'side';
    }
});