]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/form/field/Field.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / form / field / Field.js
CommitLineData
6527f429
DM
1/**\r
2 * This mixin provides a common interface for the logical behavior and state of form fields, including:\r
3 *\r
4 * - Getter and setter methods for field values\r
5 * - Events and methods for tracking value and validity changes\r
6 * - Methods for triggering validation\r
7 *\r
8 * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}\r
9 * component class rather than using this mixin directly, as BaseField contains additional logic for generating an\r
10 * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,\r
11 * plus methods that bind the Field value getters and setters to the input field's value.\r
12 *\r
13 * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then\r
14 * you will most likely want to override the following methods with custom implementations: {@link #getValue},\r
15 * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base\r
16 * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}\r
17 * is called during the component's initialization.\r
18 */\r
19Ext.define('Ext.form.field.Field', {\r
20 mixinId: 'field',\r
21\r
22 /**\r
23 * @property {Boolean} isFormField\r
24 * Flag denoting that this component is a Field. Always true.\r
25 */\r
26 isFormField : true,\r
27\r
28 config: {\r
29 /**\r
30 * @cfg {Boolean/String} validation\r
31 * This property, when a `String`, contributes its value to the error state of this\r
32 * instance as reported by `getErrors`.\r
33 */\r
34 validation: null,\r
35\r
36 /**\r
37 * @cfg {Ext.data.Field} validationField\r
38 * When binding is used with a model, this maps to the underlying {@link Ext.data.field.Field} if\r
39 * it is available. This can be used to validate the value against the model field without needing\r
40 * to push the value back into the model.\r
41 *\r
42 * @private\r
43 */\r
44 validationField: null\r
45 },\r
46\r
47 /**\r
48 * @cfg {Object} value\r
49 * A value to initialize this field with.\r
50 */\r
51\r
52 /**\r
53 * @cfg {String} name\r
54 * The name of the field. By default this is used as the parameter name when including the\r
55 * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from\r
56 * being included in the form submit, set {@link #submitValue} to false.\r
57 */\r
58\r
59 /**\r
60 * @cfg {Boolean} disabled\r
61 * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.\r
62 */\r
63 disabled : false,\r
64\r
65 /**\r
66 * @cfg {Boolean} submitValue\r
67 * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is\r
68 * not disabled.\r
69 */\r
70 submitValue: true,\r
71\r
72 /**\r
73 * @cfg {Boolean} validateOnChange\r
74 * Specifies whether this field should be validated immediately whenever a change in its value is detected.\r
75 * If the validation results in a change in the field's validity, a {@link #validitychange} event will be\r
76 * fired. This allows the field to show feedback about the validity of its contents immediately as the user is\r
77 * typing.\r
78 *\r
79 * When set to false, feedback will not be immediate. However the form will still be validated before submitting if\r
80 * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated\r
81 * manually.\r
82 *\r
83 * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are\r
84 * detected.\r
85 */\r
86 validateOnChange: true,\r
87\r
88 /**\r
89 * @cfg {String[]/String} valuePublishEvent\r
90 * The event name(s) to use to publish the {@link #value}\r
91 * {@link Ext.form.field.Base#bind} for this field.\r
92 * @since 5.0.1\r
93 */\r
94 valuePublishEvent: 'change',\r
95\r
96 /**\r
97 * @private\r
98 */\r
99 suspendCheckChange: 0,\r
100 \r
101 /**\r
102 * @property {Boolean} dirty\r
103 * The dirty state of the field.\r
104 * @private\r
105 */\r
106 dirty: false,\r
107\r
108 /**\r
109 * @event change\r
110 * Fires when the value of a field is changed. The value of a field is \r
111 * checked for changes when the field's {@link #setValue} method \r
112 * is called and when any of the events listed in \r
113 * {@link Ext.form.field.Base#checkChangeEvents checkChangeEvents} are fired.\r
114 * @param {Ext.form.field.Field} this\r
115 * @param {Object} newValue The new value\r
116 * @param {Object} oldValue The original value\r
117 */\r
118\r
119 /**\r
120 * @event validitychange\r
121 * Fires when a change in the field's validity is detected.\r
122 * @param {Ext.form.field.Field} this\r
123 * @param {Boolean} isValid Whether or not the field is now valid\r
124 */\r
125\r
126 /**\r
127 * @event dirtychange\r
128 * Fires when a change in the field's {@link #isDirty} state is detected.\r
129 * @param {Ext.form.field.Field} this\r
130 * @param {Boolean} isDirty Whether or not the field is now dirty\r
131 */\r
132\r
133 /**\r
134 * Initializes this Field mixin on the current instance. Components using this mixin should call this method during\r
135 * their own initialization process.\r
136 */\r
137 initField: function() {\r
138 var me = this,\r
139 valuePublishEvent = me.valuePublishEvent,\r
140 len, i;\r
141\r
142 me.initValue();\r
143 \r
144 //<debug>\r
145 var badNames = [\r
146 'tagName',\r
147 'nodeName',\r
148 'children',\r
149 'childNodes'\r
150 ], name = this.name;\r
151 \r
152 if (name && Ext.Array.indexOf(badNames, name) > -1) {\r
153 Ext.log.warn(\r
154 ['It is recommended to not use "', name, '" as a field name, because it ',\r
155 'can cause naming collisions during form submission.'].join('')\r
156 );\r
157 }\r
158 //</debug>\r
159\r
160 // Vast majority of cases won't be an array\r
161 if (Ext.isString(valuePublishEvent)) {\r
162 me.on(valuePublishEvent, me.publishValue, me);\r
163 } else {\r
164 for (i = 0, len = valuePublishEvent.length; i < len; ++i) {\r
165 me.on(valuePublishEvent[i], me.publishValue, me);\r
166 }\r
167 }\r
168 },\r
169\r
170 /**\r
171 * Initializes the field's value based on the initial config.\r
172 */\r
173 initValue: function() {\r
174 var me = this;\r
175\r
176 // Set the initial value if we have one.\r
177 // Prevent validation on initial set.\r
178 if ('value' in me) {\r
179 me.suspendCheckChange++;\r
180 me.setValue(me.value);\r
181 me.suspendCheckChange--;\r
182 }\r
183 \r
184 /**\r
185 * @property {Object} originalValue\r
186 * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last\r
187 * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.\r
188 */\r
189 me.initialValue = me.originalValue = me.lastValue = me.getValue();\r
190 },\r
191\r
192 // Fields can be editors, and some editors may not have a name property that maps\r
193 // to its data index, so it's necessary in these cases to look it up by its dataIndex\r
194 // property. See EXTJSIV-11650.\r
195 getFieldIdentifier: function () {\r
196 return this.isEditorComponent ? this.dataIndex : this.name;\r
197 },\r
198\r
199 /**\r
200 * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name\r
201 * when including the field value in a {@link Ext.form.Basic#submit form submit()}.\r
202 * @return {String} name The field {@link Ext.form.field.Field#name name}\r
203 */\r
204 getName: function() {\r
205 return this.name;\r
206 },\r
207\r
208 /**\r
209 * Returns the current data value of the field. The type of value returned is particular to the type of the\r
210 * particular field (e.g. a Date object for {@link Ext.form.field.Date}).\r
211 * @return {Object} value The field value\r
212 */\r
213 getValue: function() {\r
214 return this.value;\r
215 },\r
216\r
217 /**\r
218 * Sets a data value into the field and runs the change detection and validation.\r
219 * @param {Object} value The value to set\r
220 * @return {Ext.form.field.Field} this\r
221 */\r
222 setValue: function(value) {\r
223 var me = this;\r
224 me.value = value;\r
225 me.checkChange();\r
226 return me;\r
227 },\r
228\r
229 /**\r
230 * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this\r
231 * to provide custom comparison logic appropriate for the particular field's data type.\r
232 * @param {Object} value1 The first value to compare\r
233 * @param {Object} value2 The second value to compare\r
234 * @return {Boolean} True if the values are equal, false if inequal.\r
235 */\r
236 isEqual: function(value1, value2) {\r
237 return String(value1) === String(value2);\r
238 },\r
239\r
240 /**\r
241 * Returns whether two values are logically equal.\r
242 * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.\r
243 * @private\r
244 * @param {Object} value1 The first value to compare\r
245 * @param {Object} value2 The second value to compare\r
246 * @return {Boolean} True if the values are equal, false if inequal.\r
247 */\r
248 isEqualAsString: function(value1, value2){\r
249 return String(Ext.valueFrom(value1, '')) === String(Ext.valueFrom(value2, ''));\r
250 },\r
251\r
252 /**\r
253 * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be\r
254 * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being\r
255 * its current stringified value. More advanced field implementations may return more than one name-value pair.\r
256 *\r
257 * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate\r
258 * validated}.\r
259 *\r
260 * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of\r
261 * strings if that particular name has multiple values. It can also return null if there are no parameters to be\r
262 * submitted.\r
263 */\r
264 getSubmitData: function() {\r
265 var me = this,\r
266 data = null;\r
267 if (!me.disabled && me.submitValue) {\r
268 data = {};\r
269 data[me.getName()] = '' + me.getValue();\r
270 }\r
271 return data;\r
272 },\r
273\r
274 /**\r
275 * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link\r
276 * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name\r
277 * being this field's {@link #getName name} and the value being its current data value. More advanced field\r
278 * implementations may return more than one name-value pair. The returned values will be saved to the corresponding\r
279 * field names in the Model.\r
280 *\r
281 * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate\r
282 * validated}.\r
283 *\r
284 * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of\r
285 * strings if that particular name has multiple values. It can also return null if there are no parameters to be\r
286 * submitted.\r
287 */\r
288 getModelData: function(includeEmptyText, /*private*/ isSubmitting) {\r
289 var me = this,\r
290 data = null;\r
291 \r
292 // Note that we need to check if this operation is being called from a Submit action because displayfields aren't\r
293 // to be submitted, but they can call this to get their model data.\r
294 if (!me.disabled && (me.submitValue || !isSubmitting)) {\r
295 data = {};\r
296 data[me.getFieldIdentifier()] = me.getValue();\r
297 }\r
298 return data;\r
299 },\r
300\r
301 /**\r
302 * Resets the current field value to the originally loaded value and clears any validation messages. See {@link\r
303 * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}\r
304 */\r
305 reset: function(){\r
306 var me = this;\r
307\r
308 me.beforeReset();\r
309 me.setValue(me.originalValue);\r
310 me.clearInvalid();\r
311 // delete here so we reset back to the original state\r
312 delete me.wasValid;\r
313 },\r
314 \r
315 /**\r
316 * Template method before a field is reset.\r
317 * @protected\r
318 */\r
319 beforeReset: Ext.emptyFn,\r
320\r
321 /**\r
322 * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is\r
323 * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's\r
324 * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.\r
325 */\r
326 resetOriginalValue: function() {\r
327 this.originalValue = this.getValue();\r
328 this.checkDirty();\r
329 },\r
330\r
331 /**\r
332 * Checks whether the value of the field has changed since the last time it was checked.\r
333 * If the value has changed, it:\r
334 *\r
335 * 1. Fires the {@link #change change event},\r
336 * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the\r
337 * {@link #validitychange validitychange event} if the validity has changed, and\r
338 * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}\r
339 * if it has changed.\r
340 */\r
341 checkChange: function() {\r
342 var me = this,\r
343 newVal, oldVal;\r
344 \r
345 if (!me.suspendCheckChange) {\r
346 newVal = me.getValue();\r
347 oldVal = me.lastValue;\r
348 \r
349 if (!me.destroyed && me.didValueChange(newVal, oldVal)) {\r
350 me.lastValue = newVal;\r
351 me.fireEvent('change', me, newVal, oldVal);\r
352 me.onChange(newVal, oldVal);\r
353 }\r
354 }\r
355 },\r
356 \r
357 /**\r
358 * @private\r
359 * Checks if the value has changed. Allows subclasses to override for\r
360 * any more complex logic.\r
361 */\r
362 didValueChange: function(newVal, oldVal){\r
363 return !this.isEqual(newVal, oldVal);\r
364 },\r
365\r
366 /**\r
367 * @private\r
368 * Called when the field's value changes. Performs validation if the {@link #validateOnChange}\r
369 * config is enabled, and invokes the dirty check.\r
370 */\r
371 onChange: function (newVal) {\r
372 var me = this;\r
373\r
374 if (me.validateOnChange) {\r
375 me.validate();\r
376 }\r
377\r
378 me.checkDirty();\r
379 },\r
380\r
381 /**\r
382 * Publish the value of this field.\r
383 *\r
384 * @private\r
385 */\r
386 publishValue: function () {\r
387 var me = this;\r
388\r
389 if (me.rendered && !me.getErrors().length) {\r
390 me.publishState('value', me.getValue());\r
391 }\r
392 },\r
393\r
394 /**\r
395 * Returns true if the value of this Field has been changed from its {@link #originalValue}.\r
396 * Will always return false if the field is disabled.\r
397 *\r
398 * Note that if the owning {@link Ext.form.Basic form} was configured with\r
399 * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when\r
400 * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.\r
401 * @return {Boolean} True if this field has been changed from its original value (and is not disabled),\r
402 * false otherwise.\r
403 */\r
404 isDirty : function() {\r
405 var me = this;\r
406 return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);\r
407 },\r
408\r
409 /**\r
410 * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,\r
411 * fires the {@link #dirtychange} event.\r
412 */\r
413 checkDirty: function() {\r
414 var me = this,\r
415 isDirty = me.isDirty();\r
416 \r
417 if (isDirty !== me.wasDirty) {\r
418 me.dirty = isDirty;\r
419 me.fireEvent('dirtychange', me, isDirty);\r
420 me.onDirtyChange(isDirty);\r
421 me.wasDirty = isDirty;\r
422 }\r
423 },\r
424\r
425 /**\r
426 * @method\r
427 * @private\r
428 * Called when the field's dirty state changes.\r
429 * @param {Boolean} isDirty\r
430 */\r
431 onDirtyChange: Ext.emptyFn,\r
432\r
433 /**\r
434 * Runs this field's validators and returns an array of error messages for any validation failures. This is called\r
435 * internally during validation and would not usually need to be used manually.\r
436 *\r
437 * Each subclass should override or augment the return value to provide their own errors.\r
438 *\r
439 * @param {Object} value The value to get errors for (defaults to the current field value)\r
440 * @return {String[]} All error messages for this field; an empty Array if none.\r
441 */\r
442 getErrors: function (value) {\r
443 var errors = [],\r
444 validationField = this.getValidationField(),\r
445 validation = this.getValidation(),\r
446 result;\r
447\r
448 if (validationField) {\r
449 result = validationField.validate(value);\r
450 if (result !== true) {\r
451 errors.push(result);\r
452 }\r
453 }\r
454\r
455 if (validation && validation !== true) {\r
456 errors.push(validation);\r
457 }\r
458\r
459 return errors;\r
460 },\r
461\r
462 /**\r
463 * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current\r
464 * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event\r
465 * to fire. **Note**: {@link #disabled} fields are always treated as valid.\r
466 *\r
467 * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error\r
468 * message display.\r
469 *\r
470 * @return {Boolean} True if the value is valid, else false\r
471 */\r
472 isValid : function() {\r
473 var me = this;\r
474 return me.disabled || Ext.isEmpty(me.getErrors());\r
475 },\r
476\r
477 /**\r
478 * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current\r
479 * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.\r
480 * **Note**: {@link #disabled} fields are always treated as valid.\r
481 *\r
482 * Custom implementations of this method are allowed to have side-effects such as triggering error message display.\r
483 * To validate without side-effects, use {@link #isValid}.\r
484 *\r
485 * @return {Boolean} True if the value is valid, else false\r
486 */\r
487 validate : function() {\r
488 return this.checkValidityChange(this.isValid());\r
489 },\r
490\r
491 checkValidityChange: function(isValid) {\r
492 var me = this;\r
493\r
494 if (isValid !== me.wasValid) {\r
495 me.wasValid = isValid;\r
496 me.fireEvent('validitychange', me, isValid);\r
497 }\r
498 return isValid;\r
499 },\r
500\r
501 /**\r
502 * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to\r
503 * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which\r
504 * are being updated as a group; you don't want the container field to check its own changed state for each subfield\r
505 * change.\r
506 * @param {Function} fn The function to call with change checks suspended.\r
507 */\r
508 batchChanges: function(fn) {\r
509 try {\r
510 this.suspendCheckChange++;\r
511 fn();\r
512 }\r
513 catch (pseudo) { //required with IE when using 'try'\r
514 throw pseudo;\r
515 }\r
516 finally {\r
517 this.suspendCheckChange--;\r
518 }\r
519 this.checkChange();\r
520 },\r
521\r
522 /**\r
523 * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for\r
524 * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If\r
525 * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file\r
526 * input element.\r
527 * @return {Boolean}\r
528 */\r
529 isFileUpload: function() {\r
530 return false;\r
531 },\r
532\r
533 /**\r
534 * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input\r
535 * DOM element holding the user's selected file. The input will be appended into the submission form and will not be\r
536 * returned, so this method should also create a replacement.\r
537 * @return {HTMLElement}\r
538 */\r
539 extractFileInput: function() {\r
540 return null;\r
541 },\r
542\r
543 /**\r
544 * @method\r
545 * Display one or more error messages associated with this field, using \r
546 * {@link Ext.form.Labelable#msgTarget} to determine how to display the messages and \r
547 * applying {@link Ext.form.Labelable#invalidCls} to the field's UI element.\r
548 *\r
549 * var formPanel = Ext.create('Ext.form.Panel', {\r
550 * title: 'Contact Info',\r
551 * width: 300,\r
552 * bodyPadding: 10,\r
553 * renderTo: Ext.getBody(),\r
554 * items: [{\r
555 * xtype: 'textfield',\r
556 * name: 'name',\r
557 * id: 'nameId',\r
558 * fieldLabel: 'Name'\r
559 * }],\r
560 * bbar: [{\r
561 * text: 'Mark both fields invalid',\r
562 * handler: function() {\r
563 * var nameField = formPanel.getForm().findField('name');\r
564 * nameField.markInvalid('Name invalid message');\r
565 * \r
566 * // multiple error string syntax\r
567 * // nameField.markInvalid(['First message', 'Second message']);\r
568 * }\r
569 * }]\r
570 * });\r
571 * \r
572 * **Note**: this method does not cause the Field's {@link #validate} or \r
573 * {@link #isValid} methods to return `false` if the value does _pass_ validation. \r
574 * So simply marking a Field as invalid will not prevent submission of forms\r
575 * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.\r
576 * \r
577 * @param {String/String[]} errors The validation message(s) to display.\r
578 */\r
579 markInvalid: Ext.emptyFn,\r
580\r
581 /**\r
582 * @method clearInvalid\r
583 * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to\r
584 * update the components rendering to clear any existing messages.\r
585 *\r
586 * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`\r
587 * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow\r
588 * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.\r
589 */\r
590 clearInvalid: Ext.emptyFn,\r
591\r
592 updateValidation: function(validation, oldValidation) {\r
593 // Only validate if the validation is changing, not when we initial set it,\r
594 // otherwise it will mark the field invalid as soon as it is bound.\r
595 if (oldValidation) {\r
596 this.validate(); \r
597 }\r
598 },\r
599\r
600 privates: {\r
601 resetToInitialValue: function() {\r
602 var me = this,\r
603 originalValue = me.originalValue;\r
604\r
605 me.originalValue = me.initialValue;\r
606 me.reset();\r
607 me.originalValue = originalValue;\r
608 }\r
609 }\r
610});\r