X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/layout/component/field/Field.js diff --git a/src/layout/component/field/Field.js b/src/layout/component/field/Field.js new file mode 100644 index 00000000..b915baad --- /dev/null +++ b/src/layout/component/field/Field.js @@ -0,0 +1,389 @@ +/** + * @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, + + // 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.width, info.height); + } else { + me.setTargetSize(info.width, info.height); + } + me.sizeBody(info); + + me.activeError = owner.getActiveError(); + }, + + + /** + * 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.field.Field#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.field.Field#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.field.Field#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); + } + }, + 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.field.Field#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); + } + } + + var applyIf = Ext.applyIf, + emptyFn = Ext.emptyFn, + base = { + prepare: function(owner) { + setDisplayed(owner.errorEl, false); + }, + adjustHorizInsets: emptyFn, + adjustVertInsets: emptyFn, + layoutHoriz: emptyFn, + layoutVert: 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'); + } + } + }, 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() || ''); + } + }, 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; + } + } + } + +});