/**
 * Field is the base class for all form fields. It provides a lot of shared functionality to all
 * field subclasses (for example labels, simple validation, {@link #clearable clearing} and tab
 * index management), but is rarely used directly. Instead, it is much more common to use one of
 * the field subclasses:
 *
 *     xtype            Class
 *     ---------------------------------------
 *     textfield        {@link Ext.field.Text}
 *     numberfield      {@link Ext.field.Number}
 *     textareafield    {@link Ext.field.TextArea}
 *     hiddenfield      {@link Ext.field.Hidden}
 *     radiofield       {@link Ext.field.Radio}
 *     filefield        {@link Ext.field.File}
 *     checkboxfield    {@link Ext.field.Checkbox}
 *     selectfield      {@link Ext.field.Select}
 *     togglefield      {@link Ext.field.Toggle}
 *     fieldset         {@link Ext.form.FieldSet}
 *
 * Fields are normally used within the context of a form and/or fieldset. See the
 * {@link Ext.form.Panel FormPanel} and {@link Ext.form.FieldSet FieldSet} docs for examples on how
 * to put those together, or the list of links above for usage of individual field types. If you
 * wish to create your own Field subclasses you can extend this class, though it is sometimes more
 * useful to extend {@link Ext.field.Text} as this provides additional text entry functionality.
 */
