Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / form / field / Field.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @docauthor Jason Johnston <jason@sencha.com>
17  *
18  * This mixin provides a common interface for the logical behavior and state of form fields, including:
19  *
20  * - Getter and setter methods for field values
21  * - Events and methods for tracking value and validity changes
22  * - Methods for triggering validation
23  *
24  * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
25  * component class rather than using this mixin directly, as BaseField contains additional logic for generating an
26  * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
27  * plus methods that bind the Field value getters and setters to the input field's value.
28  *
29  * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
30  * you will most likely want to override the following methods with custom implementations: {@link #getValue},
31  * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
32  * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
33  * is called during the component's initialization.
34  */
35 Ext.define('Ext.form.field.Field', {
36     /**
37      * @property {Boolean} isFormField
38      * Flag denoting that this component is a Field. Always true.
39      */
40     isFormField : true,
41
42     /**
43      * @cfg {Object} value
44      * A value to initialize this field with.
45      */
46
47     /**
48      * @cfg {String} name
49      * The name of the field. By default this is used as the parameter name when including the
50      * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from
51      * being included in the form submit, set {@link #submitValue} to false.
52      */
53
54     /**
55      * @cfg {Boolean} disabled
56      * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.
57      */
58     disabled : false,
59
60     /**
61      * @cfg {Boolean} submitValue
62      * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is
63      * not disabled.
64      */
65     submitValue: true,
66
67     /**
68      * @cfg {Boolean} validateOnChange
69      * Specifies whether this field should be validated immediately whenever a change in its value is detected.
70      * If the validation results in a change in the field's validity, a {@link #validitychange} event will be
71      * fired. This allows the field to show feedback about the validity of its contents immediately as the user is
72      * typing.
73      *
74      * When set to false, feedback will not be immediate. However the form will still be validated before submitting if
75      * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated
76      * manually.
77      *
78      * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are
79      * detected.
80      */
81     validateOnChange: true,
82
83     /**
84      * @private
85      */
86     suspendCheckChange: 0,
87
88     /**
89      * Initializes this Field mixin on the current instance. Components using this mixin should call this method during
90      * their own initialization process.
91      */
92     initField: function() {
93         this.addEvents(
94             /**
95              * @event change
96              * Fires when a user-initiated change is detected in the value of the field.
97              * @param {Ext.form.field.Field} this
98              * @param {Object} newValue The new value
99              * @param {Object} oldValue The original value
100              */
101             'change',
102             /**
103              * @event validitychange
104              * Fires when a change in the field's validity is detected.
105              * @param {Ext.form.field.Field} this
106              * @param {Boolean} isValid Whether or not the field is now valid
107              */
108             'validitychange',
109             /**
110              * @event dirtychange
111              * Fires when a change in the field's {@link #isDirty} state is detected.
112              * @param {Ext.form.field.Field} this
113              * @param {Boolean} isDirty Whether or not the field is now dirty
114              */
115             'dirtychange'
116         );
117
118         this.initValue();
119     },
120
121     /**
122      * Initializes the field's value based on the initial config.
123      */
124     initValue: function() {
125         var me = this;
126
127         /**
128          * @property {Object} originalValue
129          * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last
130          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
131          */
132         me.originalValue = me.lastValue = me.value;
133
134         // Set the initial value - prevent validation on initial set
135         me.suspendCheckChange++;
136         me.setValue(me.value);
137         me.suspendCheckChange--;
138     },
139
140     /**
141      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name
142      * when including the field value in a {@link Ext.form.Basic#submit form submit()}.
143      * @return {String} name The field {@link Ext.form.field.Field#name name}
144      */
145     getName: function() {
146         return this.name;
147     },
148
149     /**
150      * Returns the current data value of the field. The type of value returned is particular to the type of the
151      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
152      * @return {Object} value The field value
153      */
154     getValue: function() {
155         return this.value;
156     },
157
158     /**
159      * Sets a data value into the field and runs the change detection and validation.
160      * @param {Object} value The value to set
161      * @return {Ext.form.field.Field} this
162      */
163     setValue: function(value) {
164         var me = this;
165         me.value = value;
166         me.checkChange();
167         return me;
168     },
169
170     /**
171      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this
172      * to provide custom comparison logic appropriate for the particular field's data type.
173      * @param {Object} value1 The first value to compare
174      * @param {Object} value2 The second value to compare
175      * @return {Boolean} True if the values are equal, false if inequal.
176      */
177     isEqual: function(value1, value2) {
178         return String(value1) === String(value2);
179     },
180     
181     /**
182      * Returns whether two values are logically equal.
183      * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.
184      * @private
185      * @param {Object} value1 The first value to compare
186      * @param {Object} value2 The second value to compare
187      * @return {Boolean} True if the values are equal, false if inequal.
188      */
189     isEqualAsString: function(value1, value2){
190         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));    
191     },
192
193     /**
194      * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be
195      * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being
196      * its current stringified value. More advanced field implementations may return more than one name-value pair.
197      *
198      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
199      * validated}.
200      *
201      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
202      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
203      * submitted.
204      */
205     getSubmitData: function() {
206         var me = this,
207             data = null;
208         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
209             data = {};
210             data[me.getName()] = '' + me.getValue();
211         }
212         return data;
213     },
214
215     /**
216      * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link
217      * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name
218      * being this field's {@link #getName name} and the value being its current data value. More advanced field
219      * implementations may return more than one name-value pair. The returned values will be saved to the corresponding
220      * field names in the Model.
221      *
222      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
223      * validated}.
224      *
225      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
226      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
227      * submitted.
228      */
229     getModelData: function() {
230         var me = this,
231             data = null;
232         if (!me.disabled && !me.isFileUpload()) {
233             data = {};
234             data[me.getName()] = me.getValue();
235         }
236         return data;
237     },
238
239     /**
240      * Resets the current field value to the originally loaded value and clears any validation messages. See {@link
241      * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
242      */
243     reset : function(){
244         var me = this;
245
246         me.setValue(me.originalValue);
247         me.clearInvalid();
248         // delete here so we reset back to the original state
249         delete me.wasValid;
250     },
251
252     /**
253      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is
254      * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
255      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
256      */
257     resetOriginalValue: function() {
258         this.originalValue = this.getValue();
259         this.checkDirty();
260     },
261
262     /**
263      * Checks whether the value of the field has changed since the last time it was checked.
264      * If the value has changed, it:
265      *
266      * 1. Fires the {@link #change change event},
267      * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the
268      *    {@link #validitychange validitychange event} if the validity has changed, and
269      * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
270      *    if it has changed.
271      */
272     checkChange: function() {
273         if (!this.suspendCheckChange) {
274             var me = this,
275                 newVal = me.getValue(),
276                 oldVal = me.lastValue;
277             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
278                 me.lastValue = newVal;
279                 me.fireEvent('change', me, newVal, oldVal);
280                 me.onChange(newVal, oldVal);
281             }
282         }
283     },
284
285     /**
286      * @private
287      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
288      * config is enabled, and invokes the dirty check.
289      */
290     onChange: function(newVal, oldVal) {
291         if (this.validateOnChange) {
292             this.validate();
293         }
294         this.checkDirty();
295     },
296
297     /**
298      * Returns true if the value of this Field has been changed from its {@link #originalValue}.
299      * Will always return false if the field is disabled.
300      *
301      * Note that if the owning {@link Ext.form.Basic form} was configured with
302      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when
303      * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.
304      * @return {Boolean} True if this field has been changed from its original value (and is not disabled),
305      * false otherwise.
306      */
307     isDirty : function() {
308         var me = this;
309         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
310     },
311
312     /**
313      * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,
314      * fires the {@link #dirtychange} event.
315      */
316     checkDirty: function() {
317         var me = this,
318             isDirty = me.isDirty();
319         if (isDirty !== me.wasDirty) {
320             me.fireEvent('dirtychange', me, isDirty);
321             me.onDirtyChange(isDirty);
322             me.wasDirty = isDirty;
323         }
324     },
325
326     /**
327      * @private Called when the field's dirty state changes.
328      * @param {Boolean} isDirty
329      */
330     onDirtyChange: Ext.emptyFn,
331
332     /**
333      * Runs this field's validators and returns an array of error messages for any validation failures. This is called
334      * internally during validation and would not usually need to be used manually.
335      *
336      * Each subclass should override or augment the return value to provide their own errors.
337      *
338      * @param {Object} value The value to get errors for (defaults to the current field value)
339      * @return {String[]} All error messages for this field; an empty Array if none.
340      */
341     getErrors: function(value) {
342         return [];
343     },
344
345     /**
346      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
347      * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event
348      * to fire. **Note**: {@link #disabled} fields are always treated as valid.
349      *
350      * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error
351      * message display.
352      *
353      * @return {Boolean} True if the value is valid, else false
354      */
355     isValid : function() {
356         var me = this;
357         return me.disabled || Ext.isEmpty(me.getErrors());
358     },
359
360     /**
361      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
362      * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.
363      * **Note**: {@link #disabled} fields are always treated as valid.
364      *
365      * Custom implementations of this method are allowed to have side-effects such as triggering error message display.
366      * To validate without side-effects, use {@link #isValid}.
367      *
368      * @return {Boolean} True if the value is valid, else false
369      */
370     validate : function() {
371         var me = this,
372             isValid = me.isValid();
373         if (isValid !== me.wasValid) {
374             me.wasValid = isValid;
375             me.fireEvent('validitychange', me, isValid);
376         }
377         return isValid;
378     },
379
380     /**
381      * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to
382      * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which
383      * are being updated as a group; you don't want the container field to check its own changed state for each subfield
384      * change.
385      * @param {Object} fn A function containing the transaction code
386      */
387     batchChanges: function(fn) {
388         try {
389             this.suspendCheckChange++;
390             fn();
391         } catch(e){
392             throw e;
393         } finally {
394             this.suspendCheckChange--;
395         }
396         this.checkChange();
397     },
398
399     /**
400      * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for
401      * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If
402      * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file
403      * input element.
404      * @return {Boolean}
405      */
406     isFileUpload: function() {
407         return false;
408     },
409
410     /**
411      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input
412      * DOM element holding the user's selected file. The input will be appended into the submission form and will not be
413      * returned, so this method should also create a replacement.
414      * @return {HTMLElement}
415      */
416     extractFileInput: function() {
417         return null;
418     },
419
420     /**
421      * @method markInvalid
422      * Associate one or more error messages with this field. Components using this mixin should implement this method to
423      * update the component's rendering to display the messages.
424      *
425      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
426      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
427      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
428      *
429      * @param {String/String[]} errors The error message(s) for the field.
430      */
431     markInvalid: Ext.emptyFn,
432
433     /**
434      * @method clearInvalid
435      * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to
436      * update the components rendering to clear any existing messages.
437      *
438      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
439      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
440      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
441      */
442     clearInvalid: Ext.emptyFn
443
444 });
445