Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / form / field / Base.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  * @class Ext.form.field.Base
17  * @extends Ext.Component
18
19 Base class for form fields that provides default event handling, rendering, and other common functionality
20 needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
21 and the {@link Ext.form.Labelable} mixin to provide label and error message display.
22
23 In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
24 rather than creating instances of this class directly. However if you are implementing a custom form field,
25 using this as the parent class is recommended.
26
27 __Values and Conversions__
28
29 Because BaseField implements the Field mixin, it has a main value that can be initialized with the
30 {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
31 value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
32 field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
33 input, this value data type can not always be directly used in the rendered field.
34
35 Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
36 and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
37 work with the raw value, though it is recommended to use getValue and setValue in most cases.
38
39 Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
40 {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
41 should override these methods to handle the conversion.
42
43 __Rendering__
44
45 The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
46 created by the {@link #getSubTplData} method. Override this template and/or method to create custom
47 field renderings.
48 {@img Ext.form.BaseField/Ext.form.BaseField.png Ext.form.BaseField BaseField component}
49 __Example usage:__
50
51     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
52     // searchUrl when the Enter key is pressed.
53     Ext.define('Ext.form.SearchField', {
54         extend: 'Ext.form.field.Base',
55         alias: 'widget.searchfield',
56     
57         inputType: 'search',
58     
59         // Config defining the search URL
60         searchUrl: 'http://www.google.com/search?q={0}',
61     
62         // Add specialkey listener
63         initComponent: function() {
64             this.callParent();
65             this.on('specialkey', this.checkEnterKey, this);
66         },
67     
68         // Handle enter key presses, execute the search if the field has a value
69         checkEnterKey: function(field, e) {
70             var value = this.getValue();
71             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
72                 location.href = Ext.String.format(this.searchUrl, value);
73             }
74         }
75     });
76
77     Ext.create('Ext.form.Panel', {
78         title: 'BaseField Example',
79         bodyPadding: 5,
80         width: 250,
81                 
82         // Fields will be arranged vertically, stretched to full width
83         layout: 'anchor',
84         defaults: {
85             anchor: '100%'
86         },
87         items: [{
88             xtype: 'searchfield',
89             fieldLabel: 'Search',
90             name: 'query'
91         }]
92         renderTo: Ext.getBody()
93     });
94
95  *
96  * @markdown
97  * @docauthor Jason Johnston <jason@sencha.com>
98  */
99 Ext.define('Ext.form.field.Base', {
100     extend: 'Ext.Component',
101     mixins: {
102         labelable: 'Ext.form.Labelable',
103         field: 'Ext.form.field.Field'
104     },
105     alias: 'widget.field',
106     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
107     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
108
109     fieldSubTpl: [
110         '<input id="{id}" type="{type}" ',
111         '<tpl if="name">name="{name}" </tpl>',
112         '<tpl if="size">size="{size}" </tpl>',
113         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
114         'class="{fieldCls} {typeCls}" autocomplete="off" />',
115         {
116             compiled: true,
117             disableFormats: true
118         }
119     ],
120
121     /**
122      * @cfg {String} name The name of the field (defaults to undefined). This is used as the parameter
123      * name when including the field value in a {@link Ext.form.Basic#submit form submit()}. If no name is
124      * configured, it falls back to the {@link #inputId}. To prevent the field from being included in the
125      * form submit, set {@link #submitValue} to <tt>false</tt>.
126      */
127
128     /**
129      * @cfg {String} inputType
130      * <p>The type attribute for input fields -- e.g. radio, text, password, file (defaults to <tt>'text'</tt>).
131      * The extended types supported by HTML5 inputs (url, email, etc.) may also be used, though using them
132      * will cause older browsers to fall back to 'text'.</p>
133      * <p>The type 'password' must be used to render that field type currently -- there is no separate Ext
134      * component for that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload
135      * field, but if you want a plain unstyled file input you can use a BaseField with inputType:'file'.</p>
136      */
137     inputType: 'text',
138
139     /**
140      * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered,
141      * not those which are built via applyTo (defaults to undefined).
142      */
143
144     /**
145      * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided
146      * (defaults to 'The value in this field is invalid')
147      */
148     invalidText : 'The value in this field is invalid',
149
150     /**
151      * @cfg {String} fieldCls The default CSS class for the field input (defaults to 'x-form-field')
152      */
153     fieldCls : Ext.baseCSSPrefix + 'form-field',
154
155     /**
156      * @cfg {String} fieldStyle Optional CSS style(s) to be applied to the {@link #inputEl field input element}.
157      * Should be a valid argument to {@link Ext.core.Element#applyStyles}. Defaults to undefined. See also the
158      * {@link #setFieldStyle} method for changing the style after initialization.
159      */
160
161     /**
162      * @cfg {String} focusCls The CSS class to use when the field receives focus (defaults to 'x-form-focus')
163      */
164     focusCls : Ext.baseCSSPrefix + 'form-focus',
165
166     /**
167      * @cfg {String} dirtyCls The CSS class to use when the field value {@link #isDirty is dirty}.
168      */
169     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
170
171     /**
172      * @cfg {Array} checkChangeEvents
173      * <p>A list of event names that will be listened for on the field's {@link #inputEl input element}, which
174      * will cause the field's value to be checked for changes. If a change is detected, the
175      * {@link #change change event} will be fired, followed by validation if the {@link #validateOnChange}
176      * option is enabled.</p>
177      * <p>Defaults to <tt>['change', 'propertychange']</tt> in Internet Explorer, and <tt>['change', 'input',
178      * 'textInput', 'keyup', 'dragdrop']</tt> in other browsers. This catches all the ways that field values
179      * can be changed in most supported browsers; the only known exceptions at the time of writing are:</p>
180      * <ul>
181      * <li>Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas</li>
182      * <li>Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text
183      * fields and textareas</li>
184      * <li>Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas</li>
185      * </ul>
186      * <p>If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
187      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is
188      * within a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges}
189      * configuration to set up such a task automatically.</p>
190      */
191     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
192                         ['change', 'propertychange'] :
193                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
194
195     /**
196      * @cfg {Number} checkChangeBuffer
197      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
198      * Defaults to 50 milliseconds.
199      */
200     checkChangeBuffer: 50,
201
202     componentLayout: 'field',
203
204     /**
205      * @cfg {Boolean} readOnly <tt>true</tt> to mark the field as readOnly in HTML
206      * (defaults to <tt>false</tt>).
207      * <br><p><b>Note</b>: this only sets the element's readOnly DOM attribute.
208      * Setting <code>readOnly=true</code>, for example, will not disable triggering a
209      * ComboBox or Date; it gives you the option of forcing the user to choose
210      * via the trigger without typing in the text box. To hide the trigger use
211      * <code>{@link Ext.form.field.Trigger#hideTrigger hideTrigger}</code>.</p>
212      */
213     readOnly : false,
214
215     /**
216      * @cfg {String} readOnlyCls The CSS class applied to the component's main element when it is {@link #readOnly}.
217      */
218     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
219
220     /**
221      * @cfg {String} inputId
222      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id.
223      * If you configure this manually, you must make sure it is unique in the document.
224      */
225
226     /**
227      * @cfg {Boolean} validateOnBlur
228      * Whether the field should validate when it loses focus (defaults to <tt>true</tt>). This will cause fields
229      * to be validated as the user steps through the fields in the form regardless of whether they are making
230      * changes to those fields along the way. See also {@link #validateOnChange}.
231      */
232     validateOnBlur: true,
233
234     // private
235     hasFocus : false,
236     
237     baseCls: Ext.baseCSSPrefix + 'field',
238     
239     maskOnDisable: false,
240
241     // private
242     initComponent : function() {
243         var me = this;
244
245         me.callParent();
246
247         me.subTplData = me.subTplData || {};
248
249         me.addEvents(
250             /**
251              * @event focus
252              * Fires when this field receives input focus.
253              * @param {Ext.form.field.Base} this
254              */
255             'focus',
256             /**
257              * @event blur
258              * Fires when this field loses input focus.
259              * @param {Ext.form.field.Base} this
260              */
261             'blur',
262             /**
263              * @event specialkey
264              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.
265              * To handle other keys see {@link Ext.panel.Panel#keys} or {@link Ext.util.KeyMap}.
266              * You can check {@link Ext.EventObject#getKey} to determine which key was pressed.
267              * For example: <pre><code>
268 var form = new Ext.form.Panel({
269     ...
270     items: [{
271             fieldLabel: 'Field 1',
272             name: 'field1',
273             allowBlank: false
274         },{
275             fieldLabel: 'Field 2',
276             name: 'field2',
277             listeners: {
278                 specialkey: function(field, e){
279                     // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
280                     // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
281                     if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
282                         var form = field.up('form').getForm();
283                         form.submit();
284                     }
285                 }
286             }
287         }
288     ],
289     ...
290 });
291              * </code></pre>
292              * @param {Ext.form.field.Base} this
293              * @param {Ext.EventObject} e The event object
294              */
295             'specialkey'
296         );
297
298         // Init mixins
299         me.initLabelable();
300         me.initField();
301
302         // Default name to inputId
303         if (!me.name) {
304             me.name = me.getInputId();
305         }
306     },
307
308     /**
309      * Returns the input id for this field. If none was specified via the {@link #inputId} config,
310      * then an id will be automatically generated.
311      */
312     getInputId: function() {
313         return this.inputId || (this.inputId = Ext.id());
314     },
315
316     /**
317      * @protected Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
318      * @return {Object} The template data
319      */
320     getSubTplData: function() {
321         var me = this,
322             type = me.inputType,
323             inputId = me.getInputId();
324
325         return Ext.applyIf(me.subTplData, {
326             id: inputId,
327             name: me.name || inputId,
328             type: type,
329             size: me.size || 20,
330             cls: me.cls,
331             fieldCls: me.fieldCls,
332             tabIdx: me.tabIndex,
333             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
334         });
335     },
336
337     /**
338      * @protected
339      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the
340      * actual input element.
341      */
342     getSubTplMarkup: function() {
343         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
344     },
345
346     initRenderTpl: function() {
347         var me = this;
348         if (!me.hasOwnProperty('renderTpl')) {
349             me.renderTpl = me.getTpl('labelableRenderTpl');
350         }
351         return me.callParent();
352     },
353
354     initRenderData: function() {
355         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
356     },
357
358     /**
359      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
360      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to
361      * {@link Ext.core.Element#applyStyles}.
362      */
363     setFieldStyle: function(style) {
364         var me = this,
365             inputEl = me.inputEl;
366         if (inputEl) {
367             inputEl.applyStyles(style);
368         }
369         me.fieldStyle = style;
370     },
371
372     // private
373     onRender : function() {
374         var me = this,
375             fieldStyle = me.fieldStyle,
376             renderSelectors = me.renderSelectors;
377
378         Ext.applyIf(renderSelectors, me.getLabelableSelectors());
379
380         Ext.applyIf(renderSelectors, {
381             /**
382              * @property inputEl
383              * @type Ext.core.Element
384              * The input Element for this Field. Only available after the field has been rendered.
385              */
386             inputEl: '.' + me.fieldCls
387         });
388
389         me.callParent(arguments);
390
391         // Make the stored rawValue get set as the input element's value
392         me.setRawValue(me.rawValue);
393
394         if (me.readOnly) {
395             me.setReadOnly(true);
396         }
397         if (me.disabled) {
398             me.disable();
399         }
400         if (fieldStyle) {
401             me.setFieldStyle(fieldStyle);
402         }
403
404         me.renderActiveError();
405     },
406
407     initAria: function() {
408         var me = this;
409         me.callParent();
410
411         // Associate the field to the error message element
412         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
413     },
414
415     getFocusEl: function() {
416         return this.inputEl;
417     },
418
419     isFileUpload: function() {
420         return this.inputType === 'file';
421     },
422
423     extractFileInput: function() {
424         var me = this,
425             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
426             clone;
427         if (fileInput) {
428             clone = fileInput.cloneNode(true);
429             fileInput.parentNode.replaceChild(clone, fileInput);
430             me.inputEl = Ext.get(clone);
431         }
432         return fileInput;
433     },
434
435     // private override to use getSubmitValue() as a convenience
436     getSubmitData: function() {
437         var me = this,
438             data = null,
439             val;
440         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
441             val = me.getSubmitValue();
442             if (val !== null) {
443                 data = {};
444                 data[me.getName()] = val;
445             }
446         }
447         return data;
448     },
449
450     /**
451      * <p>Returns the value that would be included in a standard form submit for this field. This will be combined
452      * with the field's name to form a <tt>name=value</tt> pair in the {@link #getSubmitData submitted parameters}.
453      * If an empty string is returned then just the <tt>name=</tt> will be submitted; if <tt>null</tt> is returned
454      * then nothing will be submitted.</p>
455      * <p>Note that the value returned will have been {@link #processRawValue processed} but may or may not have
456      * been successfully {@link #validate validated}.</p>
457      * @return {String} The value to be submitted, or <tt>null</tt>.
458      */
459     getSubmitValue: function() {
460         return this.processRawValue(this.getRawValue());
461     },
462
463     /**
464      * Returns the raw value of the field, without performing any normalization, conversion, or validation.
465      * To get a normalized and converted value see {@link #getValue}.
466      * @return {String} value The raw String value of the field
467      */
468     getRawValue: function() {
469         var me = this,
470             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
471         me.rawValue = v;
472         return v;
473     },
474
475     /**
476      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
477      * validation. To set the value with these additional inspections see {@link #setValue}.
478      * @param {Mixed} value The value to set
479      * @return {Mixed} value The field value that is set
480      */
481     setRawValue: function(value) {
482         var me = this;
483         value = Ext.value(value, '');
484         me.rawValue = value;
485
486         // Some Field subclasses may not render an inputEl
487         if (me.inputEl) {
488             me.inputEl.dom.value = value;
489         }
490         return value;
491     },
492
493     /**
494      * <p>Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows
495      * controlling how value objects passed to {@link #setValue} are shown to the user, including localization.
496      * For instance, for a {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue}
497      * would be converted to a String for display in the field.</p>
498      * <p>See {@link #rawToValue} for the opposite conversion.</p>
499      * <p>The base implementation simply does a standard toString conversion, and converts
500      * {@link Ext#isEmpty empty values} to an empty string.</p>
501      * @param {Mixed} value The mixed-type value to convert to the raw representation.
502      * @return {Mixed} The converted raw value.
503      */
504     valueToRaw: function(value) {
505         return '' + Ext.value(value, '');
506     },
507
508     /**
509      * <p>Converts a raw input field value into a mixed-type value that is suitable for this particular field type.
510      * This allows controlling the normalization and conversion of user-entered values into field-type-appropriate
511      * values, e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.</p>
512      * <p>It is up to individual implementations to decide how to handle raw values that cannot be successfully
513      * converted to the desired object type.</p>
514      * <p>See {@link #valueToRaw} for the opposite conversion.</p>
515      * <p>The base implementation does no conversion, returning the raw value untouched.</p>
516      * @param {Mixed} rawValue
517      * @return {Mixed} The converted value.
518      */
519     rawToValue: function(rawValue) {
520         return rawValue;
521     },
522
523     /**
524      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion}
525      * and/or {@link #validate validation}, for instance stripping out ignored characters. In the base implementation
526      * it does nothing; individual subclasses may override this as needed.
527      * @param {Mixed} value The unprocessed string value
528      * @return {Mixed} The processed string value
529      */
530     processRawValue: function(value) {
531         return value;
532     },
533
534     /**
535      * Returns the current data value of the field. The type of value returned is particular to the type of the
536      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
537      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
538      * @return {Mixed} value The field value
539      */
540     getValue: function() {
541         var me = this,
542             val = me.rawToValue(me.processRawValue(me.getRawValue()));
543         me.value = val;
544         return val;
545     },
546
547     /**
548      * Sets a data value into the field and runs the change detection and validation. To set the value directly
549      * without these inspections see {@link #setRawValue}.
550      * @param {Mixed} value The value to set
551      * @return {Ext.form.field.Field} this
552      */
553     setValue: function(value) {
554         var me = this;
555         me.setRawValue(me.valueToRaw(value));
556         return me.mixins.field.setValue.call(me, value);
557     },
558
559
560     //private
561     onDisable: function() {
562         var me = this,
563             inputEl = me.inputEl;
564         me.callParent();
565         if (inputEl) {
566             inputEl.dom.disabled = true;
567         }
568     },
569
570     //private
571     onEnable: function() {
572         var me = this,
573             inputEl = me.inputEl;
574         me.callParent();
575         if (inputEl) {
576             inputEl.dom.disabled = false;
577         }
578     },
579
580     /**
581      * Sets the read only state of this field.
582      * @param {Boolean} readOnly Whether the field should be read only.
583      */
584     setReadOnly: function(readOnly) {
585         var me = this,
586             inputEl = me.inputEl;
587         if (inputEl) {
588             inputEl.dom.readOnly = readOnly;
589             inputEl.dom.setAttribute('aria-readonly', readOnly);
590         }
591         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
592         me.readOnly = readOnly;
593     },
594
595     // private
596     fireKey: function(e){
597         if(e.isSpecialKey()){
598             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
599         }
600     },
601
602     // private
603     initEvents : function(){
604         var me = this,
605             inputEl = me.inputEl,
606             onChangeTask,
607             onChangeEvent;
608         if (inputEl) {
609             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
610             me.mon(inputEl, 'focus', me.onFocus, me);
611
612             // standardise buffer across all browsers + OS-es for consistent event order.
613             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
614             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
615
616             // listen for immediate value changes
617             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
618             me.onChangeEvent = onChangeEvent = function() {
619                 onChangeTask.delay(me.checkChangeBuffer);
620             };
621             Ext.each(me.checkChangeEvents, function(eventName) {
622                 if (eventName === 'propertychange') {
623                     me.usesPropertychange = true;
624                 }
625                 me.mon(inputEl, eventName, onChangeEvent);
626             }, me);
627         }
628         me.callParent();
629     },
630
631     doComponentLayout: function() {
632         var me = this,
633             inputEl = me.inputEl,
634             usesPropertychange = me.usesPropertychange,
635             ename = 'propertychange',
636             onChangeEvent = me.onChangeEvent;
637
638         // In IE if propertychange is one of the checkChangeEvents, we need to remove
639         // the listener prior to layout and re-add it after, to prevent it from firing
640         // needlessly for attribute and style changes applied to the inputEl.
641         if (usesPropertychange) {
642             me.mun(inputEl, ename, onChangeEvent);
643         }
644         me.callParent(arguments);
645         if (usesPropertychange) {
646             me.mon(inputEl, ename, onChangeEvent);
647         }
648     },
649
650     // private
651     preFocus: Ext.emptyFn,
652
653     // private
654     onFocus: function() {
655         var me = this,
656             focusCls = me.focusCls,
657             inputEl = me.inputEl;
658         me.preFocus();
659         if (focusCls && inputEl) {
660             inputEl.addCls(focusCls);
661         }
662         if (!me.hasFocus) {
663             me.hasFocus = true;
664             me.fireEvent('focus', me);
665         }
666     },
667
668     // private
669     beforeBlur : Ext.emptyFn,
670
671     // private
672     onBlur : function(){
673         var me = this,
674             focusCls = me.focusCls,
675             inputEl = me.inputEl;
676         me.beforeBlur();
677         if (focusCls && inputEl) {
678             inputEl.removeCls(focusCls);
679         }
680         if (me.validateOnBlur) {
681             me.validate();
682         }
683         me.hasFocus = false;
684         me.fireEvent('blur', me);
685         me.postBlur();
686     },
687
688     // private
689     postBlur : Ext.emptyFn,
690
691
692     /**
693      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
694      * @param {Boolean} isDirty
695      */
696     onDirtyChange: function(isDirty) {
697         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
698     },
699
700
701     /**
702      * Returns whether or not the field value is currently valid by
703      * {@link #getErrors validating} the {@link #processRawValue processed raw value}
704      * of the field. <b>Note</b>: {@link #disabled} fields are always treated as valid.
705      * @return {Boolean} True if the value is valid, else false
706      */
707     isValid : function() {
708         var me = this;
709         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
710     },
711
712
713     /**
714      * <p>Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed
715      * to {@link #markInvalid} and false is returned, otherwise true is returned.</p>
716      * <p>Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
717      * onwards {@link #getErrors} should be overridden instead.</p>
718      * @param {Mixed} value The value to validate
719      * @return {Boolean} True if all validations passed, false if one or more failed
720      */
721     validateValue: function(value) {
722         var me = this,
723             errors = me.getErrors(value),
724             isValid = Ext.isEmpty(errors);
725         if (!me.preventMark) {
726             if (isValid) {
727                 me.clearInvalid();
728             } else {
729                 me.markInvalid(errors);
730             }
731         }
732
733         return isValid;
734     },
735
736     /**
737      * <p>Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
738      * display the messages and applying {@link #invalidCls} to the field's UI element.</p>
739      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
740      * return <code>false</code> if the value does <i>pass</i> validation. So simply marking a Field as invalid
741      * will not prevent submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
742      * option set.</p>
743      * @param {String/Array} errors The validation message(s) to display.
744      */
745     markInvalid : function(errors) {
746         // Save the message and fire the 'invalid' event
747         var me = this,
748             oldMsg = me.getActiveError();
749         me.setActiveErrors(Ext.Array.from(errors));
750         if (oldMsg !== me.getActiveError()) {
751             me.doComponentLayout();
752         }
753     },
754
755     /**
756      * <p>Clear any invalid styles/messages for this field.</p>
757      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
758      * return <code>true</code> if the value does not <i>pass</i> validation. So simply clearing a field's errors
759      * will not necessarily allow submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
760      * option set.</p>
761      */
762     clearInvalid : function() {
763         // Clear the message and fire the 'valid' event
764         var me = this,
765             hadError = me.hasActiveError();
766         me.unsetActiveError();
767         if (hadError) {
768             me.doComponentLayout();
769         }
770     },
771
772     /**
773      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
774      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
775      */
776     renderActiveError: function() {
777         var me = this,
778             hasError = me.hasActiveError();
779         if (me.inputEl) {
780             // Add/remove invalid class
781             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
782         }
783         me.mixins.labelable.renderActiveError.call(me);
784     },
785
786
787     getActionEl: function() {
788         return this.inputEl || this.el;
789     }
790
791 });
792