/** * 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 */ extend: 'Ext.layout.component.Auto', alias: 'layout.field', uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics', 'Ext.util.CSS'], /* End Definitions */ type: 'field', beginLayout: function(ownerContext) { var me = this, owner = me.owner, widthModel = ownerContext.widthModel, width; me.callParent(arguments); ownerContext.labelStrategy = me.getLabelStrategy(); ownerContext.errorStrategy = me.getErrorStrategy(); ownerContext.labelContext = ownerContext.getEl('labelEl'); ownerContext.bodyCellContext = ownerContext.getEl('bodyEl'); ownerContext.inputContext = ownerContext.getEl('inputEl'); ownerContext.errorContext = ownerContext.getEl('errorEl'); ownerContext.inputRow = ownerContext.getEl('inputRow'); // width:100% on an element inside a table in IE6/7 "strict" sizes the content box. // store the input element's border and padding info so that subclasses can take it into consideration if needed if ((Ext.isIE6 || Ext.isIE7) && Ext.isStrict && ownerContext.inputContext) { me.ieInputWidthAdjustment = ownerContext.inputContext.getPaddingInfo().width + ownerContext.inputContext.getBorderInfo().width; } // perform preparation on the label and error (setting css classes, qtips, etc.) ownerContext.labelStrategy.prepare(ownerContext, owner); ownerContext.errorStrategy.prepare(ownerContext, owner); // Body cell must stretch to use up available width unless the field is auto width if (widthModel.shrinkWrap) { // When the width needs to be auto, table-layout cannot be fixed me.beginLayoutShrinkWrap(ownerContext); } else if (widthModel.natural) { // When a size specified, natural becomes fixed width if (typeof owner.size == 'number') { me.beginLayoutFixed(ownerContext, (width = owner.size * 6.5 + 20), 'px'); } // Otherwise it is the same as shrinkWrap else { me.beginLayoutShrinkWrap(ownerContext); } ownerContext.setWidth(width, false); } else { me.beginLayoutFixed(ownerContext, '100', '%'); } }, beginLayoutFixed: function (ownerContext, width, suffix) { var owner = ownerContext.target; owner.el.setStyle('table-layout', 'fixed'); owner.bodyEl.setStyle('width', width + suffix); }, beginLayoutShrinkWrap: function (ownerContext) { var owner = ownerContext.target; if (owner.inputEl && owner.inputEl.dom) { owner.inputEl.dom.removeAttribute('size'); } owner.el.setStyle('table-layout', 'auto'); owner.bodyEl.setStyle('width', ''); }, finishedLayout: function(ownerContext){ var owner = this.owner; this.callParent(arguments); ownerContext.labelStrategy.finishedLayout(ownerContext, owner); ownerContext.errorStrategy.finishedLayout(ownerContext, owner); }, calculateOwnerHeightFromContentHeight: function(ownerContext, contentHeight) { return contentHeight; }, measureContentHeight: function (ownerContext) { return ownerContext.el.getHeight(); }, measureContentWidth: function (ownerContext) { return ownerContext.el.getWidth(); }, measureLabelErrorHeight: function (ownerContext) { return ownerContext.labelStrategy.getHeight(ownerContext) + ownerContext.errorStrategy.getHeight(ownerContext); }, onFocus: function() { this.getErrorStrategy().onFocus(this.owner); }, /** * 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 base = { prepare: function(ownerContext, owner) { var cls = owner.labelCls + '-' + owner.labelAlign, labelEl = owner.labelEl; if (labelEl) { labelEl.addCls(cls); } }, getHeight: function () { return 0; }, finishedLayout: Ext.emptyFn }; return { base: base, /** * Label displayed above the bodyEl */ top: Ext.applyIf({ prepare: function(ownerContext, owner){ base.prepare(ownerContext, owner); var labelEl = owner.labelEl; ownerContext.hasHiddenLabel = labelEl && !owner.hideEmptyLabel && !owner.getFieldLabel(); if (ownerContext.hasHiddenLabel) { labelEl.dom.innerHTML = ' '; } }, getHeight: function (ownerContext) { var labelContext = ownerContext.labelContext, height = labelContext.getProp('height'), hasEmptyLabel = ownerContext.hasHiddenLabel; if (height === undefined || hasEmptyLabel) { height = labelContext.el.getHeight() + labelContext.getMarginInfo().height; if (hasEmptyLabel) { // only force the height if we'll be clearing it later labelContext.setHeight(height); } } return height; }, finishedLayout: function(ownerContext, owner) { if (ownerContext.hasHiddenLabel) { owner.labelEl.dom.innerHTML = ''; } } }, base), /** * Label displayed to the left of the bodyEl */ left: base, /** * Same as left, only difference is text-align in CSS */ right: base }; }()), /** * 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 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, iconCls = Ext.baseCSSPrefix + 'form-invalid-icon', iconWidth, base = { prepare: function(ownerContext, owner) { var el = owner.errorEl; if (el) { el.setDisplayed(false); } }, getHeight: function () { return 0; }, onFocus: emptyFn, finishedLayout: emptyFn }; return { none: base, /** * Error displayed as icon (with QuickTip on hover) to right of the bodyEl */ side: applyIf({ prepare: function(ownerContext, owner) { var errorEl = owner.errorEl, tempEl; // Capture error icon width once if (!iconWidth) { iconWidth = (tempEl = Ext.getBody().createChild({style: 'position:absolute', cls: iconCls})).getWidth(); tempEl.remove(); } errorEl.addCls(iconCls); errorEl.set({'data-errorqtip': owner.getActiveError() || ''}); errorEl.setDisplayed(owner.hasActiveError()); owner.bodyEl.dom.colSpan = owner.getBodyColspan(); // TODO: defer the tip call until after the layout to avoid immediate DOM reads now Ext.layout.component.field.Field.initTip(); }, onFocus: showTip }, base), /** * Error message displayed underneath the bodyEl */ under: applyIf({ prepare: function(ownerContext, owner) { var errorEl = owner.errorEl, cls = Ext.baseCSSPrefix + 'form-invalid-under'; errorEl.addCls(cls); errorEl.setDisplayed(owner.hasActiveError()); if (owner.labelAlign == 'left') { // hide the under label placeholder td errorEl.prev().setDisplayed(owner.hasVisibleLabel() ? 'block' : 'none'); } }, getHeight: function (ownerContext) { var height = 0, errorContext, props; if (ownerContext.target.hasActiveError()) { errorContext = ownerContext.errorContext; props = errorContext.props; height = props.height; if (height === undefined) { props.height = height = errorContext.el.getHeight(); } } return height; } }, base), /** * Error displayed as QuickTip on hover of the field container */ qtip: applyIf({ prepare: function(ownerContext, owner) { Ext.layout.component.field.Field.initTip(); owner.getActionEl().set({'data-errorqtip': owner.getActiveError() || ''}); }, onFocus: showTip }, base), /** * Error displayed as title tip on hover of the field container */ title: applyIf({ prepare: function(ownerContext, owner) { owner.el.set({'title': owner.getActiveError() || ''}); } }, base), /** * Error message displayed as content of an element with a given id elsewhere in the app */ elementId: applyIf({ prepare: function(ownerContext, owner) { 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. * @static */ initTip: function() { var tip = this.tip; if (!tip) { tip = this.tip = Ext.create('Ext.tip.QuickTip', { baseCls: Ext.baseCSSPrefix + 'form-invalid-tip' }); tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig); } }, /** * Destroy the error tip instance. * @static */ destroyTip: function() { var tip = this.tip; if (tip) { tip.destroy(); delete this.tip; } } } });