2 * @class Ext.layout.component.field.Field
3 * @extends Ext.layout.component.Component
4 * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
5 * the form control, label, and error message treatment.
8 Ext.define('Ext.layout.component.field.Field', {
10 /* Begin Definitions */
12 alias: ['layout.field'],
14 extend: 'Ext.layout.component.Component',
16 uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
22 beforeLayout: function(width, height) {
24 return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
27 onLayout: function(width, height) {
30 labelStrategy = me.getLabelStrategy(),
31 errorStrategy = me.getErrorStrategy(),
32 isDefined = Ext.isDefined,
33 isNumber = Ext.isNumber,
34 lastSize, autoWidth, autoHeight, info, undef;
36 lastSize = me.lastComponentSize || {};
37 if (!isDefined(width)) {
38 width = lastSize.width;
39 if (width < 0) { //first pass lastComponentSize.width is -Infinity
43 if (!isDefined(height)) {
44 height = lastSize.height;
45 if (height < 0) { //first pass lastComponentSize.height is -Infinity
49 autoWidth = !isNumber(width);
50 autoHeight = !isNumber(height);
54 autoHeight: autoHeight,
55 width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
57 setOuterWidth: false, //whether the outer el width should be set to the calculated width
59 // insets for the bodyEl from each side of the component layout area
68 // NOTE the order of calculating insets and setting styles here is very important; we must first
69 // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
70 // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
72 // perform preparation on the label and error (setting css classes, qtips, etc.)
73 labelStrategy.prepare(owner, info);
74 errorStrategy.prepare(owner, info);
76 // calculate the horizontal insets for the label and error
77 labelStrategy.adjustHorizInsets(owner, info);
78 errorStrategy.adjustHorizInsets(owner, info);
80 // set horizontal styles for label and error based on the current insets
81 labelStrategy.layoutHoriz(owner, info);
82 errorStrategy.layoutHoriz(owner, info);
84 // calculate the vertical insets for the label and error
85 labelStrategy.adjustVertInsets(owner, info);
86 errorStrategy.adjustVertInsets(owner, info);
88 // set vertical styles for label and error based on the current insets
89 labelStrategy.layoutVert(owner, info);
90 errorStrategy.layoutVert(owner, info);
92 // perform sizing of the elements based on the final dimensions and insets
93 if (autoWidth && autoHeight) {
94 // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
95 me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
97 me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
101 me.activeError = owner.getActiveError();
106 * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
108 sizeBody: function(info) {
111 insets = info.insets,
112 totalWidth = info.width,
113 totalHeight = info.height,
114 width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
115 height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
118 me.setElementSize(owner.bodyEl, width, height);
120 // size the bodyEl's inner contents if necessary
121 me.sizeBodyContents(width, height);
125 * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
126 * default, subclasses can override to handle their specific contents.
127 * @param {Number} width The bodyEl width
128 * @param {Number} height The bodyEl height
130 sizeBodyContents: Ext.emptyFn,
134 * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
135 * that is appropriate for the field's {@link Ext.form.field.Field#labelAlign labelAlign} config.
137 getLabelStrategy: function() {
139 strategies = me.labelStrategies,
140 labelAlign = me.owner.labelAlign;
141 return strategies[labelAlign] || strategies.base;
145 * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
146 * that is appropriate for the field's {@link Ext.form.field.Field#msgTarget msgTarget} config.
148 getErrorStrategy: function() {
151 strategies = me.errorStrategies,
152 msgTarget = owner.msgTarget;
153 return !owner.preventMark && Ext.isString(msgTarget) ?
154 (strategies[msgTarget] || strategies.elementId) :
161 * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
162 * An appropriate one will be chosen based on the owner field's {@link Ext.form.field.Field#labelAlign} config.
164 labelStrategies: (function() {
165 var applyIf = Ext.applyIf,
166 emptyFn = Ext.emptyFn,
168 prepare: function(owner, info) {
169 var cls = owner.labelCls + '-' + owner.labelAlign,
170 labelEl = owner.labelEl;
171 if (labelEl && !labelEl.hasCls(cls)) {
175 adjustHorizInsets: emptyFn,
176 adjustVertInsets: emptyFn,
177 layoutHoriz: emptyFn,
181 prepare: function(owner, info) {
182 base.prepare(owner, info);
183 // If auto width, add the label width to the body's natural width.
184 if (info.autoWidth) {
185 info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
187 // Must set outer width to prevent field from wrapping below floated label
188 info.setOuterWidth = true;
190 adjustHorizInsets: function(owner, info) {
192 info.insets.left += owner.labelWidth + owner.labelPad;
195 layoutHoriz: function(owner, info) {
196 // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
197 // setting the width style because it needs to account for the final calculated
198 // padding/border styles for the label. So we set the width programmatically here to
199 // normalize content-box sizing, while letting border-box browsers use the original
201 var labelEl = owner.labelEl;
202 if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
203 labelEl.setWidth(owner.labelWidth);
204 owner.isLabelSized = true;
214 * Label displayed above the bodyEl
217 adjustVertInsets: function(owner, info) {
218 var labelEl = owner.labelEl;
220 info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
221 labelEl.getFrameWidth('tb') + owner.labelPad;
227 * Label displayed to the left of the bodyEl
232 * Same as left, only difference is text-align in CSS
241 * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
242 * An appropriate one will be chosen based on the owner field's {@link Ext.form.field.Field#msgTarget} config.
244 errorStrategies: (function() {
245 function setDisplayed(el, displayed) {
246 var wasDisplayed = el.getStyle('display') !== 'none';
247 if (displayed !== wasDisplayed) {
248 el.setDisplayed(displayed);
252 function setStyle(el, name, value) {
253 if (el.getStyle(name) !== value) {
254 el.setStyle(name, value);
258 var applyIf = Ext.applyIf,
259 emptyFn = Ext.emptyFn,
261 prepare: function(owner) {
262 setDisplayed(owner.errorEl, false);
264 adjustHorizInsets: emptyFn,
265 adjustVertInsets: emptyFn,
266 layoutHoriz: emptyFn,
274 * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
277 prepare: function(owner) {
278 var errorEl = owner.errorEl;
279 errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
280 Ext.layout.component.field.Field.initTip();
281 errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
282 setDisplayed(errorEl, owner.hasActiveError());
284 adjustHorizInsets: function(owner, info) {
285 if (owner.autoFitErrors && owner.hasActiveError()) {
286 info.insets.right += owner.errorEl.getWidth();
289 layoutHoriz: function(owner, info) {
290 if (owner.hasActiveError()) {
291 setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
294 layoutVert: function(owner, info) {
295 if (owner.hasActiveError()) {
296 setStyle(owner.errorEl, 'top', info.insets.top + 'px');
302 * Error message displayed underneath the bodyEl
305 prepare: function(owner) {
306 var errorEl = owner.errorEl,
307 cls = Ext.baseCSSPrefix + 'form-invalid-under';
308 if (!errorEl.hasCls(cls)) {
311 setDisplayed(errorEl, owner.hasActiveError());
313 adjustVertInsets: function(owner, info) {
314 if (owner.autoFitErrors) {
315 info.insets.bottom += owner.errorEl.getHeight();
318 layoutHoriz: function(owner, info) {
319 var errorEl = owner.errorEl,
320 insets = info.insets;
322 setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
323 setStyle(errorEl, 'marginLeft', insets.left + 'px');
328 * Error displayed as QuickTip on hover of the field container
331 prepare: function(owner) {
332 setDisplayed(owner.errorEl, false);
333 Ext.layout.component.field.Field.initTip();
334 owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
339 * Error displayed as title tip on hover of the field container
342 prepare: function(owner) {
343 setDisplayed(owner.errorEl, false);
344 owner.el.dom.title = owner.getActiveError() || '';
349 * Error message displayed as content of an element with a given id elsewhere in the app
352 prepare: function(owner) {
353 setDisplayed(owner.errorEl, false);
354 var targetEl = Ext.fly(owner.msgTarget);
356 targetEl.dom.innerHTML = owner.getActiveError() || '';
357 targetEl.setDisplayed(owner.hasActiveError());
366 * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
367 * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
369 initTip: function() {
372 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
373 baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
374 renderTo: Ext.getBody()
376 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
381 * Destroy the error tip instance.
383 destroyTip: function() {