X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/form/field/Text.js diff --git a/src/form/field/Text.js b/src/form/field/Text.js new file mode 100644 index 00000000..f07090f1 --- /dev/null +++ b/src/form/field/Text.js @@ -0,0 +1,669 @@ +/** + * @class Ext.form.field.Text + * @extends Ext.form.field.Base + +A basic text field. Can be used as a direct replacement for traditional text inputs, +or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea} +and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}). + +#Validation# + +The Text field has a useful set of validations built in: + +- {@link #allowBlank} for making the field required +- {@link #minLength} for requiring a minimum value length +- {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it + as the `maxlength` attribute on the input element) +- {@link regex} to specify a custom regular expression for validation + +In addition, custom validations may be added: + +- {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain + custom validation logic +- {@link #validator} allows a custom arbitrary function to be called during validation + +The details around how and when each of these validation options get used are described in the +documentation for {@link #getErrors}. + +By default, the field value is checked for validity immediately while the user is typing in the +field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and +{@link #checkChangeBugger} configurations. Also see the details on Form Validation in the +{@link Ext.form.Panel} class documentation. + +#Masking and Character Stripping# + +Text fields can be configured with custom regular expressions to be applied to entered values before +validation: see {@link #maskRe} and {@link #stripCharsRe} for details. +{@img Ext.form.Text/Ext.form.Text.png Ext.form.Text component} +#Example usage:# + + Ext.create('Ext.form.Panel', { + title: 'Contact Info', + width: 300, + bodyPadding: 10, + renderTo: Ext.getBody(), + items: [{ + xtype: 'textfield', + name: 'name', + fieldLabel: 'Name', + allowBlank: false // requires a non-empty value + }, { + xtype: 'textfield', + name: 'email', + fieldLabel: 'Email Address', + vtype: 'email' // requires value to be a valid email address format + }] + }); + + * @constructor Creates a new TextField + * @param {Object} config Configuration options + * + * @xtype textfield + * @markdown + * @docauthor Jason Johnston + */ +Ext.define('Ext.form.field.Text', { + extend:'Ext.form.field.Base', + alias: 'widget.textfield', + requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'], + alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'], + + /** + * @cfg {String} vtypeText A custom error message to display in place of the default message provided + * for the {@link #vtype} currently set for this field (defaults to undefined). + * Note: only applies if {@link #vtype} is set, else ignored. + */ + + /** + * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value + * before validation (defaults to undefined). + */ + + /** + * @cfg {Number} size An initial value for the 'size' attribute on the text input element. This is only + * used if the field has no configured {@link #width} and is not given a width by its container's layout. + * Defaults to 20. + */ + size: 20, + + /** + * @cfg {Boolean} grow true if this field should automatically grow and shrink to its content + * (defaults to false) + */ + + /** + * @cfg {Number} growMin The minimum width to allow when {@link #grow} = true (defaults + * to 30) + */ + growMin : 30, + + /** + * @cfg {Number} growMax The maximum width to allow when {@link #grow} = true (defaults + * to 800) + */ + growMax : 800, + + /** + * @cfg {String} growAppend + * A string that will be appended to the field's current value for the purposes of calculating the target + * field size. Only used when the {@link #grow} config is true. Defaults to a single capital "W" + * (the widest character in common fonts) to leave enough space for the next typed character and avoid the + * field value shifting before the width is adjusted. + */ + growAppend: 'W', + + /** + * @cfg {String} vtype A validation type name as defined in {@link Ext.form.field.VTypes} (defaults to undefined) + */ + + /** + * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that do + * not match (defaults to undefined) + */ + + /** + * @cfg {Boolean} disableKeyFilter Specify true to disable input keystroke filtering (defaults + * to false) + */ + + /** + * @cfg {Boolean} allowBlank Specify false to validate that the value's length is > 0 (defaults to + * true) + */ + allowBlank : true, + + /** + * @cfg {Number} minLength Minimum input field length required (defaults to 0) + */ + minLength : 0, + + /** + * @cfg {Number} maxLength Maximum input field length allowed by validation (defaults to Number.MAX_VALUE). + * This behavior is intended to provide instant feedback to the user by improving usability to allow pasting + * and editing or overtyping and back tracking. To restrict the maximum number of characters that can be + * entered into the field use the {@link Ext.form.field.Text#enforceMaxLength enforceMaxLength} option. + */ + maxLength : Number.MAX_VALUE, + + /** + * @cfg {Boolean} enforceMaxLength True to set the maxLength property on the underlying input field. Defaults to false + */ + + /** + * @cfg {String} minLengthText Error text to display if the {@link #minLength minimum length} + * validation fails (defaults to 'The minimum length for this field is {minLength}') + */ + minLengthText : 'The minimum length for this field is {0}', + + /** + * @cfg {String} maxLengthText Error text to display if the {@link #maxLength maximum length} + * validation fails (defaults to 'The maximum length for this field is {maxLength}') + */ + maxLengthText : 'The maximum length for this field is {0}', + + /** + * @cfg {Boolean} selectOnFocus true to automatically select any existing field text when the field + * receives input focus (defaults to false) + */ + + /** + * @cfg {String} blankText The error text to display if the {@link #allowBlank} validation + * fails (defaults to 'This field is required') + */ + blankText : 'This field is required', + + /** + * @cfg {Function} validator + *

A custom validation function to be called during field validation ({@link #getErrors}) + * (defaults to undefined). If specified, this function will be called first, allowing the + * developer to override the default validation process.

+ *

This function will be passed the following Parameters:

+ *
+ *

This function is to Return:

+ *
+ */ + + /** + * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation + * (defaults to undefined). If the test fails, the field will be marked invalid using + * {@link #regexText}. + */ + + /** + * @cfg {String} regexText The error text to display if {@link #regex} is used and the + * test fails during validation (defaults to '') + */ + regexText : '', + + /** + * @cfg {String} emptyText + *

The default text to place into an empty field (defaults to undefined).

+ *

Note that normally this value will be submitted to the server if this field is enabled; to prevent this + * you can set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of + * {@link Ext.form.Basic#submit} to false.

+ *

Also note that if you use {@link #inputType inputType}:'file', {@link #emptyText} is not + * supported and should be avoided.

+ */ + + /** + * @cfg {String} emptyCls The CSS class to apply to an empty field to style the {@link #emptyText} + * (defaults to 'x-form-empty-field'). This class is automatically added and removed as needed + * depending on the current field value. + */ + emptyCls : Ext.baseCSSPrefix + 'form-empty-field', + + ariaRole: 'textbox', + + /** + * @cfg {Boolean} enableKeyEvents true to enable the proxying of key events for the HTML input field (defaults to false) + */ + + componentLayout: 'textfield', + + initComponent : function(){ + this.callParent(); + this.addEvents( + /** + * @event autosize + * Fires when the {@link #autoSize} function is triggered and the field is + * resized according to the {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result. + * This event provides a hook for the developer to apply additional logic at runtime to resize the + * field if needed. + * @param {Ext.form.field.Text} this This text field + * @param {Number} width The new field width + */ + 'autosize', + + /** + * @event keydown + * Keydown input field event. This event only fires if {@link #enableKeyEvents} + * is set to true. + * @param {Ext.form.field.Text} this This text field + * @param {Ext.EventObject} e + */ + 'keydown', + /** + * @event keyup + * Keyup input field event. This event only fires if {@link #enableKeyEvents} + * is set to true. + * @param {Ext.form.field.Text} this This text field + * @param {Ext.EventObject} e + */ + 'keyup', + /** + * @event keypress + * Keypress input field event. This event only fires if {@link #enableKeyEvents} + * is set to true. + * @param {Ext.form.field.Text} this This text field + * @param {Ext.EventObject} e + */ + 'keypress' + ); + }, + + // private + initEvents : function(){ + var me = this, + el = me.inputEl; + + me.callParent(); + if(me.selectOnFocus || me.emptyText){ + me.mon(el, 'mousedown', me.onMouseDown, me); + } + if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){ + me.mon(el, 'keypress', me.filterKeys, me); + } + + if (me.enableKeyEvents) { + me.mon(el, { + scope: me, + keyup: me.onKeyUp, + keydown: me.onKeyDown, + keypress: me.onKeyPress + }); + } + }, + + /** + * @private override - treat undefined and null values as equal to an empty string value + */ + isEqual: function(value1, value2) { + return String(Ext.value(value1, '')) === String(Ext.value(value2, '')); + }, + + /** + * @private + * If grow=true, invoke the autoSize method when the field's value is changed. + */ + onChange: function() { + this.callParent(); + this.autoSize(); + }, + + afterRender: function(){ + var me = this; + if (me.enforceMaxLength) { + me.inputEl.dom.maxLength = me.maxLength; + } + me.applyEmptyText(); + me.autoSize(); + me.callParent(); + }, + + onMouseDown: function(e){ + var me = this; + if(!me.hasFocus){ + me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true }); + } + }, + + /** + * Performs any necessary manipulation of a raw String value to prepare it for {@link #stringToValue conversion} + * and/or {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe} to the + * raw value. + * @param {String} value The unprocessed string value + * @return {String} The processed string value + */ + processRawValue: function(value) { + var me = this, + stripRe = me.stripCharsRe, + newValue; + + if (stripRe) { + newValue = value.replace(stripRe, ''); + if (newValue !== value) { + me.setRawValue(newValue); + value = newValue; + } + } + return value; + }, + + //private + onDisable: function(){ + this.callParent(); + if (Ext.isIE) { + this.inputEl.dom.unselectable = 'on'; + } + }, + + //private + onEnable: function(){ + this.callParent(); + if (Ext.isIE) { + this.inputEl.dom.unselectable = ''; + } + }, + + onKeyDown: function(e) { + this.fireEvent('keydown', this, e); + }, + + onKeyUp: function(e) { + this.fireEvent('keyup', this, e); + }, + + onKeyPress: function(e) { + this.fireEvent('keypress', this, e); + }, + + /** + * Resets the current field value to the originally-loaded value and clears any validation messages. + * Also adds {@link #emptyText} and {@link #emptyCls} if the + * original value was blank. + */ + reset : function(){ + this.callParent(); + this.applyEmptyText(); + }, + + applyEmptyText : function(){ + var me = this, + emptyText = me.emptyText, + isEmpty; + + if (me.rendered && emptyText) { + isEmpty = me.getRawValue().length < 1 && !me.hasFocus; + + if (Ext.supports.Placeholder) { + me.inputEl.dom.placeholder = emptyText; + } else if (isEmpty) { + me.setRawValue(emptyText); + } + + //all browsers need this because of a styling issue with chrome + placeholders. + //the text isnt vertically aligned when empty (and using the placeholder) + if (isEmpty) { + me.inputEl.addCls(me.emptyCls); + } + + me.autoSize(); + } + }, + + // private + preFocus : function(){ + var me = this, + inputEl = me.inputEl, + emptyText = me.emptyText, + isEmpty; + + if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) { + me.setRawValue(''); + isEmpty = true; + inputEl.removeCls(me.emptyCls); + } else if (Ext.supports.Placeholder) { + me.inputEl.removeCls(me.emptyCls); + } + if (me.selectOnFocus || isEmpty) { + inputEl.dom.select(); + } + }, + + onFocus: function() { + var me = this; + me.callParent(arguments); + if (me.emptyText) { + me.autoSize(); + } + }, + + // private + postBlur : function(){ + this.applyEmptyText(); + }, + + // private + filterKeys : function(e){ + if(e.ctrlKey){ + return; + } + var key = e.getKey(), + charCode = String.fromCharCode(e.getCharCode()); + + if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){ + return; + } + + if(!Ext.isGecko && e.isSpecialKey() && !charCode){ + return; + } + if(!this.maskRe.test(charCode)){ + e.stopEvent(); + } + }, + + /** + * Returns the raw String value of the field, without performing any normalization, conversion, or validation. + * Gets the current value of the input element if the field has been rendered, ignoring the value if it is the + * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}. + * @return {String} value The raw String value of the field + */ + getRawValue: function() { + var me = this, + v = me.callParent(); + if (v === me.emptyText) { + v = ''; + } + return v; + }, + + /** + * Sets a data value into the field and runs the change detection and validation. Also applies any configured + * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}. + * @param {Mixed} value The value to set + * @return {Ext.form.field.Text} this + */ + setValue: function(value) { + var me = this, + inputEl = me.inputEl; + + if (inputEl && me.emptyText && !Ext.isEmpty(value)) { + inputEl.removeCls(me.emptyCls); + } + + me.callParent(arguments); + + me.applyEmptyText(); + return me; + }, + + /** +Validates a value according to the field's validation rules and returns an array of errors +for any failing validations. Validation rules are processed in the following order: + +1. **Field specific validator** + + A validator offers a way to customize and reuse a validation specification. + If a field is configured with a `{@link #validator}` + function, it will be passed the current field value. The `{@link #validator}` + function is expected to return either: + + - Boolean `true` if the value is valid (validation continues). + - a String to represent the invalid message if invalid (validation halts). + +2. **Basic Validation** + + If the `{@link #validator}` has not halted validation, + basic validation proceeds as follows: + + - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`) + + Depending on the configuration of {@link #allowBlank}, a + blank field will cause validation to halt at this step and return + Boolean true or false accordingly. + + - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`) + + If the passed value does not satisfy the `{@link #minLength}` + specified, validation halts. + + - `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`) + + If the passed value does not satisfy the `{@link #maxLength}` + specified, validation halts. + +3. **Preconfigured Validation Types (VTypes)** + + If none of the prior validation steps halts validation, a field + configured with a `{@link #vtype}` will utilize the + corresponding {@link Ext.form.field.VTypes VTypes} validation function. + If invalid, either the field's `{@link #vtypeText}` or + the VTypes vtype Text property will be used for the invalid message. + Keystrokes on the field will be filtered according to the VTypes + vtype Mask property. + +4. **Field specific regex test** + + If none of the prior validation steps halts validation, a field's + configured {@link #regex} test will be processed. + The invalid message for this test is configured with `{@link #regexText}` + + * @param {Mixed} value The value to validate. The processed raw value will be used if nothing is passed + * @return {Array} Array of any validation errors + * @markdown + */ + getErrors: function(value) { + var me = this, + errors = me.callParent(arguments), + validator = me.validator, + emptyText = me.emptyText, + allowBlank = me.allowBlank, + vtype = me.vtype, + vtypes = Ext.form.field.VTypes, + regex = me.regex, + format = Ext.String.format, + msg; + + value = value || me.processRawValue(me.getRawValue()); + + if (Ext.isFunction(validator)) { + msg = validator.call(me, value); + if (msg !== true) { + errors.push(msg); + } + } + + if (value.length < 1 || value === emptyText) { + if (!allowBlank) { + errors.push(me.blankText); + } + //if value is blank, there cannot be any additional errors + return errors; + } + + if (value.length < me.minLength) { + errors.push(format(me.minLengthText, me.minLength)); + } + + if (value.length > me.maxLength) { + errors.push(format(me.maxLengthText, me.maxLength)); + } + + if (vtype) { + if(!vtypes[vtype](value, me)){ + errors.push(me.vtypeText || vtypes[vtype +'Text']); + } + } + + if (regex && !regex.test(value)) { + errors.push(me.regexText || me.invalidText); + } + + return errors; + }, + + /** + * Selects text in this field + * @param {Number} start (optional) The index where the selection should start (defaults to 0) + * @param {Number} end (optional) The index where the selection should end (defaults to the text length) + */ + selectText : function(start, end){ + var me = this, + v = me.getRawValue(), + doFocus = true, + el = me.inputEl.dom, + undef, + range; + + if (v.length > 0) { + start = start === undef ? 0 : start; + end = end === undef ? v.length : end; + if (el.setSelectionRange) { + el.setSelectionRange(start, end); + } + else if(el.createTextRange) { + range = el.createTextRange(); + range.moveStart('character', start); + range.moveEnd('character', end - v.length); + range.select(); + } + doFocus = Ext.isGecko || Ext.isOpera; + } + if (doFocus) { + me.focus(); + } + }, + + /** + * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. + * This only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the + * width changes. + */ + autoSize: function() { + var me = this, + width; + if (me.grow && me.rendered) { + me.doComponentLayout(); + width = me.inputEl.getWidth(); + if (width !== me.lastInputWidth) { + me.fireEvent('autosize', width); + me.lastInputWidth = width; + } + } + }, + + initAria: function() { + this.callParent(); + this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false); + }, + + /** + * @protected override + * To get the natural width of the inputEl, we do a simple calculation based on the + * 'size' config. We use hard-coded numbers to approximate what browsers do natively, + * to avoid having to read any styles which would hurt performance. + */ + getBodyNaturalWidth: function() { + return Math.round(this.size * 6.5) + 20; + } + +});