X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/src/widgets/form/Field.js diff --git a/src/widgets/form/Field.js b/src/widgets/form/Field.js new file mode 100644 index 00000000..26c13651 --- /dev/null +++ b/src/widgets/form/Field.js @@ -0,0 +1,640 @@ +/*! + * Ext JS Library 3.0.0 + * Copyright(c) 2006-2009 Ext JS, LLC + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.form.Field + * @extends Ext.BoxComponent + * Base class for form fields that provides default event handling, sizing, value handling and other functionality. + * @constructor + * Creates a new Field + * @param {Object} config Configuration options + * @xtype field + */ +Ext.form.Field = Ext.extend(Ext.BoxComponent, { + /** + * @cfg {String} inputType The type attribute for input fields -- e.g. radio, text, password, file (defaults + * to "text"). The types "file" and "password" must be used to render those field types currently -- there are + * no separate Ext components for those. Note that if you use inputType:'file', {@link #emptyText} + * is not supported and should be avoided. + */ + /** + * @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 {Mixed} value A value to initialize this field with (defaults to undefined). + */ + /** + * @cfg {String} name The field's HTML name attribute (defaults to ""). + * Note: this property must be set if this field is to be automatically included with + * {@link Ext.form.BasicForm#submit form submit()}. + */ + /** + * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to ""). + */ + + /** + * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid") + */ + invalidClass : "x-form-invalid", + /** + * @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} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus") + */ + focusClass : "x-form-focus", + /** + * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable + automatic validation (defaults to "keyup"). + */ + validationEvent : "keyup", + /** + * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true). + */ + validateOnBlur : true, + /** + * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation + * is initiated (defaults to 250) + */ + validationDelay : 250, + /** + * @cfg {String/Object} autoCreate

A {@link Ext.DomHelper DomHelper} element spec, or true for a default + * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. + * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:

+ *
{tag: "input", type: "text", size: "20", autocomplete: "off"}
+ */ + defaultAutoCreate : {tag: "input", type: "text", size: "20", autocomplete: "off"}, + /** + * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field") + */ + fieldClass : "x-form-field", + /** + * @cfg {String} msgTarget The location where error text should display. Should be one of the following values + * (defaults to 'qtip'): + *
+Value         Description
+-----------   ----------------------------------------------------------------------
+qtip          Display a quick tip when the user hovers over the field
+title         Display a default browser title attribute popup
+under         Add a block div beneath the field containing the error text
+side          Add an error icon to the right of the field with a popup on hover
+[element id]  Add the error text directly to the innerHTML of the specified element
+
+ */ + msgTarget : 'qtip', + /** + * @cfg {String} msgFx Experimental The effect used when displaying a validation message under the field + * (defaults to 'normal'). + */ + msgFx : 'normal', + /** + * @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 DateField; 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.TriggerField#hideTrigger hideTrigger}.

+ */ + readOnly : false, + /** + * @cfg {Boolean} disabled True to disable the field (defaults to false). + *

Be aware that conformant with the HTML specification, + * disabled Fields will not be {@link Ext.form.BasicForm#submit submitted}.

+ */ + disabled : false, + + // private + isFormField : true, + + // private + hasFocus : false, + + // private + initComponent : function(){ + Ext.form.Field.superclass.initComponent.call(this); + this.addEvents( + /** + * @event focus + * Fires when this field receives input focus. + * @param {Ext.form.Field} this + */ + 'focus', + /** + * @event blur + * Fires when this field loses input focus. + * @param {Ext.form.Field} this + */ + 'blur', + /** + * @event specialkey + * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. + * To handle other keys see {@link Ext.Panel#keys} or {@link Ext.KeyMap}. + * You can check {@link Ext.EventObject#getKey} to determine which key was pressed. + * For example:

+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.Field} this + * @param {Ext.EventObject} e The event object + */ + 'specialkey', + /** + * @event change + * Fires just before the field blurs if the field value has changed. + * @param {Ext.form.Field} this + * @param {Mixed} newValue The new value + * @param {Mixed} oldValue The original value + */ + 'change', + /** + * @event invalid + * Fires after the field has been marked as invalid. + * @param {Ext.form.Field} this + * @param {String} msg The validation message + */ + 'invalid', + /** + * @event valid + * Fires after the field has been validated with no errors. + * @param {Ext.form.Field} this + */ + 'valid' + ); + }, + + /** + * Returns the {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName} + * attribute of the field if available. + * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName} + */ + getName: function(){ + return this.rendered && this.el.dom.name ? this.el.dom.name : this.name || this.id || ''; + }, + + // private + onRender : function(ct, position){ + if(!this.el){ + var cfg = this.getAutoCreate(); + + if(!cfg.name){ + cfg.name = this.name || this.id; + } + if(this.inputType){ + cfg.type = this.inputType; + } + this.autoEl = cfg; + } + Ext.form.Field.superclass.onRender.call(this, ct, position); + + var type = this.el.dom.type; + if(type){ + if(type == 'password'){ + type = 'text'; + } + this.el.addClass('x-form-'+type); + } + if(this.readOnly){ + this.el.dom.readOnly = true; + } + if(this.tabIndex !== undefined){ + this.el.dom.setAttribute('tabIndex', this.tabIndex); + } + + this.el.addClass([this.fieldClass, this.cls]); + }, + + // private + getItemCt : function(){ + return this.el.up('.x-form-item', 4); + }, + + // private + initValue : function(){ + if(this.value !== undefined){ + this.setValue(this.value); + }else if(!Ext.isEmpty(this.el.dom.value) && this.el.dom.value != this.emptyText){ + this.setValue(this.el.dom.value); + } + /** + * The original value of the field as configured in the {@link #value} configuration, or + * as loaded by the last form load operation if the form's {@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad} + * setting is true. + * @type mixed + * @property originalValue + */ + this.originalValue = this.getValue(); + }, + + /** + *

