3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.form.field.Base
17 * @extends Ext.Component
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.
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.
27 __Values and Conversions__
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.
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.
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.
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
48 {@img Ext.form.BaseField/Ext.form.BaseField.png Ext.form.BaseField BaseField component}
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',
59 // Config defining the search URL
60 searchUrl: 'http://www.google.com/search?q={0}',
62 // Add specialkey listener
63 initComponent: function() {
65 this.on('specialkey', this.checkEnterKey, this);
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);
77 Ext.create('Ext.form.Panel', {
78 title: 'BaseField Example',
82 // Fields will be arranged vertically, stretched to full width
92 renderTo: Ext.getBody()
97 * @docauthor Jason Johnston <jason@sencha.com>
99 Ext.define('Ext.form.field.Base', {
100 extend: 'Ext.Component',
102 labelable: 'Ext.form.Labelable',
103 field: 'Ext.form.field.Field'
105 alias: 'widget.field',
106 alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
107 requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
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" />',
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>.
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>
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).
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')
148 invalidText : 'The value in this field is invalid',
151 * @cfg {String} fieldCls The default CSS class for the field input (defaults to 'x-form-field')
153 fieldCls : Ext.baseCSSPrefix + 'form-field',
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.
162 * @cfg {String} focusCls The CSS class to use when the field receives focus (defaults to 'x-form-focus')
164 focusCls : Ext.baseCSSPrefix + 'form-focus',
167 * @cfg {String} dirtyCls The CSS class to use when the field value {@link #isDirty is dirty}.
169 dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
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>
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>
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>
191 checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
192 ['change', 'propertychange'] :
193 ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
196 * @cfg {Number} checkChangeBuffer
197 * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
198 * Defaults to 50 milliseconds.
200 checkChangeBuffer: 50,
202 componentLayout: 'field',
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>
216 * @cfg {String} readOnlyCls The CSS class applied to the component's main element when it is {@link #readOnly}.
218 readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
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.
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}.
232 validateOnBlur: true,
237 baseCls: Ext.baseCSSPrefix + 'field',
239 maskOnDisable: false,
242 initComponent : function() {
247 me.subTplData = me.subTplData || {};
252 * Fires when this field receives input focus.
253 * @param {Ext.form.field.Base} this
258 * Fires when this field loses input focus.
259 * @param {Ext.form.field.Base} this
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({
271 fieldLabel: 'Field 1',
275 fieldLabel: 'Field 2',
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();
292 * @param {Ext.form.field.Base} this
293 * @param {Ext.EventObject} e The event object
302 // Default name to inputId
304 me.name = me.getInputId();
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.
312 getInputId: function() {
313 return this.inputId || (this.inputId = Ext.id());
317 * @protected Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
318 * @return {Object} The template data
320 getSubTplData: function() {
323 inputId = me.getInputId();
325 return Ext.applyIf(me.subTplData, {
327 name: me.name || inputId,
331 fieldCls: me.fieldCls,
333 typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
339 * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the
340 * actual input element.
342 getSubTplMarkup: function() {
343 return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
346 initRenderTpl: function() {
348 if (!me.hasOwnProperty('renderTpl')) {
349 me.renderTpl = me.getTpl('labelableRenderTpl');
351 return me.callParent();
354 initRenderData: function() {
355 return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
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}.
363 setFieldStyle: function(style) {
365 inputEl = me.inputEl;
367 inputEl.applyStyles(style);
369 me.fieldStyle = style;
373 onRender : function() {
375 fieldStyle = me.fieldStyle,
376 renderSelectors = me.renderSelectors;
378 Ext.applyIf(renderSelectors, me.getLabelableSelectors());
380 Ext.applyIf(renderSelectors, {
383 * @type Ext.core.Element
384 * The input Element for this Field. Only available after the field has been rendered.
386 inputEl: '.' + me.fieldCls
389 me.callParent(arguments);
391 // Make the stored rawValue get set as the input element's value
392 me.setRawValue(me.rawValue);
395 me.setReadOnly(true);
401 me.setFieldStyle(fieldStyle);
404 me.renderActiveError();
407 initAria: function() {
411 // Associate the field to the error message element
412 me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
415 getFocusEl: function() {
419 isFileUpload: function() {
420 return this.inputType === 'file';
423 extractFileInput: function() {
425 fileInput = me.isFileUpload() ? me.inputEl.dom : null,
428 clone = fileInput.cloneNode(true);
429 fileInput.parentNode.replaceChild(clone, fileInput);
430 me.inputEl = Ext.get(clone);
435 // private override to use getSubmitValue() as a convenience
436 getSubmitData: function() {
440 if (!me.disabled && me.submitValue && !me.isFileUpload()) {
441 val = me.getSubmitValue();
444 data[me.getName()] = val;
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>.
459 getSubmitValue: function() {
460 return this.processRawValue(this.getRawValue());
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
468 getRawValue: function() {
470 v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
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
481 setRawValue: function(value) {
483 value = Ext.value(value, '');
486 // Some Field subclasses may not render an inputEl
488 me.inputEl.dom.value = value;
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.
504 valueToRaw: function(value) {
505 return '' + Ext.value(value, '');
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.
519 rawToValue: function(rawValue) {
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
530 processRawValue: function(value) {
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
540 getValue: function() {
542 val = me.rawToValue(me.processRawValue(me.getRawValue()));
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
553 setValue: function(value) {
555 me.setRawValue(me.valueToRaw(value));
556 return me.mixins.field.setValue.call(me, value);
561 onDisable: function() {
563 inputEl = me.inputEl;
566 inputEl.dom.disabled = true;
571 onEnable: function() {
573 inputEl = me.inputEl;
576 inputEl.dom.disabled = false;
581 * Sets the read only state of this field.
582 * @param {Boolean} readOnly Whether the field should be read only.
584 setReadOnly: function(readOnly) {
586 inputEl = me.inputEl;
588 inputEl.dom.readOnly = readOnly;
589 inputEl.dom.setAttribute('aria-readonly', readOnly);
591 me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
592 me.readOnly = readOnly;
596 fireKey: function(e){
597 if(e.isSpecialKey()){
598 this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
603 initEvents : function(){
605 inputEl = me.inputEl,
609 me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey, me);
610 me.mon(inputEl, 'focus', me.onFocus, me);
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);
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);
621 Ext.each(me.checkChangeEvents, function(eventName) {
622 if (eventName === 'propertychange') {
623 me.usesPropertychange = true;
625 me.mon(inputEl, eventName, onChangeEvent);
631 doComponentLayout: function() {
633 inputEl = me.inputEl,
634 usesPropertychange = me.usesPropertychange,
635 ename = 'propertychange',
636 onChangeEvent = me.onChangeEvent;
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);
644 me.callParent(arguments);
645 if (usesPropertychange) {
646 me.mon(inputEl, ename, onChangeEvent);
651 preFocus: Ext.emptyFn,
654 onFocus: function() {
656 focusCls = me.focusCls,
657 inputEl = me.inputEl;
659 if (focusCls && inputEl) {
660 inputEl.addCls(focusCls);
664 me.fireEvent('focus', me);
669 beforeBlur : Ext.emptyFn,
674 focusCls = me.focusCls,
675 inputEl = me.inputEl;
677 if (focusCls && inputEl) {
678 inputEl.removeCls(focusCls);
680 if (me.validateOnBlur) {
684 me.fireEvent('blur', me);
689 postBlur : Ext.emptyFn,
693 * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
694 * @param {Boolean} isDirty
696 onDirtyChange: function(isDirty) {
697 this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
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
707 isValid : function() {
709 return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
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
721 validateValue: function(value) {
723 errors = me.getErrors(value),
724 isValid = Ext.isEmpty(errors);
725 if (!me.preventMark) {
729 me.markInvalid(errors);
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}
743 * @param {String/Array} errors The validation message(s) to display.
745 markInvalid : function(errors) {
746 // Save the message and fire the 'invalid' event
748 oldMsg = me.getActiveError();
749 me.setActiveErrors(Ext.Array.from(errors));
750 if (oldMsg !== me.getActiveError()) {
751 me.doComponentLayout();
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}
762 clearInvalid : function() {
763 // Clear the message and fire the 'valid' event
765 hadError = me.hasActiveError();
766 me.unsetActiveError();
768 me.doComponentLayout();
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)
776 renderActiveError: function() {
778 hasError = me.hasActiveError();
780 // Add/remove invalid class
781 me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
783 me.mixins.labelable.renderActiveError.call(me);
787 getActionEl: function() {
788 return this.inputEl || this.el;