Ext.define('Ext.field.Field', {
    extend: 'Ext.Component',
    alternateClassName: 'Ext.form.Field',
    xtype: 'field',
 
    mixins: [
        'Ext.field.Dirty'
    ],
 
    /**
     * @property {Boolean} isField
     * `true` in this class to identify an object this type, or subclass thereof.
     *
     * This is used by {@link Ext.form.Panel#getValues} to determine which
     * components inside a form are fields.
     * @readonly
     */
    isField: true,
 
    /**
     * @private
     */
    isFormField: true,
 
    /**
     * @event click
     * Fires whenever the input is clicked.
     * @param {Ext.event.Event} e The event object.
     */
 
    /**
     * @event keyup
     * Fires whenever keyup is detected.
     * @param {Ext.event.Event} e The event object.
     */
 
    /**
     * @event paste
     * Fires whenever paste is detected.
     * @param {Ext.event.Event} e The event object.
     */
 
    /**
     * @event mousedown
     * Fires whenever the input has a mousedown occur.
     * @param {Ext.event.Event} e The event object.
     */
 
    /**
     * @event errorchange
     * Fires when the active error message changes.
     * @param {Ext.field.Field} this 
     * @param {String} error The current error message
     * @since 6.5.0
     */
 
    /**
     * @event change
     * Fires when the value has changed.
     * @param {Ext.field.Field} this This field
     * @param {String} newValue The new value
     * @param {String} oldValue The original value
     */
 
    cachedConfig: {
        /**
         * @cfg {'start'/'center'/'end'/'stretch'}
         *
         * The horizontal alignment the contents of this field's body element.
         */
        bodyAlign: 'start',
 
        /**
         * @cfg {'top'/'left'/'bottom'/'right'}
         * The position to render the label relative to the field body.
         */
        labelAlign: 'left',
 
        /**
         * @cfg {String} 
         * Optional CSS class to add to the Label element.
         */
        labelCls: null,
 
        /**
         * @cfg {'top'/'right'/'bottom'/'left'}
         *
         * Text alignment of this field's label
         */
        labelTextAlign: 'left',
 
        /**
         * @cfg {Number/String}
         * Width of this field's label. Can be a number of pixels or any valid CSS value,
         * such as `'30%'`. To size the label to its text, use `labelWidth: 'auto'`
         */
        labelWidth: null,
 
        /**
         * @cfg {Number/String}
         * Min-width of this field's label.
         */
        labelMinWidth: null,
 
        /**
         * @cfg {Boolean} 
         * `true` to allow the label to wrap. If set to `false`, the label will be truncated
         * with an ellipsis.
         */
        labelWrap: false
    },
 
    config: {
        /**
         * @cfg {String} 
         * The field's name.  Used by form panels to gather data to be submitted.
         *
         * For {@link Ext.field.Input Input Fields} this name is set as the `name` attribute
         * of the `inputElement`
         */
        name: null,
 
        /**
         * The label of this field
         * @cfg {String} label
         * @accessor
         */
        label: null,
 
        /**
         * @cfg {Boolean} 
         * `true` to make this field required.
         */
        required: false,
 
        /**
         * @cfg {String} 
         * The error message to display when {@link #required} is `true` and the field's
         * value is "empty" (`null`, `undefined`, or empty string).
         */
        requiredMessage: 'This field is required',
 
        /**
         * @cfg {Mixed} 
         * The field's value
         */
        value: null,
 
        /**
         * @cfg {Ext.data.field.Field/Object/String} dataType
         * A config for a {@link Ext.data.field.Field} or data field sub-class instance
         * used to serialize this field's value for form submission. This is used by the
         * {@link #serialize} method unless `modelValidation` is used, in which case, the
         * data field of the bound {@link Ext.data.Model model} is used.
         *
         * For example:
         *
         *      {
         *          xtype: 'datefield',
         *          dataType: {
         *              // type: 'date'  (datefield does this by default)
         *              dateWriteFormat: 'Y-m-d'
         *          }
         *      }
         *
         * If this config is a string, it is used to create a {@link Ext.data.field.Field field}
         * by that alias.
         * @since 7.0
         */
        dataType: {
            cached: true,
            $value: null
        },
 
        /**
         * @cfg {Mixed} 
         * A validator or array of validators to be applied to the field.
         *
         * When the field is validated, each validator is applied and if any one of them
         * determines the field  is invalid, the field will be marked as invalid.  If you
         * examine the field's validators, you will get an array of Ext.data.Validators.
         *
         * Validation currently is synchronous.  If you need to validate a field with interaction
         * with a server, you would do this when the form is submitted.
         *
         * A validator may be:
         *
         * * A regexp - if the field fails to match the regexp, it is invalid.
         * * A function - the function will be called to validate the field; it should return
         * false if invalid.
         *
         * * An object - an object with a member fn that is a function to be called to validate
         * the field.
         *
         * * An instantiated Validator {@link  Ext.data.validator.Validator}
         */
        validators: null,
 
        /**
         * @cfg {String} validationMessage
         * For validation, regex, etc., this is the error message returned if field is invalid.
         */
        validationMessage: 'Is in the wrong format',
 
        /**
         * @cfg {Boolean} [validateDisabled=false]
         * `true` to validate the field, even if it is disabled.
         */
        validateDisabled: null,
 
        /**
         * @cfg {Boolean} [disabled=false] `true` to disable the field.
         *
         * Be aware that conformant with the [HTML specification](http://www.w3.org/TR/html401/interact/forms.html),
         * disabled Fields will not be {@link Ext.form.Panel#method-submit submitted}.
         * @accessor
         */
 
        /**
         * @cfg {Boolean} fullscreen
         * @hide
         */
 
        /**
         * @cfg {Boolean} [autoFitErrors=true]
         * Whether to adjust the field's body width to make room for the
         * {@link #errorTarget error messages}.
         */
        autoFitErrors: null,
 
        /**
         * @cfg {Boolean} [inline=false]
         * `true` to cause this field to layout with inline element behavior.
         * An inline field's width is determined by the total width of its label and body
         * elements instead of automatically expanding to fill the width of its container.
         */
        inline: null,
 
        /**
         * @cfg {String/String[]} error
         * The error (or errors) to display for this field. This config is typically set
         * by the field's {@link #cfg!validators validators} but can be set directly if
         * an error needs to be associated with a field manually.
         *
         * Errors will be HTML encoded as necessary and {@link #cfg!errorTpl formatted}
         * before becoming the current {@link #cfg!errorMessage}.
         * @since 6.5.0
         */
        error: null,
 
        /**
         * @cfg {String} errorMessage
         * The field's error message to display as {@link #cfg!errorTarget specified}.
         * This message must already be properly formatted and encoded as appropriate
         * for the `errorTarget`.
         * @since 6.5.0
         */
        errorMessage: null,
 
        /**
         * @cfg {String} errorTarget
         * The location where the error message text should display.
         *
         * The following are values that have predefined meaning:
         *
         * - `qtip` Display a {@link Ext.tip.Manager quick tip} containing the message
         *  when the user hovers (or taps) the field. For this option to work, quick tips
         *  must be enabled by calling {@link Ext.tip.Manager#init}.
         * - `side` Add an error icon to the right of the field, displaying the message
         *  in a popup on hover or tap.
         * - `title` Display the message in a default browser `title` attribute.
         * - `under` Add a `div` beneath the field containing the error message.
         * @since 6.5.0
         */
        errorTarget: 'qtip',
 
        /**
         * @cfg {String/String[]/Ext.XTemplate} errorTpl
         * The template used to format the {@link #cfg!error error} set for this field.
         * By default, the {@link #cfg!errorTarget errorTarget} is used to determine
         * whether the error(s) are formatted as an HTML list or as plain text.
         * @since 6.5.0
         */
        errorTpl: null,
 
        /**
         * @cfg {Object} errorTip
         * The default config that will be used to display errors in the tooltip.
         * @since 6.5.0
         */
        errorTip: null,
 
        /**
         * @cfg {String} 
         * @private
         */
        sideError: null,
 
        /**
         * @cfg {String} 
         * @private
         */
        tipError: null,
 
        /**
         * @cfg {String} 
         * @private
         */
        titleError: null,
 
        /**
         * @cfg {String} 
         * @private
         */
        underError: null
    },
 
    /**
     * @property {Ext.XTemplate} htmlErrorsTpl
     * The default template used to format errors in HTML. This property is promoted
     * to an `Ext.XTemplate` instance on first use.
     * @private
     * @readonly
     * @since 6.5.0
     */
    htmlErrorsTpl: [
        '<tpl if="count == 1">',
        '<tpl for="errors">{.:htmlEncode}</tpl>',
        '<tpl elseif="count">',
        '<ul class="{listCls}">',
        '<tpl for="errors"><li>{.:htmlEncode}</li></tpl>',
        '</ul>',
        '</tpl>'
    ],
 
    /**
     * @property {Ext.XTemplate} plainErrorsTpl
     * The default template used to format errors as plain text. This property is promoted
     * to an `Ext.XTemplate` instance on first use.
     * @private
     * @readonly
     * @since 6.5.0
     */
    plainErrorsTpl: [
        '<tpl if="count">',
        '<tpl for="errors" between="\\n">{.}</tpl>',
        '</tpl>'
    ],
 
    _errorTplMap: {
        title: 'plainErrorsTpl'
    },
 
    /**
     * @property {Mixed} originalValue
     * The original value when the field was instantiated.
     * @private
     */
    originalValue: null,
 
    focusable: true,
 
    classCls: Ext.baseCSSPrefix + 'field',
    requiredCls: Ext.baseCSSPrefix + 'required',
    noLabelWrapCls: Ext.baseCSSPrefix + 'no-label-wrap',
    invalidCls: Ext.baseCSSPrefix + 'invalid',
    noAutoFitErrorsCls: Ext.baseCSSPrefix + 'no-auto-fit-errors',
    inlineCls: Ext.baseCSSPrefix + 'inline',
    labelAlignVerticalCls: Ext.baseCSSPrefix + 'label-align-vertical',
    labelAlignHorizontalCls: Ext.baseCSSPrefix + 'label-align-horizontal',
    labeledCls: Ext.baseCSSPrefix + 'labeled',
 
    verticalLabelMap: {
        top: 1,
        placeholder: 1,
        bottom: 1
    },
 
    horizontalLabelMap: {
        left: 1,
        right: 1
    },
 
    nameable: true,
 
    /**
     * @property {"none"/"auto"/"all"} validateOnInit
     * Determines how initial values will handle validation
     * - none: Will not validate any initial values
     * - auto: Will only validate non-empty initial values
     * - all: Will validate all initial values
     */
    validateOnInit: 'auto',
 
    errorElement: null,
    errorIconElement: null,
    errorMessageElement: null,
 
    element: {
        reference: 'element',
        classList: [
            Ext.supports.CSSMinContent ? '' : Ext.baseCSSPrefix + 'no-min-content',
            Ext.supports.PercentageSizeFlexBug
                ? Ext.baseCSSPrefix + 'has-percentage-size-flex-bug'
                : ''
        ]
    },
 
    initialize: function() {
        var me = this;
 
        me.callParent();
 
        if (me.getValue() === '' && me.validateOnInit === 'all') {
            me.validate();
        }
 
        me.handleFieldDefaults();
    },
 
    /**
     * @private
     * Checks if the value has changed. Allows subclasses to override for
     * any more complex logic.
     */
    didValueChange: function(newVal, oldVal) {
        return !this.isEqual(newVal, oldVal);
    },
 
    getTemplate: function() {
        return [{
            reference: 'labelElement',
            cls: Ext.baseCSSPrefix + 'label-el',
            tag: 'label',
            children: [{
                reference: 'labelTextElement',
                cls: Ext.baseCSSPrefix + 'label-text-el',
                tag: 'span'
            }]
        }, {
            reference: 'bodyWrapElement',
            cls: Ext.baseCSSPrefix + 'body-wrap-el',
            children: [{
                reference: 'bodyElement',
                cls: Ext.baseCSSPrefix + 'body-el',
                children: this.getBodyTemplate()
            }, {
                reference: 'errorElement',
                cls: Ext.baseCSSPrefix + 'error-el',
                children: [{
                    reference: 'errorIconElement',
                    cls: Ext.baseCSSPrefix + 'error-icon-el ' +
                    Ext.baseCSSPrefix + 'font-icon'
                }, {
                    reference: 'errorMessageElement',
                    cls: Ext.baseCSSPrefix + 'error-message-el'
                }]
            }]
        }];
    },
 
    getBodyTemplate: Ext.emptyFn,
 
    initElement: function() {
        this.callParent();
 
        // alias for backward compatibility
        this.innerElement = this.innerElement || this.bodyElement;
    },
 
    onFocusLeave: function(e) {
        this.callParent([e]);
 
        this.completeEdit();
    },
 
    /**
     * @method
     * @protected
     * Called when focus leaves this input field.
     * Used to postprocess raw values and perform conversion and validation.
     */
    completeEdit: Ext.emptyFn,
 
    updateBodyAlign: function(bodyAlign, oldBodyAlign) {
        var element = this.element;
 
        if (oldBodyAlign) {
            element.removeCls(Ext.baseCSSPrefix + 'body-align-' + oldBodyAlign);
        }
 
        if (bodyAlign) {
            element.addCls(Ext.baseCSSPrefix + 'body-align-' + bodyAlign);
        }
    },
 
    updateAutoFitErrors: function(autoFitErrors) {
        this.toggleCls(this.noAutoFitErrorsCls, autoFitErrors === false);
    },
 
    applyErrorTpl: function(tpl) {
        if (tpl && !tpl.isTemplate) {
            tpl = Ext.XTemplate.get(tpl);
        }
 
        return tpl;
    },
 
    applyDataType: function(field) {
        return field && Ext.Factory.dataField(field);
    },
 
    /**
     * Formats the given error(s) based on the given {@link #cfg!errorTpl} and the
     * specified {@link #cfg!errorTarget}.
     * @param {String[]} errors 
     * @return {String} 
     * @protected
     * @since 6.5.0
     */
    formatErrors: function(errors) {
        var me = this,
            tpl = me.getErrorTpl();
 
        if (!tpl) {
            tpl = me.lookupTpl(me._errorTplMap[me.getErrorTarget()] || 'htmlErrorsTpl');
        }
 
        return tpl.apply({
            count: errors ? errors.length : 0,
            label: me.getLabel(),
            errors: errors
        });
    },
 
    updateError: function(value) {
        var msg = this.formatErrors(Ext.Array.from(value));
 
        this.setErrorMessage(msg);
    },
 
    updateErrorMessage: function(msg) {
        var me = this,
            owner, errorTarget;
 
        me.fireEvent('errorchange', me, msg);
 
        if (me.preventMark) {
            return;
        }
 
        me.toggleInvalidCls(!!msg);
 
        errorTarget = me.getErrorTarget();
 
        switch (errorTarget) {
            case 'side':
                me.setSideError(msg);
                break;
 
            case 'qtip':
                me.setTipError(msg);
                break;
 
            case 'title':
                me.setTitleError(msg);
                break;
 
            case 'under':
                me.setUnderError(msg);
                break;
 
            case 'parent':
                owner = me.up('[onFieldErrorChange]');
 
                if (owner) {
                    owner.onFieldErrorChange(me, msg);
                }
 
                break;
 
            // default:
                // TODO
                // @method ==> controller
                // .foo ==> DQ
        }
    },
 
    updateErrorTarget: function(target, oldTarget) {
        var me = this,
            error, owner;
 
        if (oldTarget) {
            me.removeCls(Ext.baseCSSPrefix + 'error-target-' + oldTarget);
 
            if (oldTarget === 'qtip') {
                me.setTipError(null);
            }
            else if (oldTarget === 'title') {
                me.setTitleError(null);
            }
            else if (oldTarget === 'side') {
                me.setSideError(null);
            }
            else if (oldTarget === 'under') {
                me.setUnderError(null);
            }
            else if (oldTarget === 'parent') {
                owner = me.up('[onFieldErrorChange]');
 
                if (owner) {
                    owner.onFieldErrorChange(me);
                }
            }
        }
 
        if (target) {
            me.addCls(Ext.baseCSSPrefix + 'error-target-' + target);
 
            if (!me.isConfiguring) {
                error = me.getError();
 
                if (error) {
                    if (target === 'qtip') {
                        me.setTipError(error);
                    }
                    else if (target === 'title') {
                        me.setTitleError(error);
                    }
                    else if (target === 'side') {
                        me.setSideError(error);
                    }
                    else if (target === 'under') {
                        me.setUnderError(error);
                    }
                    else if (target === 'parent') {
                        owner = me.up('[onFieldErrorChange]');
 
                        if (owner) {
                            owner.onFieldErrorChange(me, error);
                        }
                    }
                }
            }
        }
    },
 
    updateInline: function(inline) {
        this.toggleCls(this.inlineCls, inline);
    },
 
    updateSideError: function(error) {
        if (error) {
            error = Ext.apply({
                html: error
            }, this.getErrorTip());
        }
 
        this.errorElement.getData().qtip = error;
    },
 
    updateTipError: function(error) {
        if (error) {
            error = Ext.apply({
                html: error
            }, this.getErrorTip());
        }
 
        // Using the bodyElement as the target of the qtip ensures that the tip is visually
        // aligned to the field body, regardless of label positioning or bottom padding added
        // by the stylesheet to support $field-vertical-spacing
        this.bodyElement.getData().qtip = error;
    },
 
    updateTitleError: function(error) {
        var dom = this.el.dom;
 
        if (error) {
            dom.setAttribute('title', error);
        }
        else {
            dom.removeAttribute('title');
        }
    },
 
    updateUnderError: function(error) {
        this.errorMessageElement.dom.innerHTML = error || '';
    },
 
    updateLabel: function(label) {
        this.labelTextElement.setHtml(label);
        this.el.toggleCls(this.labeledCls, !!label);
    },
 
    updateLabelAlign: function(newLabelAlign, oldLabelAlign) {
        var me = this,
            element = me.element;
 
        if (oldLabelAlign) {
            element.removeCls(Ext.baseCSSPrefix + 'label-align-' + oldLabelAlign);
        }
 
        if (newLabelAlign) {
            element.addCls(Ext.baseCSSPrefix + 'label-align-' + newLabelAlign);
        }
 
        element.toggleCls(me.labelAlignVerticalCls, newLabelAlign in me.verticalLabelMap);
        element.toggleCls(me.labelAlignHorizontalCls, newLabelAlign in me.horizontalLabelMap);
    },
 
    updateLabelTextAlign: function(labelTextAlign, oldLabelTextAlign) {
        var element = this.element;
 
        if (oldLabelTextAlign) {
            element.removeCls(Ext.baseCSSPrefix + 'label-text-align-' + oldLabelTextAlign);
        }
 
        if (labelTextAlign) {
            element.addCls(Ext.baseCSSPrefix + 'label-text-align-' + labelTextAlign);
        }
    },
 
    updateLabelCls: function(newLabelCls, oldLabelCls) {
        var labelElement = this.labelElement;
 
        if (newLabelCls) {
            labelElement.addCls(newLabelCls);
        }
 
        if (oldLabelCls) {
            labelElement.removeCls(oldLabelCls);
        }
    },
 
    updateLabelWidth: function(labelWidth) {
        this.labelElement.setWidth(labelWidth);
    },
 
    updateLabelMinWidth: function(labelMinWidth) {
        this.labelElement.setStyle('min-width', Ext.Element.addUnits(labelMinWidth));
    },
 
    updateLabelWrap: function(labelWrap) {
        this.element.toggleCls(this.noLabelWrapCls, !labelWrap);
    },
 
    updateName: function(newName) {
        this.name = newName;
    },
 
    updateRequired: function(required) {
        var me = this;
 
        me.element.toggleCls(me.requiredCls, required);
 
        if (!me.isConfiguring) {
            me.validate();
        }
    },
 
    updateRequiredMessage: function() {
        if (!this.isConfiguring) {
            this.validate();
        }
    },
 
    updateDisabled: function(disabled, oldDisabled) {
        this.callParent([disabled, oldDisabled]);
 
        if (!this.isConfiguring) {
            this.validate();
        }
    },
 
    updateValidateDisabled: function() {
        if (!this.isConfiguring) {
            this.validate();
        }
    },
 
    applyValue: function(value) {
        if (this.isConfiguring) {
            this.originalValue = value;
        }
 
        return value;
    },
 
    updateValue: function(value, oldValue) {
        var me = this,
            rawToValue;
 
        // Don't try to validate the field if the value transitions between empty values (null,
        // undefined, '', etc.). This can happen after initialization when binding value to an
        // empty record field (e.g while building a creation form, which is initially empty).
        if (!(Ext.isEmpty(value) && Ext.isEmpty(oldValue))) {
            me.validate();
        }
 
        if (value !== oldValue) {
            rawToValue = me.rawToValue(me.processRawValue(me.getRawValue()));
 
            if (!Ext.isEmpty(rawToValue, true) && String(value) !== String(rawToValue)) {
                me._value = value = rawToValue;
            }
 
            if (!me.isConfiguring) {
                me.fireEvent('change', me, value, oldValue);
            }
        }
 
        me.setDirty(me.isDirty());
    },
 
    /**
     * Resets the current field value back to the original value on this field when it was created.
     *
     *     // This will create a field with an original value
     *     var field = Ext.Viewport.add({
     *         xtype: 'textfield',
     *         value: 'first value'
     *     });
     *
     *     // Update the value
     *     field.setValue('new value');
     *
     *     // Now you can reset it back to the `first value`
     *     field.reset();
     *
     * @return {Ext.field.Field} this
     */
    reset: function() {
        this.setValue(this.originalValue);
 
        return this;
    },
 
    /**
     * Resets the field's {@link #originalValue} property so it matches the current
     * {@link #getValue value}. This is called by
     * {@link Ext.form.Panel}.{@link Ext.form.Panel#setValues setValues} if the form's
     * {@link Ext.form.Panel#trackResetOnLoad trackResetOnLoad} property is set to true.
     */
    resetOriginalValue: function() {
        this.originalValue = this.getValue();
        this.setDirty(false);
    },
 
    /**
     * Returns `true` if the value of this Field has been changed from its {@link #originalValue}.
     * Will return `false` if the field is disabled or has not been rendered yet.
     *
     * @return {Boolean} `true` if this field has been changed from its original value (and
     * is not disabled), `false` otherwise.
     */
    isDirty: function() {
        return !this.isEqual(this.getValue(), this.originalValue);
    },
 
    /**
     * @private
     * Add/remove invalid class(es)
     * @param {Boolean} hasError 
     */
    toggleInvalidCls: function(hasError) {
        this.el[hasError ? 'addCls' : 'removeCls'](this.invalidCls);
    },
 
    /**
     * Mark field as invalid.
     * @deprecated 6.5.0 Use {@link #setError} instead. (for classic compatibility)
     * @since 6.5.0
     */
    markInvalid: function(messages) {
        this.setError(messages);
    },
 
    /**
     * Mark field as valid.
     * @deprecated 6.5.0 Use {@link #setError setError(null)} instead. (for classic compatibility)
     * @since 6.5.0
     */
    clearInvalid: function() {
        this.setError(null);
    },
 
    /**
     * Returns true if field is valid.
     */
    isValid: function() {
        return !this.getError();
    },
 
    /**
     * Returns whether two field {@link #getValue values} are logically equal.
     * Field implementations may override this to provide custom comparison logic appropriate
     * for the particular field's data type.
     * @param {Object} value1 The first value to compare
     * @param {Object} value2 The second value to compare
     * @return {Boolean} True if the values are equal, false if inequal.
     */
    isEqual: function(value1, value2) {
        return String(value1) === String(value2);
    },
 
    /**
     * @private
     */
    applyValidators: function(validators) {
        var me = this,
            i, len, ret;
 
        validators = (validators && !Ext.isArray(validators)) ? [validators] : validators;
        len = validators && validators.length;
        ret = len ? [] : null;
 
        for (= 0; i < len; ++i) {
            ret.push(me.decodeValidator(validators[i]));
        }
 
        return ret;
    },
 
    wrapValidatorFn: function(fn, validator) {
        var me = this,
            scope = validator && validator.scope;
 
        return new Ext.data.validator.Validator(function(value) {
            return Ext.callback(fn, scope, [value], 0, me);
        });
    },
 
    /**
     * This method is called by {@link #method!validate validate} if the value is both
     * non-empty (not `null`, `undefined` or `''`) and if the value can be parsed by the
     * {@link #method!parseValue parseValue} method. This parsing concern is technically
     * only in play for `Ext.field.Text` and derived classes (such as `Ext.field.Date` and
     * `Ext.field.Number`) but the guarantee here is that the `value` will be a parsed
     * value and not the raw string and if the value cannot be parsed, this method will
     * not be called.
     *
     * @param {Mixed} value The (parsed) value
     * @param {String[]} errors The array of validation errors
     * @param {Boolean} [skipLazy] `false` (the default) to run all validators.
     * @private
     */
    doValidate: function(value, errors, skipLazy) {
        var validators = this.getValidators(),
            len = validators && validators.length,
            i, result, validator;
 
        for (= 0; i < len; ++i) {
            validator = validators[i];
 
            if (!skipLazy || !validator.lazy) {
                result = validator.validate(value);
 
                if (result !== true) {
                    //<debug>
                    if (!result || typeof result !== 'string') {
                        Ext.raise('Validator did not return a valid result.');
                    }
                    //</debug>
 
                    errors.push(result);
                }
            }
        }
    },
 
    parseValue: Ext.identityFn, // documented on textfield
 
    /**
     * Validate the field and return it's validity state.
     * To get the existing validity state without re-validating current value,
     * use {@link isValid}.
     *
     * @param {Boolean} [skipLazy] (private) Pass `true` to skip validators marked as `lazy`.
     * @return {Boolean} The new validity state.
     */
    validate: function(skipLazy) {
        var me = this,
            empty, errors, field, record, validity, value;
 
        // If we are in configuration and not validating any values, skip out of here
        if (me.isConfiguring && me.validateOnInit === 'none') {
            return true;
        }
 
        // if field is disabled and cfg not set to validate if disabled, skip out of here
        if (!me.getDisabled() || me.getValidateDisabled()) {
            errors = [];
 
            // If we are a textual input field, get the input element's value.
            // Check the DOM validity state first in case a type="number"
            // check has failed.
            if (me.isInputField && !me.isSelectField) {
                value = me.getInputValue();
                empty = !value;
                validity = empty && me.inputElement.dom.validity;
 
                if (validity && validity.badInput) {
                    errors.push(me.badFormatMessage);
                    empty = false;
                }
            }
            else {
                value = me.getValue();
                empty = value === '' || value == null;
            }
 
            if (empty && me.getRequired()) {
                errors.push(me.getRequiredMessage());
            }
            else if (!errors.length) {
                if (!empty) {
                    // Pass non-empty values along to parseValue to handle things like
                    // datefield and numberfield. Technically this concern is more of a
                    // textfield family issue, but it is awkward to leap out of this
                    // sequence in such a way as to make a surgical override practical...
                    // So we simply provide identityFn as the default parseValue impl
                    value = me.parseValue(value, errors);
                }
 
                if (!errors.length) {
                    field = me._validationField;
                    record = me._validationRecord;
 
                    if (field && record) {
                        field.validate(value, null, errors, record);
                    }
 
                    if (!empty) {
                        me.doValidate(value, errors, skipLazy);
                    }
                }
            }
 
            if (errors.length) {
                me.setError(errors);
 
                return false;
            }
        }
 
        me.setError(null);
 
        return true;
    },
 
    getFocusClsEl: function() {
        return this.element;
    },
 
    updateHeight: function(height, oldHeight) {
        this.callParent([height, oldHeight]);
        this.syncFormLayoutHeight();
    },
 
    onAdded: function(parent, instanced) {
        this.callParent([parent, instanced]);
        this.syncFormLayoutHeight();
        this.validateLayout();
    },
 
    onRemoved: function(destroying) {
        this.callParent([destroying]);
        this.syncFormLayoutHeight();
    },
 
    /**
     * @method rawToValue
     * Converts a raw input field value into a mixed-type value that is suitable for this particular
     * field type. This allows controlling the normalization and conversion of user-entered values
     * into field-type-appropriate values, e.g. a Date object for {@link Ext.field.Date},
     * and is invoked by {@link #getValue}.
     *
     * It is up to individual implementations to decide how to handle raw values that cannot be
     * successfully converted to the desired object type.
     *
     * The base implementation does no conversion, returning the raw value untouched.
     *
     * @param {Object} rawValue 
     * @return {Object} The converted value.
     *
     * @since 7.0
     */
    rawToValue: Ext.identityFn,
 
    /**
     * @method processRawValue
     * Performs any necessary manipulation of a raw field value to prepare it for
     * {@link #rawToValue conversion} and/or {@link #validate validation}, for instance
     * stripping out ignored characters. In the base implementation it does nothing;
     * individual subclasses may override this as needed.
     *
     * @param {Object} value The unprocessed string value
     * @return {Object} The processed string value
     *
     * @since 7.0
     */
    processRawValue: Ext.identityFn,
 
    /**
     * @method transformRawValue
     * Transform the raw value before it is set
     * @protected
     * @param {Object} value The value
     * @return {Object} The value to set
     *
     * @since 7.0
     */
    transformRawValue: Ext.identityFn,
 
    /**
     * @method getRawValue
     * Returns the raw value of the field, without performing any normalization, conversion,
     * or validation. To get a normalized and converted value see {@link #getValue}.
     * @return {String} value The raw String value of the field
     *
     * @since 7.0
     */
    getRawValue: function() {
        var me = this,
            value = me.inputElement ? me.inputElement.getValue() : Ext.valueFrom(me.rawValue, '');
 
        me.rawValue = value;
 
        return value;
    },
 
    handleFieldDefaults: function() {
        var me = this,
            inheritedState = me.getInherited(),
            fieldDefaults, key, prop, initialConfig, value;
 
        // Update set of field defaults values to current field instance.
        // Checks if component doesn't have passed property,
        // then set current fields with passed property
        if (inheritedState) {
            fieldDefaults = inheritedState.fieldDefaults;
 
            if (fieldDefaults) {
                initialConfig = me.initialConfig;
 
                for (key in fieldDefaults) {
                    if (!initialConfig[key]) {
                        // to prepare setter method syntax
                        prop = Ext.util.Format.capitalize(key);
                        value = fieldDefaults[key];
 
                        if (me['set' + prop]) {
                            me['set' + prop](value);
                        }
                        else {
                            me[key] = value;
                        }
                    }
                }
            }
        }
    },
 
    /**
     * A function which converts the field’s value for submission. This is the value used
     * for form submit. The field's value is serialized using the serializer for the
     * associated {@link Ext.data.Model} when using `modelValidation`, or using the
     * serializer specified by the {@link #dataType} config.
     * @return {String} 
     * @since 7.0
     */
    serialize: function() {
        var me = this,
            value = me.getValue(),
            dataField;
 
        // Serialization uses one of the available field serializers,
        // either through model validation specified on the form (and
        // exposed via this._validationField) or on the dataField. Both
        // are optional, and even if the data field is there, the
        // serialize method is optional.
 
        if (value != null) {
            dataField = me._validationField || me.getDataType();
 
            if (dataField && dataField.serialize) {
                value = dataField.serialize(value);
            }
        }
 
        return value;
    },
 
    privates: {
        syncFormLayoutHeight: function() {
            var me = this,
                parent = me.parent,
                height = me.getHeight();
 
            if (!(height && parent && parent.getLayout().isFormLayout)) {
                height = null;
            }
 
            me.bodyElement.setHeight(height);
        },
 
        validateLayout: function() {
            var errorTarget = this.getErrorTarget(),
                parent = this.parent;
 
            if (this.isInner && parent && parent.getLayout().isFormLayout) {
                // Form layout only supports left aligned labels
                this.setLabelAlign('left');
 
                // Form layout does not support "under" error target
                if (errorTarget === 'under') {
                    this.setErrorTarget('side');
                }
            }
        },
 
        applyBind: function(bind, currentBindings) {
            var me = this,
                valueBinding = currentBindings && currentBindings.value,
                bindings, newValueBind;
 
            bindings = me.callParent([ bind, currentBindings ]);
 
            if (bindings) {
                newValueBind = bindings.value;
                me.hasBindingValue = !!newValueBind;
 
                if (newValueBind !== valueBinding && me.getInherited().modelValidation) {
                    me.updateValueBinding(bindings);
                }
            }
 
            return bindings;
        },
 
        updateValueBinding: function(bindings) {
            var me = this,
                newBinding = bindings.value,
                fieldBinding = bindings.$fieldBinding;
 
            if (fieldBinding) {
                fieldBinding.destroy();
                bindings.$fieldBinding = null;
            }
 
            if (newBinding && newBinding.bindValidationField) {
                me.fieldBinding = newBinding.bindValidationField('setValidationField', me);
            }
        },
 
        setValidationField: function(field, record) {
            this._validationField = field;
            this._validationRecord = record;
        },
 
        decodeValidator: function(validator) {
            var type = Ext.typeOf(validator),
                result = validator.fn;
 
            if (type === 'function') {
                result = this.wrapValidatorFn(validator);
            }
            else if (type === 'regexp') {
                result = Ext.Factory.validator({
                    type: 'format',
                    matcher: validator
                });
            }
            else if (type === 'object' && result && !validator.isValidator) {
                result = this.wrapValidatorFn(result, validator);
            }
            else {
                result = Ext.Factory.validator(validator);
            }
 
            return result;
        }
    }
});