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();
119 this.getErrorStrategy().onFocus(this.owner);
124 * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
126 sizeBody: function(info) {
129 insets = info.insets,
130 totalWidth = info.width,
131 totalHeight = info.height,
132 width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
133 height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
136 me.setElementSize(owner.bodyEl, width, height);
138 // size the bodyEl's inner contents if necessary
139 me.sizeBodyContents(width, height);
143 * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
144 * default, subclasses can override to handle their specific contents.
145 * @param {Number} width The bodyEl width
146 * @param {Number} height The bodyEl height
148 sizeBodyContents: Ext.emptyFn,
152 * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
153 * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
155 getLabelStrategy: function() {
157 strategies = me.labelStrategies,
158 labelAlign = me.owner.labelAlign;
159 return strategies[labelAlign] || strategies.base;
163 * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
164 * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
166 getErrorStrategy: function() {
169 strategies = me.errorStrategies,
170 msgTarget = owner.msgTarget;
171 return !owner.preventMark && Ext.isString(msgTarget) ?
172 (strategies[msgTarget] || strategies.elementId) :
179 * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
180 * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
182 labelStrategies: (function() {
183 var applyIf = Ext.applyIf,
184 emptyFn = Ext.emptyFn,
186 prepare: function(owner, info) {
187 var cls = owner.labelCls + '-' + owner.labelAlign,
188 labelEl = owner.labelEl;
189 if (labelEl && !labelEl.hasCls(cls)) {
193 adjustHorizInsets: emptyFn,
194 adjustVertInsets: emptyFn,
195 layoutHoriz: emptyFn,
199 prepare: function(owner, info) {
200 base.prepare(owner, info);
201 // If auto width, add the label width to the body's natural width.
202 if (info.autoWidth) {
203 info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
205 // Must set outer width to prevent field from wrapping below floated label
206 info.setOuterWidth = true;
208 adjustHorizInsets: function(owner, info) {
210 info.insets.left += owner.labelWidth + owner.labelPad;
213 layoutHoriz: function(owner, info) {
214 // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
215 // setting the width style because it needs to account for the final calculated
216 // padding/border styles for the label. So we set the width programmatically here to
217 // normalize content-box sizing, while letting border-box browsers use the original
219 var labelEl = owner.labelEl;
220 if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
221 labelEl.setWidth(owner.labelWidth);
222 owner.isLabelSized = true;
232 * Label displayed above the bodyEl
235 adjustVertInsets: function(owner, info) {
236 var labelEl = owner.labelEl;
238 info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
239 labelEl.getFrameWidth('tb') + owner.labelPad;
245 * Label displayed to the left of the bodyEl
250 * Same as left, only difference is text-align in CSS
259 * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
260 * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
262 errorStrategies: (function() {
263 function setDisplayed(el, displayed) {
264 var wasDisplayed = el.getStyle('display') !== 'none';
265 if (displayed !== wasDisplayed) {
266 el.setDisplayed(displayed);
270 function setStyle(el, name, value) {
271 if (el.getStyle(name) !== value) {
272 el.setStyle(name, value);
276 function showTip(owner) {
277 var tip = Ext.layout.component.field.Field.tip,
280 if (tip && tip.isVisible()) {
281 target = tip.activeTarget;
282 if (target && target.el === owner.getActionEl().dom) {
288 var applyIf = Ext.applyIf,
289 emptyFn = Ext.emptyFn,
291 prepare: function(owner) {
292 setDisplayed(owner.errorEl, false);
294 adjustHorizInsets: emptyFn,
295 adjustVertInsets: emptyFn,
296 layoutHoriz: emptyFn,
305 * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
308 prepare: function(owner) {
309 var errorEl = owner.errorEl;
310 errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
311 Ext.layout.component.field.Field.initTip();
312 errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
313 setDisplayed(errorEl, owner.hasActiveError());
315 adjustHorizInsets: function(owner, info) {
316 if (owner.autoFitErrors && owner.hasActiveError()) {
317 info.insets.right += owner.errorEl.getWidth();
320 layoutHoriz: function(owner, info) {
321 if (owner.hasActiveError()) {
322 setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
325 layoutVert: function(owner, info) {
326 if (owner.hasActiveError()) {
327 setStyle(owner.errorEl, 'top', info.insets.top + 'px');
334 * Error message displayed underneath the bodyEl
337 prepare: function(owner) {
338 var errorEl = owner.errorEl,
339 cls = Ext.baseCSSPrefix + 'form-invalid-under';
340 if (!errorEl.hasCls(cls)) {
343 setDisplayed(errorEl, owner.hasActiveError());
345 adjustVertInsets: function(owner, info) {
346 if (owner.autoFitErrors) {
347 info.insets.bottom += owner.errorEl.getHeight();
350 layoutHoriz: function(owner, info) {
351 var errorEl = owner.errorEl,
352 insets = info.insets;
354 setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
355 setStyle(errorEl, 'marginLeft', insets.left + 'px');
360 * Error displayed as QuickTip on hover of the field container
363 prepare: function(owner) {
364 setDisplayed(owner.errorEl, false);
365 Ext.layout.component.field.Field.initTip();
366 owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
372 * Error displayed as title tip on hover of the field container
375 prepare: function(owner) {
376 setDisplayed(owner.errorEl, false);
377 owner.el.dom.title = owner.getActiveError() || '';
382 * Error message displayed as content of an element with a given id elsewhere in the app
385 prepare: function(owner) {
386 setDisplayed(owner.errorEl, false);
387 var targetEl = Ext.fly(owner.msgTarget);
389 targetEl.dom.innerHTML = owner.getActiveError() || '';
390 targetEl.setDisplayed(owner.hasActiveError());
399 * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
400 * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
402 initTip: function() {
405 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
406 baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
407 renderTo: Ext.getBody()
409 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
414 * Destroy the error tip instance.
416 destroyTip: function() {