Returns true if the value of this Field has been changed from its original value. + * Will return false if the field is disabled or has not been rendered yet.

+ *

Note that if the owning {@link Ext.form.BasicForm form} was configured with + * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad} + * then the original value is updated when the values are loaded by + * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#setValues setValues}.

+ * @return {Boolean} True if this field has been changed from its original value (and + * is not disabled), false otherwise. + */ + isDirty : function() { + if(this.disabled || !this.rendered) { + return false; + } + return String(this.getValue()) !== String(this.originalValue); + }, + + // private + afterRender : function(){ + Ext.form.Field.superclass.afterRender.call(this); + this.initEvents(); + this.initValue(); + }, + + // private + fireKey : function(e){ + if(e.isSpecialKey()){ + this.fireEvent("specialkey", this, e); + } + }, + + /** + * Resets the current field value to the originally loaded value and clears any validation messages. + * See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad} + */ + reset : function(){ + this.setValue(this.originalValue); + this.clearInvalid(); + }, + + // private + initEvents : function(){ + this.mon(this.el, Ext.EventManager.useKeydown ? "keydown" : "keypress", this.fireKey, this); + this.mon(this.el, 'focus', this.onFocus, this); + + // fix weird FF/Win editor issue when changing OS window focus + var o = this.inEditor && Ext.isWindows && Ext.isGecko ? {buffer:10} : null; + this.mon(this.el, 'blur', this.onBlur, this, o); + }, + + // private + onFocus : function(){ + if(this.focusClass){ + this.el.addClass(this.focusClass); + } + if(!this.hasFocus){ + this.hasFocus = true; + this.startValue = this.getValue(); + this.fireEvent("focus", this); + } + }, + + // private + beforeBlur : Ext.emptyFn, + + // private + onBlur : function(){ + this.beforeBlur(); + if(this.focusClass){ + this.el.removeClass(this.focusClass); + } + this.hasFocus = false; + if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){ + this.validate(); + } + var v = this.getValue(); + if(String(v) !== String(this.startValue)){ + this.fireEvent('change', this, v, this.startValue); + } + this.fireEvent("blur", this); + }, + + /** + * Returns whether or not the field value is currently valid + * @param {Boolean} preventMark True to disable marking the field invalid + * @return {Boolean} True if the value is valid, else false + */ + isValid : function(preventMark){ + if(this.disabled){ + return true; + } + var restore = this.preventMark; + this.preventMark = preventMark === true; + var v = this.validateValue(this.processValue(this.getRawValue())); + this.preventMark = restore; + return v; + }, + + /** + * Validates the field value + * @return {Boolean} True if the value is valid, else false + */ + validate : function(){ + if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){ + this.clearInvalid(); + return true; + } + return false; + }, + + // protected - should be overridden by subclasses if necessary to prepare raw values for validation + processValue : function(value){ + return value; + }, + + // private + // Subclasses should provide the validation implementation by overriding this + validateValue : function(value){ + return true; + }, + + /** + * Mark this field as invalid, using {@link #msgTarget} to determine how to display the error and + * applying {@link #invalidClass} to the field's element. + * @param {String} msg (optional) The validation message (defaults to {@link #invalidText}) + */ + markInvalid : function(msg){ + if(!this.rendered || this.preventMark){ // not rendered + return; + } + msg = msg || this.invalidText; + + var mt = this.getMessageHandler(); + if(mt){ + mt.mark(this, msg); + }else if(this.msgTarget){ + this.el.addClass(this.invalidClass); + var t = Ext.getDom(this.msgTarget); + if(t){ + t.innerHTML = msg; + t.style.display = this.msgDisplay; + } + } + this.fireEvent('invalid', this, msg); + }, + + /** + * Clear any invalid styles/messages for this field + */ + clearInvalid : function(){ + if(!this.rendered || this.preventMark){ // not rendered + return; + } + this.el.removeClass(this.invalidClass); + var mt = this.getMessageHandler(); + if(mt){ + mt.clear(this); + }else if(this.msgTarget){ + this.el.removeClass(this.invalidClass); + var t = Ext.getDom(this.msgTarget); + if(t){ + t.innerHTML = ''; + t.style.display = 'none'; + } + } + this.fireEvent('valid', this); + }, + + // private + getMessageHandler : function(){ + return Ext.form.MessageTargets[this.msgTarget]; + }, + + // private + getErrorCt : function(){ + return this.el.findParent('.x-form-element', 5, true) || // use form element wrap if available + this.el.findParent('.x-form-field-wrap', 5, true); // else direct field wrap + }, + + // private + alignErrorIcon : function(){ + this.errorIcon.alignTo(this.el, 'tl-tr', [2, 0]); + }, + + /** + * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}. + * @return {Mixed} value The field value + */ + getRawValue : function(){ + var v = this.rendered ? this.el.getValue() : Ext.value(this.value, ''); + if(v === this.emptyText){ + v = ''; + } + return v; + }, + + /** + * Returns the normalized data value (undefined or emptyText will be returned as ''). To return the raw value see {@link #getRawValue}. + * @return {Mixed} value The field value + */ + getValue : function(){ + if(!this.rendered) { + return this.value; + } + var v = this.el.getValue(); + if(v === this.emptyText || v === undefined){ + v = ''; + } + return v; + }, + + /** + * Sets the underlying DOM field's value directly, bypassing validation. To set the value with validation see {@link #setValue}. + * @param {Mixed} value The value to set + * @return {Mixed} value The field value that is set + */ + setRawValue : function(v){ + return (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)); + }, + + /** + * Sets a data value into the field and validates it. To set the value directly without validation see {@link #setRawValue}. + * @param {Mixed} value The value to set + * @return {Ext.form.Field} this + */ + setValue : function(v){ + this.value = v; + if(this.rendered){ + this.el.dom.value = (Ext.isEmpty(v) ? '' : v); + this.validate(); + } + return this; + }, + + // private, does not work for all fields + append : function(v){ + this.setValue([this.getValue(), v].join('')); + }, + + // private + adjustSize : function(w, h){ + var s = Ext.form.Field.superclass.adjustSize.call(this, w, h); + s.width = this.adjustWidth(this.el.dom.tagName, s.width); + if(this.offsetCt){ + var ct = this.getItemCt(); + s.width -= ct.getFrameWidth('lr'); + s.height -= ct.getFrameWidth('tb'); + } + return s; + }, + + // private + adjustWidth : function(tag, w){ + if(typeof w == 'number' && (Ext.isIE && (Ext.isIE6 || !Ext.isStrict)) && /input|textarea/i.test(tag) && !this.inEditor){ + return w - 3; + } + return w; + } + + /** + * @cfg {Boolean} autoWidth @hide + */ + /** + * @cfg {Boolean} autoHeight @hide + */ + + /** + * @cfg {String} autoEl @hide + */ +}); + + +Ext.form.MessageTargets = { + 'qtip' : { + mark: function(field, msg){ + field.el.addClass(field.invalidClass); + field.el.dom.qtip = msg; + field.el.dom.qclass = 'x-form-invalid-tip'; + if(Ext.QuickTips){ // fix for floating editors interacting with DND + Ext.QuickTips.enable(); + } + }, + clear: function(field){ + field.el.removeClass(field.invalidClass); + field.el.dom.qtip = ''; + } + }, + 'title' : { + mark: function(field, msg){ + field.el.addClass(field.invalidClass); + field.el.dom.title = msg; + }, + clear: function(field){ + field.el.dom.title = ''; + } + }, + 'under' : { + mark: function(field, msg){ + field.el.addClass(field.invalidClass); + if(!field.errorEl){ + var elp = field.getErrorCt(); + if(!elp){ // field has no container el + field.el.dom.title = msg; + return; + } + field.errorEl = elp.createChild({cls:'x-form-invalid-msg'}); + field.errorEl.setWidth(elp.getWidth(true)-20); + } + field.errorEl.update(msg); + Ext.form.Field.msgFx[field.msgFx].show(field.errorEl, field); + }, + clear: function(field){ + field.el.removeClass(field.invalidClass); + if(field.errorEl){ + Ext.form.Field.msgFx[field.msgFx].hide(field.errorEl, field); + }else{ + field.el.dom.title = ''; + } + } + }, + 'side' : { + mark: function(field, msg){ + field.el.addClass(field.invalidClass); + if(!field.errorIcon){ + var elp = field.getErrorCt(); + if(!elp){ // field has no container el + field.el.dom.title = msg; + return; + } + field.errorIcon = elp.createChild({cls:'x-form-invalid-icon'}); + } + field.alignErrorIcon(); + field.errorIcon.dom.qtip = msg; + field.errorIcon.dom.qclass = 'x-form-invalid-tip'; + field.errorIcon.show(); + field.on('resize', field.alignErrorIcon, field); + }, + clear: function(field){ + field.el.removeClass(field.invalidClass); + if(field.errorIcon){ + field.errorIcon.dom.qtip = ''; + field.errorIcon.hide(); + field.un('resize', field.alignErrorIcon, field); + }else{ + field.el.dom.title = ''; + } + } + } +}; + +// anything other than normal should be considered experimental +Ext.form.Field.msgFx = { + normal : { + show: function(msgEl, f){ + msgEl.setDisplayed('block'); + }, + + hide : function(msgEl, f){ + msgEl.setDisplayed(false).update(''); + } + }, + + slide : { + show: function(msgEl, f){ + msgEl.slideIn('t', {stopFx:true}); + }, + + hide : function(msgEl, f){ + msgEl.slideOut('t', {stopFx:true,useDisplay:true}); + } + }, + + slideRight : { + show: function(msgEl, f){ + msgEl.fixDisplay(); + msgEl.alignTo(f.el, 'tl-tr'); + msgEl.slideIn('l', {stopFx:true}); + }, + + hide : function(msgEl, f){ + msgEl.slideOut('l', {stopFx:true,useDisplay:true}); + } + } +}; +Ext.reg('field', Ext.form.Field);