/** * @class Ext.form.BaseField * @extends Ext.Component *Base class for form fields that provides default event handling, rendering, and other common functionality * needed by all form field types. Utilizes the {@link Ext.form.Field} mixin for value handling and validation, * and the {@link Ext.form.Labelable} mixin to provide label and error message display.
*In most cases you will want to use a subclass, such as {@link Ext.form.Text} or {@link Ext.form.Checkbox}, * rather than using this class directly.
* * @constructor * Creates a new Field * @param {Object} config Configuration options * * @xtype field */ Ext.define('Ext.form.BaseField', { extend: 'Ext.Component', mixins: { labelable: 'Ext.form.Labelable', field: 'Ext.form.Field' }, alias: 'widget.field', requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.form.Field'], /** * @cfg {String} inputType *The type attribute for input fields -- e.g. radio, text, password, file (defaults to 'text'). * The extended types supported by HTML5 inputs (url, email, etc.) may also be used, though using them * will cause older browsers to fall back to 'text'.
*The types 'file' and 'password' must be used to render those field types currently -- there are * no separate Ext components for those.
*/ inputType: 'text', /** * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered, * not those which are built via applyTo (defaults to undefined). */ /** * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided * (defaults to 'The value in this field is invalid') */ invalidText : 'The value in this field is invalid', /** * @cfg {String} fieldCls The default CSS class for the field input (defaults to 'x-form-field') */ fieldCls : Ext.baseCSSPrefix + 'form-field', /** * @cfg {String} focusCls The CSS class to use when the field receives focus (defaults to 'x-form-focus') */ focusCls : Ext.baseCSSPrefix + 'form-focus', /** * @cfg {Array} checkChangeEvents *A list of event names that will be listened for on the field's {@link #inputEl input element}, which * will cause the field's value to be checked for changes. If a change is detected, the * {@link #change change event} will be fired, followed by validation if the {@link #validateOnChange} * option is enabled.
*Defaults to ['change', 'propertychange'] in Internet Explorer, and ['change', 'input', * 'textInput', 'keyup', 'dragdrop'] in other browsers. This catches all the ways that field values * can be changed in most supported browsers; the only known exceptions at the time of writing are:
*
If you need to guarantee on-the-fly change notifications including these edge cases, you can call the * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskMgr}, or if the field is * within a {@link Ext.form.FormPanel}, you can use the FormPanel's {@link Ext.form.FormPanel#pollForChanges} * configuration to set up such a task automatically.
*/ checkChangeEvents: Ext.isIE ? ['change', 'propertychange'] : ['change', 'input', 'textInput', 'keyup', 'dragdrop'], /** * @cfg {Number} checkChangeBuffer * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession. * Defaults to 50 milliseconds. */ checkChangeBuffer: 50, componentLayout: 'field', /** * @cfg {Boolean} readOnly true to mark the field as readOnly in HTML * (defaults to false). *Note: this only sets the element's readOnly DOM attribute.
* Setting readOnly=true
, for example, will not disable triggering a
* ComboBox or Date; it gives you the option of forcing the user to choose
* via the trigger without typing in the text box. To hide the trigger use
* {@link Ext.form.Trigger#hideTrigger hideTrigger}
.
var form = new Ext.form.FormPanel({
...
items: [{
fieldLabel: 'Field 1',
name: 'field1',
allowBlank: false
},{
fieldLabel: 'Field 2',
name: 'field2',
listeners: {
specialkey: function(field, e){
// e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
// e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
var form = field.ownerCt.getForm();
form.submit();
}
}
}
}
],
...
});
*
* @param {Ext.form.BaseField} this
* @param {Ext.EventObject} e The event object
*/
'specialkey'
);
// Init mixins
me.initLabelable();
me.initField();
},
/**
* Returns the input id for this field. If none was specified via the {@link #inputId} config,
* then an id will be automatically generated.
*/
getInputId: function() {
return this.inputId || (this.inputId = Ext.id());
},
getSubTplData: function() {
var me = this,
type = me.inputType,
inputId = me.getInputId();
return Ext.applyIf(me.subTplData, {
id: inputId,
name: me.name || inputId,
type: type,
size: me.size || 20,
cls: me.cls,
fieldCls: me.fieldCls,
tabIdx: me.tabIndex,
typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
});
},
/**
* @protected
* Gets the markup to be inserted into the outer template's bodyEl. For fields this is the
* actual input element.
*/
getSubTplMarkup: function() {
return this.fieldSubTpl.apply(this.getSubTplData());
},
initRenderTpl: function() {
var me = this;
if (!me.hasOwnProperty('renderTpl')) {
me.renderTpl = me.labelableRenderTpl;
}
return me.callParent();
},
initRenderData: function() {
return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
},
// private
onRender : function() {
var me = this,
renderSelectors = me.renderSelectors;
Ext.applyIf(renderSelectors, me.getLabelableSelectors());
Ext.applyIf(renderSelectors, {
/**
* @property inputEl
* @type Ext.core.Element
* The input Element for this Field. Only available after the field has been rendered.
*/
inputEl: '.' + me.fieldCls
});
me.callParent(arguments);
// Make the stored rawValue get set as the input element's value
me.setRawValue(me.rawValue);
if (me.readOnly) {
me.setReadOnly(true);
}
if (me.disabled) {
me.disable();
}
me.renderActiveError();
},
initAria: function() {
var me = this;
me.callParent();
// Associate the field to the error message element
me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
},
getFocusEl: function() {
return this.inputEl;
},
getSubmitValue: function() {
var me = this;
return (me.inputType === 'file') ? null : me.mixins.field.getSubmitValue.call(me);
},
getRawValue: function() {
var me = this,
v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
me.rawValue = v;
return v;
},
setRawValue: function(value) {
var me = this;
value = Ext.value(value, '');
me.rawValue = value;
// Some Field subclasses may not render an inputEl
if (me.inputEl) {
me.inputEl.dom.value = value;
}
return value;
},
//private
onDisable: function() {
var me = this,
inputEl = me.inputEl;
me.callParent();
if (inputEl) {
inputEl.dom.disabled = true;
}
},
//private
onEnable: function() {
var me = this,
inputEl = me.inputEl;
me.callParent();
if (inputEl) {
inputEl.dom.disabled = false;
}
},
/**
* Sets the read only state of this field.
* @param {Boolean} readOnly Whether the field should be read only.
*/
setReadOnly: function(readOnly) {
if (this.inputEl) {
this.inputEl.dom.readOnly = readOnly;
}
this.readOnly = readOnly;
},
// private
fireKey : function(e){
if(e.isSpecialKey()){
this.fireEvent('specialkey', this, e);
}
},
// private
initEvents : function(){
var me = this,
inputEl = me.inputEl,
onChangeTask,
onChangeEvent;
if (inputEl) {
me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey, me);
me.mon(inputEl, 'focus', me.onFocus, me);
// standardise buffer across all browsers + OS-es for consistent event order.
// (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
// listen for immediate value changes
onChangeTask = new Ext.util.DelayedTask(me.checkChange, me);
onChangeEvent = function() {
onChangeTask.delay(me.checkChangeBuffer);
};
Ext.each(me.checkChangeEvents, function(eventName) {
me.mon(inputEl, eventName, onChangeEvent);
}, me);
}
},
// private
preFocus: Ext.emptyFn,
// private
onFocus: function() {
var me = this,
focusCls = me.focusCls,
inputEl = me.inputEl;
me.preFocus();
if (focusCls && inputEl) {
inputEl.addCls(focusCls);
}
if (!me.hasFocus) {
me.hasFocus = true;
me.fireEvent('focus', me);
}
},
// private
beforeBlur : Ext.emptyFn,
// private
onBlur : function(){
var me = this,
focusCls = me.focusCls,
inputEl = me.inputEl;
me.beforeBlur();
if (focusCls && inputEl) {
inputEl.removeCls(focusCls);
}
me.hasFocus = false;
me.fireEvent('blur', me);
me.postBlur();
},
// private
postBlur : Ext.emptyFn,
/**
* Returns whether or not the field value is currently valid by
* {@link #getErrors validating} the {@link #processRawValue processed raw value}
* of the field. Note: {@link #disabled} fields are always treated as valid.
* @return {Boolean} True if the value is valid, else false
*/
isValid : function() {
var me = this;
return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()), true);
},
/**
* Validates the field value
* @return {Boolean} True if the value is valid, else false
*/
validate : function() {
var me = this;
return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
},
/**
* Uses {@link #getErrors} to build an array of validation errors. If any errors are found, {@link #markInvalid} * is called with the first and false is returned, otherwise true is returned.
*Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2 * onwards {@link #getErrors} should be overridden instead.
* @param {Mixed} value The value to validate * @param {Boolean} preventMark true to prevent marking the field invalid * @return {Boolean} True if all validations passed, false if one or more failed */ validateValue: function(value, preventMark) { var me = this, error = me.getErrors(value)[0], //currently we only show 1 error at a time for a field, so just use the first one undef, isValid = error === undef; if (!preventMark) { if (isValid) { me.clearInvalid(); } else { me.markInvalid(error); } } return isValid; }, /** *Display an error message associated with this field, using {@link #msgTarget} to determine how to * display the message and applying {@link #invalidCls} to the field's UI element.
*Note: this method does not cause the Field's {@link #validate} method to return false
* if the value does pass validation. So simply marking a Field as invalid will not prevent
* submission of forms submitted with the {@link Ext.form.action.Action.Submit#clientValidation} option set.