/** * @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 <jason@sencha.com> */ 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 <b><code>{@link #vtype}</code></b> currently set for this field (defaults to <tt>undefined</tt>). * <b>Note</b>: only applies if <b><code>{@link #vtype}</code></b> is set, else ignored. */ /** * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value * before validation (defaults to <tt>undefined</tt>). */ /** * @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 <tt>20</tt>. */ size: 20, /** * @cfg {Boolean} grow <tt>true</tt> if this field should automatically grow and shrink to its content * (defaults to <tt>false</tt>) */ /** * @cfg {Number} growMin The minimum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults * to <tt>30</tt>) */ growMin : 30, /** * @cfg {Number} growMax The maximum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults * to <tt>800</tt>) */ 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 <tt>true</tt>. 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 <tt>undefined</tt>) */ /** * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that do * not match (defaults to <tt>undefined</tt>) */ /** * @cfg {Boolean} disableKeyFilter Specify <tt>true</tt> to disable input keystroke filtering (defaults * to <tt>false</tt>) */ /** * @cfg {Boolean} allowBlank Specify <tt>false</tt> to validate that the value's length is > 0 (defaults to * <tt>true</tt>) */ allowBlank : true, /** * @cfg {Number} minLength Minimum input field length required (defaults to <tt>0</tt>) */ 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 <tt><b>{@link Ext.form.field.Text#enforceMaxLength enforceMaxLength}</b></tt> option. */ maxLength : Number.MAX_VALUE, /** * @cfg {Boolean} enforceMaxLength True to set the maxLength property on the underlying input field. Defaults to <tt>false</tt> */ /** * @cfg {String} minLengthText Error text to display if the <b><tt>{@link #minLength minimum length}</tt></b> * validation fails (defaults to <tt>'The minimum length for this field is {minLength}'</tt>) */ minLengthText : 'The minimum length for this field is {0}', /** * @cfg {String} maxLengthText Error text to display if the <b><tt>{@link #maxLength maximum length}</tt></b> * validation fails (defaults to <tt>'The maximum length for this field is {maxLength}'</tt>) */ maxLengthText : 'The maximum length for this field is {0}', /** * @cfg {Boolean} selectOnFocus <tt>true</tt> to automatically select any existing field text when the field * receives input focus (defaults to <tt>false</tt>) */ /** * @cfg {String} blankText The error text to display if the <b><tt>{@link #allowBlank}</tt></b> validation * fails (defaults to <tt>'This field is required'</tt>) */ blankText : 'This field is required', /** * @cfg {Function} validator * <p>A custom validation function to be called during field validation ({@link #getErrors}) * (defaults to <tt>undefined</tt>). If specified, this function will be called first, allowing the * developer to override the default validation process.</p> * <br><p>This function will be passed the following Parameters:</p> * <div class="mdetail-params"><ul> * <li><code>value</code>: <i>Mixed</i> * <div class="sub-desc">The current field value</div></li> * </ul></div> * <br><p>This function is to Return:</p> * <div class="mdetail-params"><ul> * <li><code>true</code>: <i>Boolean</i> * <div class="sub-desc"><code>true</code> if the value is valid</div></li> * <li><code>msg</code>: <i>String</i> * <div class="sub-desc">An error message if the value is invalid</div></li> * </ul></div> */ /** * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation * (defaults to <tt>undefined</tt>). If the test fails, the field will be marked invalid using * <b><tt>{@link #regexText}</tt></b>. */ /** * @cfg {String} regexText The error text to display if <b><tt>{@link #regex}</tt></b> is used and the * test fails during validation (defaults to <tt>''</tt>) */ regexText : '', /** * @cfg {String} emptyText * <p>The default text to place into an empty field (defaults to <tt>undefined</tt>).</p> * <p>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 <tt>false</tt>.</p> * <p>Also note that if you use <tt>{@link #inputType inputType}:'file'</tt>, {@link #emptyText} is not * supported and should be avoided.</p> */ /** * @cfg {String} emptyCls The CSS class to apply to an empty field to style the <b><tt>{@link #emptyText}</tt></b> * (defaults to <tt>'x-form-empty-field'</tt>). 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 <tt>true</tt> to enable the proxying of key events for the HTML input field (defaults to <tt>false</tt>) */ componentLayout: 'textfield', initComponent : function(){ this.callParent(); this.addEvents( /** * @event autosize * Fires when the <tt><b>{@link #autoSize}</b></tt> 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 <tt><b>{@link #enableKeyEvents}</b></tt> * 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 <tt><b>{@link #enableKeyEvents}</b></tt> * 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 <tt><b>{@link #enableKeyEvents}</b></tt> * 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 <tt><b>{@link #emptyText}</b></tt> and <tt><b>{@link #emptyCls}</b></tt> 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 <code>{@link #allowBlank}</code>, 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 <code>{@link #regex}</code> 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 <tt>{@link #grow} = true</tt>, 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; } });