    /**
     * @class Ext.layout.component.field.Field
     * @extends Ext.layout.component.Component
     * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
     * the form control, label, and error message treatment.
     * @private
     */
    Ext.define('Ext.layout.component.field.Field', {

        /* Begin Definitions */

        alias: ['layout.field'],

        extend: 'Ext.layout.component.Component',

        uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],

        /* End Definitions */

        type: 'field',

        beforeLayout: function(width, height) {
            var me = this;
            return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
        },

        onLayout: function(width, height) {
            var me = this,
                owner = me.owner,
                labelStrategy = me.getLabelStrategy(),
                errorStrategy = me.getErrorStrategy(),
                isDefined = Ext.isDefined,
                isNumber = Ext.isNumber,
                lastSize, autoWidth, autoHeight, info, undef;

            lastSize = me.lastComponentSize || {};
            if (!isDefined(width)) {
                width = lastSize.width;
                if (width < 0) { //first pass lastComponentSize.width is -Infinity
                    width = undef;
                }
            }
            if (!isDefined(height)) {
                height = lastSize.height;
                if (height < 0) { //first pass lastComponentSize.height is -Infinity
                    height = undef;
                }
            }
            autoWidth = !isNumber(width);
            autoHeight = !isNumber(height);

            info = {
                autoWidth: autoWidth,
                autoHeight: autoHeight,
                width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
                height: height,
                setOuterWidth: false, //whether the outer el width should be set to the calculated width

                // insets for the bodyEl from each side of the component layout area
                insets: {
                    top: 0,
                    right: 0,
                    bottom: 0,
                    left: 0
                }
            };

            // NOTE the order of calculating insets and setting styles here is very important; we must first
            // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
            // on the vertical sizes due to wrapping, then calculate and set the vertical layout.

            // perform preparation on the label and error (setting css classes, qtips, etc.)
            labelStrategy.prepare(owner, info);
            errorStrategy.prepare(owner, info);

            // calculate the horizontal insets for the label and error
            labelStrategy.adjustHorizInsets(owner, info);
            errorStrategy.adjustHorizInsets(owner, info);

            // set horizontal styles for label and error based on the current insets
            labelStrategy.layoutHoriz(owner, info);
            errorStrategy.layoutHoriz(owner, info);

            // calculate the vertical insets for the label and error
            labelStrategy.adjustVertInsets(owner, info);
            errorStrategy.adjustVertInsets(owner, info);

            // set vertical styles for label and error based on the current insets
            labelStrategy.layoutVert(owner, info);
            errorStrategy.layoutVert(owner, info);

            // perform sizing of the elements based on the final dimensions and insets
            if (autoWidth && autoHeight) {
                // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
                me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
            } else {
                me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
            }
            me.sizeBody(info);

            me.activeError = owner.getActiveError();
        },

        onFocus: function(){
            this.getErrorStrategy().onFocus(this.owner);
        },

        /**
         * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
         */
        sizeBody: function(info) {
            var me = this,
                owner = me.owner,
                insets = info.insets,
                totalWidth = info.width,
                totalHeight = info.height,
                width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
                height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;

            // size the bodyEl
            me.setElementSize(owner.bodyEl, width, height);

            // size the bodyEl's inner contents if necessary
            me.sizeBodyContents(width, height);
        },

        /**
         * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
         * default, subclasses can override to handle their specific contents.
         * @param {Number} width The bodyEl width
         * @param {Number} height The bodyEl height
         */
        sizeBodyContents: Ext.emptyFn,

        /**
         * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
         * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
         */
        getLabelStrategy: function() {
            var me = this,
                strategies = me.labelStrategies,
                labelAlign = me.owner.labelAlign;
            return strategies[labelAlign] || strategies.base;
        },

        /**
         * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
         * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
         */
        getErrorStrategy: function() {
            var me = this,
                owner = me.owner,
                strategies = me.errorStrategies,
                msgTarget = owner.msgTarget;
            return !owner.preventMark && Ext.isString(msgTarget) ?
                    (strategies[msgTarget] || strategies.elementId) :
                    strategies.none;
        },

        /**
         * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
         * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
         */
        labelStrategies: (function() {
            var applyIf = Ext.applyIf,
                emptyFn = Ext.emptyFn,
                base = {
                    prepare: function(owner, info) {
                        var cls = owner.labelCls + '-' + owner.labelAlign,
                            labelEl = owner.labelEl;
                        if (labelEl && !labelEl.hasCls(cls)) {
                            labelEl.addCls(cls);
                        }
                    },
                    adjustHorizInsets: emptyFn,
                    adjustVertInsets: emptyFn,
                    layoutHoriz: emptyFn,
                    layoutVert: emptyFn
                },
                left = applyIf({
                    prepare: function(owner, info) {
                        base.prepare(owner, info);
                        // If auto width, add the label width to the body's natural width.
                        if (info.autoWidth) {
                            info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
                        }
                        // Must set outer width to prevent field from wrapping below floated label
                        info.setOuterWidth = true;
                    },
                    adjustHorizInsets: function(owner, info) {
                        if (owner.labelEl) {
                            info.insets.left += owner.labelWidth + owner.labelPad;
                        }
                    },
                    layoutHoriz: function(owner, info) {
                        // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
                        // setting the width style because it needs to account for the final calculated
                        // padding/border styles for the label. So we set the width programmatically here to
                        // normalize content-box sizing, while letting border-box browsers use the original
                        // width style.
                        var labelEl = owner.labelEl;
                        if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
                            labelEl.setWidth(owner.labelWidth);
                            owner.isLabelSized = true;
                        }
                    }
                }, base);

            return {
                base: base,

                /**
                 * Label displayed above the bodyEl
                 */
                top: applyIf({
                    adjustVertInsets: function(owner, info) {
                        var labelEl = owner.labelEl;
                        if (labelEl) {
                            info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
                                               labelEl.getFrameWidth('tb') + owner.labelPad;
                        }
                    }
                }, base),

                /**
                 * Label displayed to the left of the bodyEl
                 */
                left: left,

                /**
                 * Same as left, only difference is text-align in CSS
                 */
                right: left
            };
        })(),

        /**
         * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
         * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
         */
        errorStrategies: (function() {
            function setDisplayed(el, displayed) {
                var wasDisplayed = el.getStyle('display') !== 'none';
                if (displayed !== wasDisplayed) {
                    el.setDisplayed(displayed);
                }
            }

            function setStyle(el, name, value) {
                if (el.getStyle(name) !== value) {
                    el.setStyle(name, value);
                }
            }

            function showTip(owner) {
                var tip = Ext.layout.component.field.Field.tip,
                    target;

                if (tip && tip.isVisible()) {
                    target = tip.activeTarget;
                    if (target && target.el === owner.getActionEl().dom) {
                        tip.toFront(true);
                    }
                }
            }

            var applyIf = Ext.applyIf,
                emptyFn = Ext.emptyFn,
                base = {
                    prepare: function(owner) {
                        setDisplayed(owner.errorEl, false);
                    },
                    adjustHorizInsets: emptyFn,
                    adjustVertInsets: emptyFn,
                    layoutHoriz: emptyFn,
                    layoutVert: emptyFn,
                    onFocus: emptyFn
                };

            return {
                none: base,

                /**
                 * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
                 */
                side: applyIf({
                    prepare: function(owner) {
                        var errorEl = owner.errorEl;
                        errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
                        Ext.layout.component.field.Field.initTip();
                        errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
                        setDisplayed(errorEl, owner.hasActiveError());
                    },
                    adjustHorizInsets: function(owner, info) {
                        if (owner.autoFitErrors && owner.hasActiveError()) {
                            info.insets.right += owner.errorEl.getWidth();
                        }
                    },
                    layoutHoriz: function(owner, info) {
                        if (owner.hasActiveError()) {
                            setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
                        }
                    },
                    layoutVert: function(owner, info) {
                        if (owner.hasActiveError()) {
                            setStyle(owner.errorEl, 'top', info.insets.top + 'px');
                        }
                    },
                    onFocus: showTip
                }, base),

                /**
                 * Error message displayed underneath the bodyEl
                 */
                under: applyIf({
                    prepare: function(owner) {
                        var errorEl = owner.errorEl,
                            cls = Ext.baseCSSPrefix + 'form-invalid-under';
                        if (!errorEl.hasCls(cls)) {
                            errorEl.addCls(cls);
                        }
                        setDisplayed(errorEl, owner.hasActiveError());
                    },
                    adjustVertInsets: function(owner, info) {
                        if (owner.autoFitErrors) {
                            info.insets.bottom += owner.errorEl.getHeight();
                        }
                    },
                    layoutHoriz: function(owner, info) {
                        var errorEl = owner.errorEl,
                            insets = info.insets;

                        setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
                        setStyle(errorEl, 'marginLeft', insets.left + 'px');
                    }
                }, base),

                /**
                 * Error displayed as QuickTip on hover of the field container
                 */
                qtip: applyIf({
                    prepare: function(owner) {
                        setDisplayed(owner.errorEl, false);
                        Ext.layout.component.field.Field.initTip();
                        owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
                    },
                    onFocus: showTip
                }, base),

                /**
                 * Error displayed as title tip on hover of the field container
                 */
                title: applyIf({
                    prepare: function(owner) {
                        setDisplayed(owner.errorEl, false);
                        owner.el.dom.title = owner.getActiveError() || '';
                    }
                }, base),

                /**
                 * Error message displayed as content of an element with a given id elsewhere in the app
                 */
                elementId: applyIf({
                    prepare: function(owner) {
                        setDisplayed(owner.errorEl, false);
                        var targetEl = Ext.fly(owner.msgTarget);
                        if (targetEl) {
                            targetEl.dom.innerHTML = owner.getActiveError() || '';
                            targetEl.setDisplayed(owner.hasActiveError());
                        }
                    }
                }, base)
            };
        })(),

        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.
             */
            initTip: function() {
                var tip = this.tip;
                if (!tip) {
                    tip = this.tip = Ext.create('Ext.tip.QuickTip', {
                        baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
                        renderTo: Ext.getBody()
                    });
                    tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
                }
            },

            /**
             * Destroy the error tip instance.
             */
            destroyTip: function() {
                var tip = this.tip;
                if (tip) {
                    tip.destroy();
                    delete this.tip;
                }
            }
        }

    });