]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/form/Labelable.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / form / Labelable.js
CommitLineData
6527f429
DM
1/**\r
2 * A mixin which allows a component to be configured and decorated with a label and/or error message as is\r
3 * common for form fields. This is used by e.g. Ext.form.field.Base and Ext.form.FieldContainer\r
4 * to let them be managed by the Field layout.\r
5 *\r
6 * NOTE: This mixin is mainly for internal library use and most users should not need to use it directly. It\r
7 * is more likely you will want to use one of the component classes that import this mixin, such as\r
8 * Ext.form.field.Base or Ext.form.FieldContainer.\r
9 *\r
10 * Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any\r
11 * logic or state related to values or validation; that is handled by the related Ext.form.field.Field\r
12 * mixin. These two mixins may be used separately (for example Ext.form.FieldContainer is Labelable but not a\r
13 * Field), or in combination (for example Ext.form.field.Base implements both and has logic for connecting the\r
14 * two.)\r
15 *\r
16 * Component classes which use this mixin should use the Field layout\r
17 * or a derivation thereof to properly size and position the label and message according to the component config.\r
18 * They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets\r
19 * set up correctly.\r
20 */\r
21Ext.define("Ext.form.Labelable", {\r
22 extend: 'Ext.Mixin',\r
23\r
24 requires: [\r
25 'Ext.XTemplate',\r
26 'Ext.overrides.dom.Element'\r
27 ],\r
28\r
29 isLabelable: true,\r
30\r
31 mixinConfig: {\r
32 id: 'labelable',\r
33\r
34 on: {\r
35 beforeRender: 'beforeLabelRender',\r
36 onRender: 'onLabelRender'\r
37 }\r
38 },\r
39\r
40 config: {\r
41 childEls: [\r
42 /**\r
43 * @property {Ext.dom.Element} labelEl\r
44 * The label Element for this component. Only available after the component has been rendered.\r
45 */\r
46 'labelEl',\r
47\r
48 /**\r
49 * @property {Ext.dom.Element} bodyEl\r
50 * The div Element wrapping the component's contents. Only available after the component has been rendered.\r
51 */\r
52 'bodyEl',\r
53\r
54 /**\r
55 * @property {Ext.dom.Element} errorEl\r
56 * The div Element that will contain the component's error message(s). Note that depending on the configured\r
57 * {@link #msgTarget}, this element may be hidden in favor of some other form of presentation, but will always\r
58 * be present in the DOM for use by assistive technologies.\r
59 */\r
60 'errorEl',\r
61\r
62 'errorWrapEl',\r
63 'ariaErrorEl'\r
64 ]\r
65 },\r
66\r
67 /**\r
68 * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl\r
69 * The rendering template for the field decorations. Component classes using this mixin\r
70 * should include logic to use this as their {@link Ext.Component#renderTpl renderTpl},\r
71 * and implement the {@link #getSubTplMarkup} method to generate the field body content.\r
72 * @private\r
73 */\r
74 labelableRenderTpl: [\r
75 '{beforeLabelTpl}',\r
76 '<label id="{id}-labelEl" data-ref="labelEl" class="{labelCls} {labelCls}-{ui} {labelClsExtra} ',\r
77 '{childElCls} {unselectableCls}" style="{labelStyle}"<tpl if="inputId">',\r
78 ' for="{inputId}"</tpl> {labelAttrTpl}>',\r
79 '<span class="{labelInnerCls} {labelInnerCls}-{ui}" style="{labelInnerStyle}">',\r
80 '{beforeLabelTextTpl}',\r
81 '<tpl if="fieldLabel">{fieldLabel}',\r
82 '<tpl if="labelSeparator">{labelSeparator}</tpl>',\r
83 '</tpl>',\r
84 '{afterLabelTextTpl}',\r
85 '</span>',\r
86 '</label>',\r
87 '{afterLabelTpl}',\r
88 '<div id="{id}-bodyEl" data-ref="bodyEl" role="presentation"',\r
89 ' class="{baseBodyCls} {baseBodyCls}-{ui}<tpl if="fieldBodyCls">',\r
90 ' {fieldBodyCls} {fieldBodyCls}-{ui}</tpl> {growCls} {extraFieldBodyCls}"',\r
91 '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',\r
92 '{beforeBodyEl}',\r
93 '{beforeSubTpl}',\r
94 '{[values.$comp.getSubTplMarkup(values)]}',\r
95 '{afterSubTpl}',\r
96 '{afterBodyEl}',\r
97 // Unlike errorEl below ariaErrorEl is always rendered but is clipped out of existence\r
98 '<div id="{id}-ariaErrorEl" data-ref="ariaErrorEl" role="alert" aria-live="polite"',\r
99 ' class="' + Ext.baseCSSPrefix + 'hidden-clip">',\r
100 '</div>',\r
101 '</div>',\r
102 '<tpl if="renderError">',\r
103 '<div id="{id}-errorWrapEl" data-ref="errorWrapEl" class="{errorWrapCls} {errorWrapCls}-{ui}',\r
104 ' {errorWrapExtraCls}" style="{errorWrapStyle}">',\r
105 '<div role="presentation" id="{id}-errorEl" data-ref="errorEl" ',\r
106 'class="{errorMsgCls} {invalidMsgCls} {invalidMsgCls}-{ui}" ',\r
107 'data-anchorTarget="{tipAnchorTarget}">',\r
108 '</div>',\r
109 '</div>',\r
110 '</tpl>',\r
111 {\r
112 disableFormats: true\r
113 }\r
114 ],\r
115\r
116 /**\r
117 * @cfg {String/String[]/Ext.XTemplate} activeErrorsTpl\r
118 * The template used to format the Array of error messages passed to {@link #setActiveErrors} into a single HTML\r
119 * string. if the {@link #msgTarget} is title, it defaults to a list separated by new lines. Otherwise, it\r
120 * renders each message as an item in an unordered list.\r
121 */\r
122 activeErrorsTpl: undefined,\r
123\r
124 htmlActiveErrorsTpl: [\r
125 '<tpl if="errors && errors.length">',\r
126 '<ul class="{listCls}">',\r
127 '<tpl for="errors"><li>{.}</li></tpl>',\r
128 '</ul>',\r
129 '</tpl>'\r
130 ],\r
131\r
132 plaintextActiveErrorsTpl: [\r
133 '<tpl if="errors && errors.length">',\r
134 '<tpl for="errors"><tpl if="xindex &gt; 1">\n</tpl>{.}</tpl>',\r
135 '</tpl>'\r
136 ],\r
137\r
138 /**\r
139 * @property {Boolean} isFieldLabelable\r
140 * Flag denoting that this object is labelable as a field. Always true.\r
141 */\r
142 isFieldLabelable: true,\r
143\r
144 /**\r
145 * @cfg {String} formItemCls\r
146 * A CSS class to be applied to the outermost element to denote that it is participating in the form field layout.\r
147 */\r
148 formItemCls: Ext.baseCSSPrefix + 'form-item',\r
149\r
150 /**\r
151 * @cfg {String} labelCls\r
152 * The CSS class to be applied to the label element. This (single) CSS class is used to formulate the renderSelector\r
153 * and drives the field layout where it is concatenated with a hyphen ('-') and {@link #labelAlign}. To add\r
154 * additional classes, use {@link #labelClsExtra}.\r
155 */\r
156 labelCls: Ext.baseCSSPrefix + 'form-item-label',\r
157\r
158 /**\r
159 * @private\r
160 */\r
161 topLabelCls: Ext.baseCSSPrefix + 'form-item-label-top',\r
162 rightLabelCls: Ext.baseCSSPrefix + 'form-item-label-right',\r
163 labelInnerCls: Ext.baseCSSPrefix + 'form-item-label-inner',\r
164 topLabelSideErrorCls: Ext.baseCSSPrefix + 'form-item-label-top-side-error',\r
165\r
166 /**\r
167 * @cfg {String} labelClsExtra\r
168 * An optional string of one or more additional CSS classes to add to the label element. Defaults to empty.\r
169 */\r
170\r
171 /**\r
172 * @cfg {String} errorMsgCls\r
173 * The CSS class to be applied to the error message element.\r
174 */\r
175 errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',\r
176\r
177 errorWrapCls: Ext.baseCSSPrefix + 'form-error-wrap',\r
178 errorWrapSideCls: Ext.baseCSSPrefix + 'form-error-wrap-side',\r
179 errorWrapUnderCls: Ext.baseCSSPrefix + 'form-error-wrap-under',\r
180 errorWrapUnderSideLabelCls: Ext.baseCSSPrefix + 'form-error-wrap-under-side-label',\r
181\r
182 /**\r
183 * @cfg {String} baseBodyCls\r
184 * The CSS class to be applied to the body content element.\r
185 */\r
186 baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',\r
187\r
188 invalidIconCls: Ext.baseCSSPrefix + 'form-invalid-icon',\r
189\r
190 invalidUnderCls: Ext.baseCSSPrefix + 'form-invalid-under',\r
191\r
192 noLabelCls: Ext.baseCSSPrefix + 'form-item-no-label',\r
193\r
194 /**\r
195 * @cfg {String} fieldBodyCls\r
196 * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.\r
197 */\r
198 fieldBodyCls: '',\r
199\r
200 /**\r
201 * @cfg {String} invalidCls\r
202 * The CSS class to use when marking the component invalid.\r
203 */\r
204 invalidCls : Ext.baseCSSPrefix + 'form-invalid',\r
205\r
206 /**\r
207 * @cfg {String} fieldLabel\r
208 * The label for the field. It gets appended with the {@link #labelSeparator}, and its position and sizing is\r
209 * determined by the {@link #labelAlign} and {@link #labelWidth} configs.\r
210 */\r
211 fieldLabel: undefined,\r
212\r
213 /**\r
214 * @cfg {String} labelAlign\r
215 * Controls the position and alignment of the {@link #fieldLabel}. Valid values are:\r
216 *\r
217 * - "left" (the default) - The label is positioned to the left of the field, with its text aligned to the left.\r
218 * Its width is determined by the {@link #labelWidth} config.\r
219 * - "top" - The label is positioned above the field.\r
220 * - "right" - The label is positioned to the left of the field, with its text aligned to the right.\r
221 * Its width is determined by the {@link #labelWidth} config.\r
222 */\r
223 labelAlign : 'left',\r
224\r
225 /**\r
226 * @cfg {Number} labelWidth\r
227 * The width of the {@link #fieldLabel} in pixels. Only applicable if {@link #labelAlign}\r
228 * is set to "left" or "right".\r
229 */\r
230 labelWidth: 100,\r
231\r
232 /**\r
233 * @cfg {Number} labelPad\r
234 * The amount of space in pixels between the {@link #fieldLabel} and the field body.\r
235 * This defaults to `5` for compatibility with Ext JS 4, however, as of Ext JS 5\r
236 * the space between the label and the body can optionally be determined by the theme\r
237 * using the {@link #$form-label-horizontal-spacing} (for side-aligned labels) and\r
238 * {@link #$form-label-vertical-spacing} (for top-aligned labels) SASS variables.\r
239 * In order for the stylesheet values as to take effect, you must use a labelPad value\r
240 * of `null`.\r
241 */\r
242 labelPad: 5,\r
243\r
244 //<locale>\r
245 /**\r
246 * @cfg {String} labelSeparator\r
247 * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.\r
248 *\r
249 * Set to empty string to hide the separator completely.\r
250 */\r
251 labelSeparator : ':',\r
252 //</locale>\r
253\r
254 /**\r
255 * @cfg {String} labelStyle\r
256 * A CSS style specification string to apply directly to this field's label.\r
257 */\r
258 \r
259 /**\r
260 * @cfg {Boolean} hideLabel\r
261 * Set to true to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}). Also see\r
262 * {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.\r
263 */\r
264 hideLabel: false,\r
265\r
266 /**\r
267 * @cfg {Boolean} hideEmptyLabel\r
268 * When set to true, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be automatically\r
269 * hidden if the {@link #fieldLabel} is empty. Setting this to false will cause the empty label element to be\r
270 * rendered and space to be reserved for it; this is useful if you want a field without a label to line up with\r
271 * other labeled fields in the same form.\r
272 *\r
273 * If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set the\r
274 * {@link #hideLabel} config to true.\r
275 */\r
276 hideEmptyLabel: true,\r
277\r
278 /**\r
279 * @cfg {Boolean} preventMark\r
280 * true to disable displaying any {@link #setActiveError error message} set on this object.\r
281 */\r
282 preventMark: false,\r
283\r
284 /**\r
285 * @cfg {Boolean} autoFitErrors\r
286 * Whether to adjust the component's body width to make room for 'side'\r
287 * {@link #msgTarget error messages}.\r
288 */\r
289 autoFitErrors: true,\r
290\r
291 /**\r
292 * @cfg {String} msgTarget\r
293 * The location where the error message text should display. Must be one of the following values:\r
294 *\r
295 * - `qtip` Display a quick tip containing the message when the user hovers over the field.\r
296 * This is the default.\r
297 *\r
298 * **{@link Ext.tip.QuickTipManager#init} must have been called for this setting to work.**\r
299 *\r
300 * - `title` Display the message in a default browser title attribute popup.\r
301 * - `under` Add a block div beneath the field containing the error message.\r
302 * - `side` Add an error icon to the right of the field, displaying the message in a popup on hover.\r
303 * - `none` Don't display any error message. This might be useful if you are implementing custom error display.\r
304 * - `[element id]` Add the error message directly to the innerHTML of the specified element.\r
305 */\r
306 msgTarget: 'qtip',\r
307\r
308 /**\r
309 * @private\r
310 * Map for msg target lookup, if target is not in this map it is assumed\r
311 * to be an element id\r
312 */\r
313 msgTargets: {\r
314 qtip: 1,\r
315 title: 1,\r
316 under: 1,\r
317 side: 1,\r
318 none: 1\r
319 },\r
320\r
321 /**\r
322 * @cfg {String} activeError\r
323 * If specified, then the component will be displayed with this value as its active error when first rendered. Use\r
324 * {@link #setActiveError} or {@link #unsetActiveError} to change it after component creation.\r
325 */\r
326\r
327 /**\r
328 * @private\r
329 * Tells the layout system that the height can be measured immediately because the width does not need setting.\r
330 */\r
331 noWrap: true,\r
332\r
333 labelableInsertions: [\r
334\r
335 /**\r
336 * @cfg {String/Array/Ext.XTemplate} beforeBodyEl\r
337 * An optional string or `XTemplate` configuration to insert in the field markup\r
338 * at the beginning of the input containing element. If an `XTemplate` is used, the component's {@link Ext.Component#renderData render data}\r
339 * serves as the context.\r
340 */\r
341 'beforeBodyEl',\r
342\r
343 /**\r
344 * @cfg {String/Array/Ext.XTemplate} afterBodyEl\r
345 * An optional string or `XTemplate` configuration to insert in the field markup\r
346 * at the end of the input containing element. If an `XTemplate` is used, the component's {@link Ext.Component#renderData render data}\r
347 * serves as the context.\r
348 */\r
349 'afterBodyEl',\r
350\r
351 /**\r
352 * @cfg {String/Array/Ext.XTemplate} beforeLabelTpl\r
353 * An optional string or `XTemplate` configuration to insert in the field markup\r
354 * before the label element. If an `XTemplate` is used, the component's {@link Ext.Component#renderData render data}\r
355 * serves as the context.\r
356 */\r
357 'beforeLabelTpl',\r
358\r
359 /**\r
360 * @cfg {String/Array/Ext.XTemplate} afterLabelTpl\r
361 * An optional string or `XTemplate` configuration to insert in the field markup\r
362 * after the label element. If an `XTemplate` is used, the component's {@link Ext.Component#renderData render data}\r
363 * serves as the context.\r
364 */\r
365 'afterLabelTpl',\r
366\r
367 /**\r
368 * @cfg {String/Array/Ext.XTemplate} beforeSubTpl\r
369 * An optional string or `XTemplate` configuration to insert in the field markup\r
370 * before the {@link #getSubTplMarkup subTpl markup}. If an `XTemplate` is used, the\r
371 * component's {@link Ext.Component#renderData render data} serves as the context.\r
372 */\r
373 'beforeSubTpl',\r
374\r
375 /**\r
376 * @cfg {String/Array/Ext.XTemplate} afterSubTpl\r
377 * An optional string or `XTemplate` configuration to insert in the field markup\r
378 * after the {@link #getSubTplMarkup subTpl markup}. If an `XTemplate` is used, the\r
379 * component's {@link Ext.Component#renderData render data} serves as the context.\r
380 */\r
381 'afterSubTpl',\r
382\r
383 /**\r
384 * @cfg {String/Array/Ext.XTemplate} beforeLabelTextTpl\r
385 * An optional string or `XTemplate` configuration to insert in the field markup\r
386 * before the label text. If an `XTemplate` is used, the component's {@link Ext.Component#renderData render data}\r
387 * serves as the context.\r
388 */\r
389 'beforeLabelTextTpl',\r
390\r
391 /**\r
392 * @cfg {String/Array/Ext.XTemplate} afterLabelTextTpl\r
393 * An optional string or `XTemplate` configuration to insert in the field markup\r
394 * after the label text. If an `XTemplate` is used, the component's {@link Ext.Component#renderData render data}\r
395 * serves as the context.\r
396 */\r
397 'afterLabelTextTpl',\r
398\r
399 /**\r
400 * @cfg {String/Array/Ext.XTemplate} labelAttrTpl\r
401 * An optional string or `XTemplate` configuration to insert in the field markup\r
402 * inside the label element (as attributes). If an `XTemplate` is used, the component's\r
403 * {@link Ext.Component#renderData render data} serves as the context.\r
404 */\r
405 'labelAttrTpl'\r
406 ],\r
407\r
408 statics: {\r
409 /**\r
410 * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we\r
411 * can give it a custom frame style. Responds to errorqtip rather than the qtip property.\r
412 * @static\r
413 * @private\r
414 */\r
415 initTip: function() {\r
416 var tip = this.tip,\r
417 cfg, copy;\r
418\r
419 if (tip) {\r
420 return;\r
421 }\r
422\r
423 cfg = {\r
424 id: 'ext-form-error-tip',\r
425 //<debug>\r
426 // tell the spec runner to ignore this element when checking if the dom is clean\r
427 sticky: true,\r
428 //</debug>\r
429 ui: 'form-invalid'\r
430 };\r
431\r
432 // On Touch devices, tapping the target shows the qtip\r
433 if (Ext.supports.Touch) {\r
434 cfg.dismissDelay = 0;\r
435 cfg.anchor = 'top';\r
436 cfg.showDelay = 0;\r
437 cfg.listeners = {\r
438 beforeshow: function() {\r
439 this.minWidth = Ext.fly(this.anchorTarget).getWidth();\r
440 }\r
441 };\r
442 }\r
443 tip = this.tip = Ext.create('Ext.tip.QuickTip', cfg);\r
444 copy = Ext.apply({}, tip.tagConfig);\r
445 copy.attribute = 'errorqtip';\r
446 tip.setTagConfig(copy);\r
447 },\r
448\r
449 /**\r
450 * Destroy the error tip instance.\r
451 * @static\r
452 */\r
453 destroyTip: function() {\r
454 this.tip = Ext.destroy(this.tip);\r
455 }\r
456 },\r
457\r
458 /**\r
459 * @event errorchange\r
460 * Fires when the active error message is changed via {@link #setActiveError}.\r
461 * @param {Ext.form.Labelable} this\r
462 * @param {String} error The active error message\r
463 */\r
464\r
465 /**\r
466 * Performs initialization of this mixin. Component classes using this mixin should call this method during their\r
467 * own initialization.\r
468 */\r
469 initLabelable: function() {\r
470 var me = this,\r
471 padding = me.padding;\r
472\r
473 // This Component is rendered as a table. Padding doesn't work on tables\r
474 // Before padding can be applied to the encapsulating table element, copy the padding into\r
475 // an extraMargins property which is to be added to all computed margins post render :(\r
476 if (padding) {\r
477 me.padding = undefined;\r
478 me.extraMargins = Ext.Element.parseBox(padding);\r
479 }\r
480\r
481 // IE8 hack for https://sencha.jira.com/browse/EXTJS-17536.\r
482 // Need to force a relayout of the display:table form item.\r
483 // TODO: Remove when IE8 retires.\r
484 if (Ext.isIE8) {\r
485 me.restoreDisplay = Ext.Function.createDelayed(me.doRestoreDisplay, 0, me);\r
486 }\r
487\r
488 if (!me.activeErrorsTpl) {\r
489 if (me.msgTarget === 'title') {\r
490 me.activeErrorsTpl = me.plaintextActiveErrorsTpl;\r
491 } else {\r
492 me.activeErrorsTpl = me.htmlActiveErrorsTpl;\r
493 }\r
494 }\r
495\r
496 me.addCls([me.formItemCls, me.formItemCls + '-' + me.ui]);\r
497\r
498 // Prevent first render of active error, at Field render time from signalling a change from undefined to "\r
499 me.lastActiveError = '';\r
500\r
501 // bubbleEvents on the prototype of a mixin won't work, so call enableBubble\r
502 me.enableBubble('errorchange');\r
503 },\r
504\r
505 /**\r
506 * Returns the trimmed label by slicing off the label separator character. Can be overridden.\r
507 * @return {String} The trimmed field label, or empty string if not defined\r
508 */\r
509 trimLabelSeparator: function() {\r
510 var me = this,\r
511 separator = me.labelSeparator,\r
512 label = me.fieldLabel || '',\r
513 lastChar = label.substr(label.length - 1);\r
514\r
515 // if the last char is the same as the label separator then slice it off otherwise just return label value\r
516 return lastChar === separator ? label.slice(0, -1) : label;\r
517 },\r
518\r
519 /**\r
520 * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be overridden\r
521 * to provide a custom generated label.\r
522 * @template\r
523 * @return {String} The configured field label, or empty string if not defined\r
524 */\r
525 getFieldLabel: function() {\r
526 return this.trimLabelSeparator();\r
527 },\r
528\r
529 /**\r
530 * Set the label of this field.\r
531 * @param {String} label The new label. The {@link #labelSeparator} will be automatically appended to the label\r
532 * string.\r
533 */\r
534 setFieldLabel: function(label){\r
535 label = label || '';\r
536\r
537 var me = this,\r
538 separator = me.labelSeparator,\r
539 labelEl = me.labelEl,\r
540 errorWrapEl = me.errorWrapEl,\r
541 sideLabel = (me.labelAlign !== 'top'),\r
542 noLabelCls = me.noLabelCls,\r
543 errorWrapUnderSideLabelCls = me.errorWrapUnderSideLabelCls;\r
544\r
545 me.fieldLabel = label;\r
546 if (me.rendered) {\r
547 if (Ext.isEmpty(label) && me.hideEmptyLabel) {\r
548 me.addCls(noLabelCls);\r
549 if (sideLabel && errorWrapEl) {\r
550 errorWrapEl.removeCls(errorWrapUnderSideLabelCls);\r
551 }\r
552 } else {\r
553 if (separator) {\r
554 label = me.trimLabelSeparator() + separator;\r
555 }\r
556 labelEl.dom.firstChild.innerHTML = label;\r
557 me.removeCls(noLabelCls);\r
558 if (sideLabel && errorWrapEl) {\r
559 errorWrapEl.addCls(errorWrapUnderSideLabelCls);\r
560 }\r
561 }\r
562 me.updateLayout();\r
563 }\r
564 },\r
565\r
566 setHideLabel: function(hideLabel) {\r
567 var me = this;\r
568\r
569 if (hideLabel !== me.hideLabel) {\r
570 me.hideLabel = hideLabel;\r
571 if (me.rendered) {\r
572 me[hideLabel ? 'addCls' : 'removeCls'](me.noLabelCls);\r
573 me.updateLayout();\r
574 }\r
575 }\r
576 },\r
577\r
578 setHideEmptyLabel: function(hideEmptyLabel) {\r
579 var me = this,\r
580 hide;\r
581\r
582 if (hideEmptyLabel !== me.hideEmptyLabel) {\r
583 me.hideEmptyLabel = hideEmptyLabel;\r
584 if (me.rendered && !me.hideLabel) {\r
585 hide = hideEmptyLabel && !me.getFieldLabel();\r
586 me[hide ? 'addCls' : 'removeCls'](me.noLabelCls);\r
587 me.updateLayout();\r
588 }\r
589 }\r
590 },\r
591\r
592 getInsertionRenderData: function (data, names) {\r
593 var i = names.length,\r
594 name, value;\r
595\r
596 while (i--) {\r
597 name = names[i];\r
598 value = this[name];\r
599\r
600 if (value) {\r
601 if (typeof value !== 'string') {\r
602 if (!value.isTemplate) {\r
603 value = Ext.XTemplate.getTpl(this, name);\r
604 }\r
605 value = value.apply(data);\r
606 }\r
607 }\r
608\r
609 data[name] = value || '';\r
610 }\r
611\r
612 return data;\r
613 },\r
614\r
615 /**\r
616 * Generates the arguments for the field decorations {@link #labelableRenderTpl\r
617 * rendering template}.\r
618 * @param {Object} data optional object to use as the base data object. If provided,\r
619 * this method will add properties to the base object instead of creating a new one.\r
620 * @return {Object} The template arguments\r
621 * @protected\r
622 */\r
623 getLabelableRenderData: function() {\r
624 var me = this,\r
625 labelAlign = me.labelAlign,\r
626 topLabel = (labelAlign === 'top'),\r
627 rightLabel = (labelAlign === 'right'),\r
628 sideError = (me.msgTarget === 'side'),\r
629 underError = (me.msgTarget === 'under'),\r
630 errorMsgCls = me.errorMsgCls,\r
631 labelPad = me.labelPad,\r
632 labelWidth = me.labelWidth,\r
633 labelClsExtra = me.labelClsExtra || '',\r
634 errorWrapExtraCls = sideError ? me.errorWrapSideCls : me.errorWrapUnderCls,\r
635 labelStyle = '',\r
636 labelInnerStyle = '',\r
637 labelVisible = me.hasVisibleLabel(),\r
638 autoFitErrors = me.autoFitErrors,\r
639 defaultBodyWidth = me.defaultBodyWidth,\r
640 bodyStyle, data;\r
641\r
642 if (topLabel) {\r
643 labelClsExtra += ' ' + me.topLabelCls;\r
644 if (labelPad) {\r
645 labelInnerStyle = 'padding-bottom:' + labelPad + 'px;';\r
646 }\r
647 if (sideError && !autoFitErrors) {\r
648 labelClsExtra += ' ' + me.topLabelSideErrorCls;\r
649 }\r
650 } else {\r
651 if (rightLabel) {\r
652 labelClsExtra += ' ' + me.rightLabelCls;\r
653 }\r
654 if (labelPad) {\r
655 labelStyle += me.getHorizontalPaddingStyle() + labelPad + 'px;';\r
656 }\r
657 labelStyle += 'width:' + (labelWidth + (labelPad ? labelPad : 0)) + 'px;';\r
658 // inner label needs width as well so that setting width on the outside\r
659 // that is smaller than the natural width, will be ensured to take width\r
660 // away from the body, and not the label.\r
661 labelInnerStyle = 'width:' + labelWidth + 'px';\r
662 }\r
663\r
664 if (labelVisible) {\r
665 if (!topLabel && underError) {\r
666 errorWrapExtraCls += ' ' + me.errorWrapUnderSideLabelCls;\r
667 }\r
668 }\r
669\r
670 if (defaultBodyWidth) {\r
671 // This is here to support textfield's deprecated "size" config\r
672 bodyStyle = 'min-width:' + defaultBodyWidth + 'px;max-width:' +\r
673 defaultBodyWidth + 'px;';\r
674 }\r
675\r
676 data = {\r
677 id: me.id,\r
678 inputId: me.getInputId(),\r
679 labelCls: me.labelCls,\r
680 labelClsExtra: labelClsExtra,\r
681 labelStyle: labelStyle + (me.labelStyle || ''),\r
682 labelInnerStyle: labelInnerStyle,\r
683 labelInnerCls: me.labelInnerCls,\r
684 unselectableCls: Ext.Element.unselectableCls,\r
685 bodyStyle: bodyStyle,\r
686 baseBodyCls: me.baseBodyCls,\r
687 fieldBodyCls: me.fieldBodyCls,\r
688 extraFieldBodyCls: me.extraFieldBodyCls,\r
689 errorWrapCls: me.errorWrapCls,\r
690 errorWrapExtraCls: errorWrapExtraCls,\r
691 renderError: sideError || underError,\r
692 invalidMsgCls: sideError ? me.invalidIconCls : underError ? me.invalidUnderCls : '',\r
693 errorMsgCls: errorMsgCls,\r
694 growCls: me.grow ? me.growCls : '',\r
695 tipAnchorTarget: me.id + '-inputEl',\r
696 errorWrapStyle: (sideError && !autoFitErrors) ?\r
697 'visibility:hidden' : 'display:none',\r
698 fieldLabel: me.getFieldLabel(),\r
699 labelSeparator: me.labelSeparator\r
700 };\r
701\r
702 me.getInsertionRenderData(data, me.labelableInsertions);\r
703\r
704 return data;\r
705 },\r
706\r
707 // hook for rtl\r
708 getHorizontalPaddingStyle: function() {\r
709 return 'padding-right:';\r
710 },\r
711\r
712 beforeLabelRender: function() {\r
713 var me = this;\r
714 me.setFieldDefaults(me.getInherited().fieldDefaults);\r
715 if (me.ownerLayout) {\r
716 me.addCls(Ext.baseCSSPrefix + me.ownerLayout.type + '-form-item');\r
717 }\r
718 if (!me.hasVisibleLabel()) {\r
719 me.addCls(me.noLabelCls);\r
720 }\r
721 },\r
722\r
723 onLabelRender: function() {\r
724 var me = this,\r
725 style = {},\r
726 ExtElement = Ext.Element,\r
727 errorWrapEl = me.errorWrapEl,\r
728 margins, side;\r
729\r
730 if (errorWrapEl) {\r
731 errorWrapEl.setVisibilityMode((me.msgTarget === 'side' && !me.autoFitErrors) ?\r
732 ExtElement.VISIBILITY : ExtElement.DISPLAY);\r
733 }\r
734\r
735 if (me.extraMargins) {\r
736 margins = me.el.getMargin();\r
737 for (side in margins) {\r
738 if (margins.hasOwnProperty(side)) {\r
739 style['margin-' + side] = (margins[side] + me.extraMargins[side]) + 'px';\r
740 }\r
741 }\r
742 me.el.setStyle(style);\r
743 }\r
744 },\r
745\r
746 /**\r
747 * Checks if the field has a visible label\r
748 * @return {Boolean} True if the field has a visible label\r
749 */\r
750 hasVisibleLabel: function(){\r
751 if (this.hideLabel) {\r
752 return false;\r
753 }\r
754 return !(this.hideEmptyLabel && !this.getFieldLabel());\r
755 },\r
756\r
757 /**\r
758 * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should be implemented\r
759 * by classes including this mixin as needed.\r
760 * @return {String} The markup to be inserted\r
761 * @protected\r
762 */\r
763 getSubTplMarkup: function() {\r
764 return '';\r
765 },\r
766\r
767 /**\r
768 * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.\r
769 * Implementing subclasses may also use this as e.g. the id for their own input element.\r
770 * @return {String} The input id\r
771 */\r
772 getInputId: function() {\r
773 return '';\r
774 },\r
775\r
776 /**\r
777 * Gets the active error message for this component, if any. This does not trigger validation on its own, it merely\r
778 * returns any message that the component may already hold.\r
779 * @return {String} The active error message on the component; if there is no error, an empty string is returned.\r
780 */\r
781 getActiveError : function() {\r
782 return this.activeError || '';\r
783 },\r
784\r
785 /**\r
786 * Tells whether the field currently has an active error message. This does not trigger validation on its own, it\r
787 * merely looks for any message that the component may already hold.\r
788 * @return {Boolean}\r
789 */\r
790 hasActiveError: function() {\r
791 return !!this.getActiveError();\r
792 },\r
793\r
794 /**\r
795 * Sets the active error message to the given string. This replaces the entire error message contents with the given\r
796 * string. Also see {@link #setActiveErrors} which accepts an Array of messages and formats them according to the\r
797 * {@link #activeErrorsTpl}. Note that this only updates the error message element's text and attributes, you'll\r
798 * have to call doComponentLayout to actually update the field's layout to match. If the field extends {@link\r
799 * Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.\r
800 * @param {String} msg The error message\r
801 */\r
802 setActiveError: function(msg) {\r
803 this.setActiveErrors(msg);\r
804 },\r
805\r
806 /**\r
807 * Gets an Array of any active error messages currently applied to the field. This does not trigger validation on\r
808 * its own, it merely returns any messages that the component may already hold.\r
809 * @return {String[]} The active error messages on the component; if there are no errors, an empty Array is\r
810 * returned.\r
811 */\r
812 getActiveErrors: function() {\r
813 return this.activeErrors || [];\r
814 },\r
815\r
816 /**\r
817 * Set the active error message to an Array of error messages. The messages are formatted into a single message\r
818 * string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError} which allows setting the entire error\r
819 * contents with a single string. Note that this only updates the error message element's text and attributes,\r
820 * you'll have to call doComponentLayout to actually update the field's layout to match. If the field extends\r
821 * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.\r
822 * @param {String[]} errors The error messages\r
823 */\r
824 setActiveErrors: function(errors) {\r
825 var me = this,\r
826 errorWrapEl = me.errorWrapEl,\r
827 msgTarget = me.msgTarget,\r
828 isSide = msgTarget === 'side',\r
829 isQtip = msgTarget === 'qtip',\r
830 actionEl, activeError, tpl, targetEl;\r
831\r
832 errors = Ext.Array.from(errors);\r
833 tpl = me.getTpl('activeErrorsTpl');\r
834\r
835 me.activeErrors = errors;\r
836 activeError = me.activeError = tpl.apply({\r
837 fieldLabel: me.fieldLabel,\r
838 errors: errors,\r
839 listCls: Ext.baseCSSPrefix + 'list-plain'\r
840 });\r
841\r
842 me.renderActiveError();\r
843\r
844 if (me.rendered) {\r
845 actionEl = me.getActionEl();\r
846 \r
847 if (isSide) {\r
848 me.errorEl.dom.setAttribute('data-errorqtip', activeError);\r
849 }\r
850 else if (isQtip) {\r
851 actionEl.dom.setAttribute('data-errorqtip', activeError);\r
852 }\r
853 else if (msgTarget === 'title') {\r
854 actionEl.dom.setAttribute('title', activeError);\r
855 }\r
856\r
857 // If msgTarget is title, setting an alert is redundant for ARIA purposes\r
858 if (msgTarget !== 'title') {\r
859 me.ariaErrorEl.dom.innerHTML = errors.join('. ');\r
860 actionEl.dom.setAttribute('aria-describedby', me.ariaErrorEl.id);\r
861 }\r
862 \r
863 if (isSide || isQtip) {\r
864 Ext.form.Labelable.initTip();\r
865 }\r
866\r
867 if (!me.msgTargets[msgTarget]) {\r
868 targetEl = Ext.get(msgTarget);\r
869\r
870 if (targetEl) {\r
871 targetEl.dom.innerHTML = activeError;\r
872 }\r
873 }\r
874 }\r
875\r
876 if (errorWrapEl) {\r
877 errorWrapEl.setVisible(errors.length > 0);\r
878 if (isSide && me.autoFitErrors) {\r
879 me.labelEl.addCls(me.topLabelSideErrorCls);\r
880 }\r
881 me.updateLayout();\r
882 }\r
883 },\r
884\r
885 /**\r
886 * Clears the active error message(s). Note that this only clears the error message element's text and attributes,\r
887 * you'll have to call doComponentLayout to actually update the field's layout to match. If the field extends {@link\r
888 * Ext.form.field.Base} you should call {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.\r
889 */\r
890 unsetActiveError: function() {\r
891 var me = this,\r
892 errorWrapEl = me.errorWrapEl,\r
893 msgTarget = me.msgTarget,\r
894 restoreDisplay = me.restoreDisplay,\r
895 actionEl, targetEl;\r
896\r
897 if (me.hasActiveError()) {\r
898 delete me.activeError;\r
899 delete me.activeErrors;\r
900 me.renderActiveError();\r
901\r
902 if (me.rendered) {\r
903 actionEl = me.getActionEl();\r
904 \r
905 if (msgTarget === 'qtip') {\r
906 actionEl.dom.removeAttribute('data-errorqtip');\r
907 }\r
908 else if (msgTarget === 'title') {\r
909 actionEl.dom.removeAttribute('title');\r
910 }\r
911 \r
912 if (msgTarget !== 'title') {\r
913 me.ariaErrorEl.dom.innerHTML = '';\r
914 actionEl.dom.removeAttribute('aria-describedby');\r
915 }\r
916\r
917 if (!me.msgTargets[msgTarget]) {\r
918 targetEl = Ext.get(msgTarget);\r
919\r
920 if (targetEl) {\r
921 targetEl.dom.innerHTML = '';\r
922 }\r
923 }\r
924\r
925 if (errorWrapEl) {\r
926 errorWrapEl.hide();\r
927 if (msgTarget === 'side' && me.autoFitErrors) {\r
928 me.labelEl.removeCls(me.topLabelSideErrorCls);\r
929 }\r
930 me.updateLayout();\r
931\r
932 // IE8 hack for https://sencha.jira.com/browse/EXTJS-17536.\r
933 // Need to force a relayout of the display:table form item.\r
934 // TODO: Remove when IE8 retires.\r
935 if (restoreDisplay) {\r
936 me.el.dom.style.display = 'block';\r
937 me.restoreDisplay();\r
938 }\r
939 }\r
940 }\r
941 }\r
942 },\r
943\r
944 doRestoreDisplay: function() {\r
945 // IE8 hack for https://sencha.jira.com/browse/EXTJS-17536.\r
946 // Need to force a relayout of the display:table form item.\r
947 // TODO: Remove this method when IE8 retires.\r
948 var el = this.el;\r
949 if (el && el.dom) {\r
950 el.dom.style.display = '';\r
951 }\r
952 },\r
953\r
954 /**\r
955 * @private\r
956 * Updates the rendered DOM to match the current activeError. This only updates the content and\r
957 * attributes, you'll have to call doComponentLayout to actually update the display.\r
958 */\r
959 renderActiveError: function() {\r
960 var me = this,\r
961 activeError = me.getActiveError(),\r
962 hasError = !!activeError;\r
963\r
964 if (activeError !== me.lastActiveError) {\r
965 me.lastActiveError = activeError;\r
966 me.fireEvent('errorchange', me, activeError);\r
967 }\r
968\r
969 if (me.rendered && !me.destroyed && !me.preventMark) {\r
970 me.toggleInvalidCls(hasError);\r
971 // Update the errorEl (There will only be one if msgTarget is 'side' or 'under') with the error message text\r
972 if (me.errorEl) {\r
973 me.errorEl.dom.innerHTML = activeError;\r
974 }\r
975 }\r
976 },\r
977\r
978 /**\r
979 * @private\r
980 * Add/remove invalid class(es)\r
981 * @param {Boolean} hasError\r
982 */\r
983 toggleInvalidCls: function(hasError) {\r
984 this.el[hasError ? 'addCls' : 'removeCls'](this.invalidCls);\r
985 },\r
986\r
987 /**\r
988 * Applies a set of default configuration values to this Labelable instance. For each of the properties in the given\r
989 * object, check if this component hasOwnProperty that config; if not then it's inheriting a default value from its\r
990 * prototype and we should apply the default value.\r
991 * @param {Object} defaults The defaults to apply to the object.\r
992 */\r
993 setFieldDefaults: function(defaults) {\r
994 var key;\r
995\r
996 for (key in defaults) {\r
997 if (!this.hasOwnProperty(key)) {\r
998 this[key] = defaults[key];\r
999 }\r
1000 }\r
1001 }\r
1002}, function() {\r
1003 if (Ext.supports.Touch) {\r
1004 this.prototype.msgTarget = 'side';\r
1005 }\r
1006});\r