]>
Commit | Line | Data |
---|---|---|
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 | |
21 | Ext.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 > 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 |