3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.layout.component.field.Field
17 * @extends Ext.layout.component.Component
18 * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
19 * the form control, label, and error message treatment.
22 Ext.define('Ext.layout.component.field.Field', {
24 /* Begin Definitions */
26 alias: ['layout.field'],
28 extend: 'Ext.layout.component.Component',
30 uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
36 beforeLayout: function(width, height) {
38 return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
41 onLayout: function(width, height) {
44 labelStrategy = me.getLabelStrategy(),
45 errorStrategy = me.getErrorStrategy(),
46 isDefined = Ext.isDefined,
47 isNumber = Ext.isNumber,
48 lastSize, autoWidth, autoHeight, info, undef;
50 lastSize = me.lastComponentSize || {};
51 if (!isDefined(width)) {
52 width = lastSize.width;
53 if (width < 0) { //first pass lastComponentSize.width is -Infinity
57 if (!isDefined(height)) {
58 height = lastSize.height;
59 if (height < 0) { //first pass lastComponentSize.height is -Infinity
63 autoWidth = !isNumber(width);
64 autoHeight = !isNumber(height);
68 autoHeight: autoHeight,
69 width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
71 setOuterWidth: false, //whether the outer el width should be set to the calculated width
73 // insets for the bodyEl from each side of the component layout area
82 // NOTE the order of calculating insets and setting styles here is very important; we must first
83 // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
84 // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
86 // perform preparation on the label and error (setting css classes, qtips, etc.)
87 labelStrategy.prepare(owner, info);
88 errorStrategy.prepare(owner, info);
90 // calculate the horizontal insets for the label and error
91 labelStrategy.adjustHorizInsets(owner, info);
92 errorStrategy.adjustHorizInsets(owner, info);
94 // set horizontal styles for label and error based on the current insets
95 labelStrategy.layoutHoriz(owner, info);
96 errorStrategy.layoutHoriz(owner, info);
98 // calculate the vertical insets for the label and error
99 labelStrategy.adjustVertInsets(owner, info);
100 errorStrategy.adjustVertInsets(owner, info);
102 // set vertical styles for label and error based on the current insets
103 labelStrategy.layoutVert(owner, info);
104 errorStrategy.layoutVert(owner, info);
106 // perform sizing of the elements based on the final dimensions and insets
107 if (autoWidth && autoHeight) {
108 // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
109 me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
111 me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
115 me.activeError = owner.getActiveError();
120 * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
122 sizeBody: function(info) {
125 insets = info.insets,
126 totalWidth = info.width,
127 totalHeight = info.height,
128 width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
129 height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
132 me.setElementSize(owner.bodyEl, width, height);
134 // size the bodyEl's inner contents if necessary
135 me.sizeBodyContents(width, height);
139 * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
140 * default, subclasses can override to handle their specific contents.
141 * @param {Number} width The bodyEl width
142 * @param {Number} height The bodyEl height
144 sizeBodyContents: Ext.emptyFn,
148 * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
149 * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
151 getLabelStrategy: function() {
153 strategies = me.labelStrategies,
154 labelAlign = me.owner.labelAlign;
155 return strategies[labelAlign] || strategies.base;
159 * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
160 * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
162 getErrorStrategy: function() {
165 strategies = me.errorStrategies,
166 msgTarget = owner.msgTarget;
167 return !owner.preventMark && Ext.isString(msgTarget) ?
168 (strategies[msgTarget] || strategies.elementId) :
175 * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
176 * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
178 labelStrategies: (function() {
179 var applyIf = Ext.applyIf,
180 emptyFn = Ext.emptyFn,
182 prepare: function(owner, info) {
183 var cls = owner.labelCls + '-' + owner.labelAlign,
184 labelEl = owner.labelEl;
185 if (labelEl && !labelEl.hasCls(cls)) {
189 adjustHorizInsets: emptyFn,
190 adjustVertInsets: emptyFn,
191 layoutHoriz: emptyFn,
195 prepare: function(owner, info) {
196 base.prepare(owner, info);
197 // If auto width, add the label width to the body's natural width.
198 if (info.autoWidth) {
199 info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
201 // Must set outer width to prevent field from wrapping below floated label
202 info.setOuterWidth = true;
204 adjustHorizInsets: function(owner, info) {
206 info.insets.left += owner.labelWidth + owner.labelPad;
209 layoutHoriz: function(owner, info) {
210 // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
211 // setting the width style because it needs to account for the final calculated
212 // padding/border styles for the label. So we set the width programmatically here to
213 // normalize content-box sizing, while letting border-box browsers use the original
215 var labelEl = owner.labelEl;
216 if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
217 labelEl.setWidth(owner.labelWidth);
218 owner.isLabelSized = true;
228 * Label displayed above the bodyEl
231 adjustVertInsets: function(owner, info) {
232 var labelEl = owner.labelEl;
234 info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
235 labelEl.getFrameWidth('tb') + owner.labelPad;
241 * Label displayed to the left of the bodyEl
246 * Same as left, only difference is text-align in CSS
255 * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
256 * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
258 errorStrategies: (function() {
259 function setDisplayed(el, displayed) {
260 var wasDisplayed = el.getStyle('display') !== 'none';
261 if (displayed !== wasDisplayed) {
262 el.setDisplayed(displayed);
266 function setStyle(el, name, value) {
267 if (el.getStyle(name) !== value) {
268 el.setStyle(name, value);
272 var applyIf = Ext.applyIf,
273 emptyFn = Ext.emptyFn,
275 prepare: function(owner) {
276 setDisplayed(owner.errorEl, false);
278 adjustHorizInsets: emptyFn,
279 adjustVertInsets: emptyFn,
280 layoutHoriz: emptyFn,
288 * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
291 prepare: function(owner) {
292 var errorEl = owner.errorEl;
293 errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
294 Ext.layout.component.field.Field.initTip();
295 errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
296 setDisplayed(errorEl, owner.hasActiveError());
298 adjustHorizInsets: function(owner, info) {
299 if (owner.autoFitErrors && owner.hasActiveError()) {
300 info.insets.right += owner.errorEl.getWidth();
303 layoutHoriz: function(owner, info) {
304 if (owner.hasActiveError()) {
305 setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
308 layoutVert: function(owner, info) {
309 if (owner.hasActiveError()) {
310 setStyle(owner.errorEl, 'top', info.insets.top + 'px');
316 * Error message displayed underneath the bodyEl
319 prepare: function(owner) {
320 var errorEl = owner.errorEl,
321 cls = Ext.baseCSSPrefix + 'form-invalid-under';
322 if (!errorEl.hasCls(cls)) {
325 setDisplayed(errorEl, owner.hasActiveError());
327 adjustVertInsets: function(owner, info) {
328 if (owner.autoFitErrors) {
329 info.insets.bottom += owner.errorEl.getHeight();
332 layoutHoriz: function(owner, info) {
333 var errorEl = owner.errorEl,
334 insets = info.insets;
336 setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
337 setStyle(errorEl, 'marginLeft', insets.left + 'px');
342 * Error displayed as QuickTip on hover of the field container
345 prepare: function(owner) {
346 setDisplayed(owner.errorEl, false);
347 Ext.layout.component.field.Field.initTip();
348 owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
353 * Error displayed as title tip on hover of the field container
356 prepare: function(owner) {
357 setDisplayed(owner.errorEl, false);
358 owner.el.dom.title = owner.getActiveError() || '';
363 * Error message displayed as content of an element with a given id elsewhere in the app
366 prepare: function(owner) {
367 setDisplayed(owner.errorEl, false);
368 var targetEl = Ext.fly(owner.msgTarget);
370 targetEl.dom.innerHTML = owner.getActiveError() || '';
371 targetEl.setDisplayed(owner.hasActiveError());
380 * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
381 * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
383 initTip: function() {
386 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
387 baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
388 renderTo: Ext.getBody()
390 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
395 * Destroy the error tip instance.
397 destroyTip: function() {