slackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangouts
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1296 lines
44 KiB
1296 lines
44 KiB
/** |
|
* 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 #checkChangeBuffer} 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. |
|
* |
|
* # Example usage |
|
* |
|
* @example |
|
* 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 |
|
* }] |
|
* }); |
|
* |
|
* # Custom Subclasses |
|
* |
|
* This class can be extended to provide additional functionality. The example below demonstrates creating |
|
* a custom search field that uses the HTML5 search input type. |
|
* |
|
* @example |
|
* // A simple subclass of Base that creates a HTML5 search field. Redirects to the |
|
* // searchUrl when the Enter key is pressed.222 |
|
* Ext.define('Ext.form.SearchField', { |
|
* extend: 'Ext.form.field.Text', |
|
* alias: 'widget.searchfield', |
|
* |
|
* inputType: 'search', |
|
* |
|
* // Config defining the search URL |
|
* searchUrl: 'http://www.google.com/search?q={0}', |
|
* |
|
* // Add specialkey listener |
|
* initComponent: function() { |
|
* this.callParent(); |
|
* this.on('specialkey', this.checkEnterKey, this); |
|
* }, |
|
* |
|
* // Handle enter key presses, execute the search if the field has a value |
|
* checkEnterKey: function(field, e) { |
|
* var value = this.getValue(); |
|
* if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) { |
|
* location.href = Ext.String.format(this.searchUrl, value); |
|
* } |
|
* } |
|
* }); |
|
* |
|
* Ext.create('Ext.form.Panel', { |
|
* title: 'Base Example', |
|
* bodyPadding: 5, |
|
* width: 250, |
|
* |
|
* // Fields will be arranged vertically, stretched to full width |
|
* layout: 'anchor', |
|
* defaults: { |
|
* anchor: '100%' |
|
* }, |
|
* items: [{ |
|
* xtype: 'searchfield', |
|
* fieldLabel: 'Search', |
|
* name: 'query' |
|
* }], |
|
* renderTo: Ext.getBody() |
|
* }); |
|
*/ |
|
Ext.define('Ext.form.field.Text', { |
|
extend:'Ext.form.field.Base', |
|
alias: 'widget.textfield', |
|
requires: [ |
|
'Ext.form.field.VTypes', |
|
'Ext.form.trigger.Trigger', |
|
'Ext.util.TextMetrics' |
|
], |
|
alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'], |
|
|
|
config: { |
|
/** |
|
* @cfg {Boolean} hideTrigger |
|
* `true` to hide all triggers |
|
*/ |
|
hideTrigger: false, |
|
|
|
// @cmd-auto-dependency {aliasPrefix: "trigger.", isKeyedObject: true} |
|
/** |
|
* @cfg {Object} triggers |
|
* {@link Ext.form.trigger.Trigger Triggers} to use in this field. The keys in |
|
* this object are unique identifiers for the triggers. The values in this object |
|
* are {@link Ext.form.trigger.Trigger Trigger} configuration objects. |
|
* |
|
* Ext.create('Ext.form.field.Text', { |
|
* renderTo: document.body, |
|
* fieldLabel: 'My Custom Field', |
|
* triggers: { |
|
* foo: { |
|
* cls: 'my-foo-trigger', |
|
* handler: function() { |
|
* console.log('foo trigger clicked'); |
|
* } |
|
* }, |
|
* bar: { |
|
* cls: 'my-bar-trigger', |
|
* handler: function() { |
|
* console.log('bar trigger clicked'); |
|
* } |
|
* } |
|
* } |
|
* }); |
|
* |
|
* The weight value may be a negative value in order to position custom triggers |
|
* ahead of default triggers like that of ComboBox. |
|
* |
|
* Ext.create('Ext.form.field.ComboBox', { |
|
* renderTo: Ext.getBody(), |
|
* fieldLabel: 'My Custom Field', |
|
* triggers: { |
|
* foo: { |
|
* cls: 'my-foo-trigger', |
|
* weight: -2, // negative to place before default triggers |
|
* handler: function() { |
|
* console.log('foo trigger clicked'); |
|
* } |
|
* }, |
|
* bar: { |
|
* cls: 'my-bar-trigger', |
|
* weight: -1, |
|
* handler: function() { |
|
* console.log('bar trigger clicked'); |
|
* } |
|
* } |
|
* } |
|
* }); |
|
*/ |
|
triggers: undefined |
|
}, |
|
|
|
/** |
|
* @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. **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 |
|
* during input. If `stripCharsRe` is specified, |
|
* every *character sequence* matching `stripCharsRe` will be removed. |
|
*/ |
|
|
|
/** |
|
* @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. |
|
* @deprecated use {@link #width} instead. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [grow=false] |
|
* true if this field should automatically grow and shrink to its content |
|
*/ |
|
|
|
/** |
|
* @cfg {Number} growMin |
|
* The minimum width to allow when `{@link #grow} = true` |
|
*/ |
|
growMin : 30, |
|
|
|
/** |
|
* @cfg {Number} growMax |
|
* The maximum width to allow when `{@link #grow} = true` |
|
*/ |
|
growMax : 800, |
|
|
|
//<locale> |
|
/** |
|
* @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', |
|
//</locale> |
|
|
|
/** |
|
* @cfg {String} vtype |
|
* A validation type name as defined in {@link Ext.form.field.VTypes} |
|
*/ |
|
|
|
/** |
|
* @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes (character being |
|
* typed) that do not match. |
|
* Note: It does not filter characters already in the input. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [disableKeyFilter=false] |
|
* Specify true to disable input keystroke filtering |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [allowBlank=true] |
|
* Specify false to validate that the value's length must be > 0. If `true`, then a blank value is **always** taken to be valid regardless of any {@link #vtype} |
|
* validation that may be applied. |
|
* |
|
* If {@link #vtype} validation must still be applied to blank values, configure {@link #validateBlank} as `true`; |
|
*/ |
|
allowBlank : true, |
|
|
|
/** |
|
* @cfg {Boolean} [validateBlank=false] |
|
* Specify as `true` to modify the behaviour of {@link #allowBlank} so that blank values are not passed as valid, but are subject to any configure {@link #vtype} validation. |
|
*/ |
|
validateBlank: false, |
|
|
|
/** |
|
* @cfg {Boolean} allowOnlyWhitespace |
|
* Specify false to automatically trim the value before validating |
|
* the whether the value is blank. Setting this to false automatically |
|
* sets {@link #allowBlank} to false. |
|
*/ |
|
allowOnlyWhitespace: true, |
|
|
|
/** |
|
* @cfg {Number} minLength |
|
* Minimum input field length required |
|
*/ |
|
minLength : 0, |
|
|
|
/** |
|
* @cfg {Number} maxLength |
|
* Maximum input field length allowed by validation. 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. |
|
* |
|
* Defaults to Number.MAX_VALUE. |
|
*/ |
|
maxLength : Number.MAX_VALUE, |
|
|
|
/** |
|
* @cfg {Boolean} enforceMaxLength |
|
* True to set the maxLength property on the underlying input field. Defaults to false |
|
*/ |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} minLengthText |
|
* Error text to display if the **{@link #minLength minimum length}** validation fails. |
|
*/ |
|
minLengthText : 'The minimum length for this field is {0}', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} maxLengthText |
|
* Error text to display if the **{@link #maxLength maximum length}** validation fails |
|
*/ |
|
maxLengthText : 'The maximum length for this field is {0}', |
|
//</locale> |
|
|
|
/** |
|
* @cfg {Boolean} [selectOnFocus=false] |
|
* `true` to automatically select any existing field text when the field receives input |
|
* focus. Only applies when {@link #editable editable} = true |
|
*/ |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} blankText |
|
* The error text to display if the **{@link #allowBlank}** validation fails |
|
*/ |
|
blankText : 'This field is required', |
|
//</locale> |
|
|
|
/** |
|
* @cfg {Function} validator |
|
* A custom validation function to be called during field validation ({@link #getErrors}). |
|
* If specified, this function will be called first, allowing the developer to override the default validation |
|
* process. |
|
* |
|
* Ext.create('Ext.form.field.Text', { |
|
* renderTo: document.body, |
|
* name: 'phone', |
|
* fieldLabel: 'Phone Number', |
|
* validator: function (val) { |
|
* // remove non-numeric characters |
|
* var tn = val.replace(/[^0-9]/g,''), |
|
* errMsg = "Must be a 10 digit telephone number"; |
|
* // if the numeric value is not 10 digits return an error message |
|
* return (tn.length === 10) ? true : errMsg; |
|
* } |
|
* }); |
|
* |
|
* @param {Object} validator.value The current field value |
|
* @return {Boolean/String} response |
|
* |
|
* - True if the value is valid |
|
* - An error message if the value is invalid |
|
*/ |
|
|
|
/** |
|
* @cfg {RegExp} regex |
|
* A JavaScript RegExp object to be tested against the field value during validation. |
|
* If the test fails, the field will be marked invalid using |
|
* either **{@link #regexText}** or **{@link #invalidText}**. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} regexText |
|
* The error text to display if **{@link #regex}** is used and the test fails during validation |
|
*/ |
|
regexText : '', |
|
|
|
/** |
|
* @cfg {String} emptyText |
|
* The default text to place into an empty field. |
|
* |
|
* 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. |
|
* |
|
* Note that for browsers that support it, setting this property will use the HTML 5 placeholder attribute, and for |
|
* older browsers that don't support the HTML 5 placeholder attribute the value will be placed directly into the input |
|
* element itself as the raw value. This means that older browsers will obfuscate the {@link #emptyText} value for |
|
* password input fields. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} [emptyCls='x-form-empty-field'] |
|
* The CSS class to apply to an empty field to style the **{@link #emptyText}**. |
|
* This class is automatically added and removed as needed depending on the current field value. |
|
*/ |
|
emptyCls : Ext.baseCSSPrefix + 'form-empty-field', |
|
|
|
/** |
|
* @cfg {String} [requiredCls='x-form-required-field'] |
|
* The CSS class to apply to a required field, i.e. a field where **{@link #allowBlank}** is false. |
|
*/ |
|
requiredCls : Ext.baseCSSPrefix + 'form-required-field', |
|
|
|
/** |
|
* @cfg {Boolean} [enableKeyEvents=false] |
|
* true to enable the proxying of key events for the HTML input field |
|
*/ |
|
|
|
// private |
|
valueContainsPlaceholder : false, |
|
|
|
ariaRole: 'textbox', |
|
|
|
/** |
|
* @cfg {Boolean} editable |
|
* false to prevent the user from typing text directly into the field; the field can |
|
* only have its value set programmatically or via an action invoked by a trigger. |
|
*/ |
|
editable: true, |
|
|
|
/** |
|
* @cfg {Boolean} repeatTriggerClick |
|
* `true` to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger(s). |
|
* Click repeating behavior can also be configured on the individual {@link #triggers |
|
* trigger instances using the trigger's {@link {Ext.form.trigger.Trigger#repeatClick |
|
* repeatClick} config. |
|
*/ |
|
repeatTriggerClick: false, |
|
|
|
/** |
|
* @cfg {Boolean} readOnly |
|
* `true` to prevent the user from changing the field, and hide all triggers. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} |
|
* The CSS class that is added to the div wrapping the input element and trigger button(s). |
|
*/ |
|
triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap', |
|
|
|
triggerWrapFocusCls: Ext.baseCSSPrefix + 'form-trigger-wrap-focus', |
|
triggerWrapInvalidCls: Ext.baseCSSPrefix + 'form-trigger-wrap-invalid', |
|
|
|
fieldBodyCls: Ext.baseCSSPrefix + 'form-text-field-body', |
|
|
|
/** |
|
* @cfg {String} |
|
* The CSS class that is added to the element wrapping the input element |
|
*/ |
|
inputWrapCls: Ext.baseCSSPrefix + 'form-text-wrap', |
|
|
|
inputWrapFocusCls: Ext.baseCSSPrefix + 'form-text-wrap-focus', |
|
inputWrapInvalidCls: Ext.baseCSSPrefix + 'form-text-wrap-invalid', |
|
growCls: Ext.baseCSSPrefix + 'form-text-grow', |
|
|
|
// private |
|
monitorTab: true, |
|
// private |
|
mimicing: false, |
|
|
|
needArrowKeys: true, |
|
|
|
childEls: [ |
|
/** |
|
* @property {Ext.dom.Element} triggerWrap |
|
* A reference to the element which encapsulates the input field and all |
|
* trigger button(s). Only set after the field has been rendered. |
|
*/ |
|
'triggerWrap', |
|
|
|
/** |
|
* @property {Ext.dom.Element} inputWrap |
|
* A reference to the element that wraps the input element. Only set after the |
|
* field has been rendered. |
|
*/ |
|
'inputWrap' |
|
], |
|
|
|
preSubTpl: [ |
|
'<div id="{cmpId}-triggerWrap" data-ref="triggerWrap" class="{triggerWrapCls} {triggerWrapCls}-{ui}">', |
|
'<div id={cmpId}-inputWrap data-ref="inputWrap" class="{inputWrapCls} {inputWrapCls}-{ui}">' |
|
], |
|
|
|
postSubTpl: [ |
|
'</div>', // end inputWrap |
|
'<tpl for="triggers">{[values.renderTrigger(parent)]}</tpl>', |
|
'</div>' // end triggerWrap |
|
], |
|
|
|
/** |
|
* @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 |
|
*/ |
|
|
|
/** |
|
* @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.event.Event} e |
|
*/ |
|
|
|
/** |
|
* @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.event.Event} e |
|
*/ |
|
|
|
/** |
|
* @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.event.Event} e |
|
*/ |
|
|
|
initComponent: function () { |
|
var me = this, |
|
emptyCls = me.emptyCls; |
|
|
|
if (me.allowOnlyWhitespace === false) { |
|
me.allowBlank = false; |
|
} |
|
|
|
//<debug> |
|
if (me.size) { |
|
Ext.log.warn('Ext.form.field.Text "size" config was deprecated in Ext 5.0. Please specify a "width" or use a layout instead.'); |
|
} |
|
//</debug> |
|
// In Ext JS 4.x the layout system used the following magic formula for converting |
|
// the "size" config into a pixel value. |
|
if (me.size) { |
|
me.defaultBodyWidth = me.size * 6.5 + 20; |
|
} |
|
|
|
if (!me.onTrigger1Click) { |
|
// for compat with 4.x TriggerField |
|
me.onTrigger1Click = me.onTriggerClick; |
|
} |
|
|
|
me.callParent(); |
|
|
|
if (me.readOnly) { |
|
me.setReadOnly(me.readOnly); |
|
} |
|
me.fieldFocusCls = me.baseCls + '-focus'; |
|
me.emptyUICls = emptyCls + ' ' + emptyCls + '-' + me.ui; |
|
me.addStateEvents('change'); |
|
}, |
|
|
|
// private |
|
initEvents: function(){ |
|
var me = this, |
|
el = me.inputEl; |
|
|
|
me.callParent(); |
|
|
|
// Workaround for https://code.google.com/p/chromium/issues/detail?id=4505 |
|
// On mousedown, add a single: true mouseup listener which prevents default. |
|
// That will prevent deselection of the text that was selected in the onFocus method. |
|
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 this.isEqualAsString(value1, value2); |
|
}, |
|
|
|
/** |
|
* @private |
|
* If grow=true, invoke the autoSize method when the field's value is changed. |
|
*/ |
|
onChange: function(newVal, oldVal) { |
|
this.callParent(arguments); |
|
this.autoSize(); |
|
}, |
|
|
|
getSubTplData: function(fieldData) { |
|
var me = this, |
|
value = me.getRawValue(), |
|
isEmpty = me.emptyText && value.length < 1, |
|
maxLength = me.maxLength, |
|
placeholder; |
|
|
|
// We can't just dump the value here, since MAX_VALUE ends up |
|
// being something like 1.xxxxe+300, which gets interpreted as 1 |
|
// in the markup |
|
if (me.enforceMaxLength) { |
|
if (maxLength === Number.MAX_VALUE) { |
|
maxLength = undefined; |
|
} |
|
} else { |
|
maxLength = undefined; |
|
} |
|
|
|
if (isEmpty) { |
|
if (Ext.supports.Placeholder) { |
|
placeholder = me.emptyText; |
|
} else { |
|
value = me.emptyText; |
|
me.valueContainsPlaceholder = true; |
|
} |
|
} |
|
|
|
return Ext.apply(me.callParent(arguments), { |
|
triggerWrapCls: me.triggerWrapCls, |
|
inputWrapCls: me.inputWrapCls, |
|
triggers: me.orderedTriggers, |
|
maxLength: maxLength, |
|
readOnly: !me.editable || me.readOnly, |
|
placeholder: placeholder, |
|
value: value, |
|
fieldCls: me.fieldCls + ((isEmpty && (placeholder || value)) ? ' ' + me.emptyUICls : '') + (me.allowBlank ? '' : ' ' + me.requiredCls) |
|
}); |
|
}, |
|
|
|
onRender: function() { |
|
var me = this, |
|
triggers = me.getTriggers(), |
|
elements = [], |
|
id, triggerEl; |
|
|
|
if (Ext.supports.FixedTableWidthBug) { |
|
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=130239 and |
|
// https://code.google.com/p/chromium/issues/detail?id=377190 |
|
// See styleHooks for more details |
|
me.el._needsTableWidthFix = true; |
|
} |
|
|
|
me.callParent(); |
|
|
|
if (triggers) { |
|
this.invokeTriggers('onFieldRender'); |
|
|
|
/** |
|
* @property {Ext.CompositeElement} triggerEl |
|
* @deprecated 5.0 |
|
* A composite of all the trigger button elements. Only set after the field has |
|
* been rendered. |
|
*/ |
|
for(id in triggers) { |
|
elements.push(triggers[id].el); |
|
} |
|
// for 4.x compat, also set triggerCell |
|
triggerEl = me.triggerEl = me.triggerCell = new Ext.CompositeElement(elements, true); |
|
} |
|
|
|
/** |
|
* @property {Ext.dom.Element} inputCell |
|
* A reference to the element that wraps the input element. Only set after the |
|
* field has been rendered. |
|
* @deprecated 5.0 use {@link #inputWrap} instead |
|
*/ |
|
me.inputCell = me.inputWrap; |
|
}, |
|
|
|
afterRender: function(){ |
|
var me = this; |
|
|
|
me.autoSize(); |
|
me.callParent(); |
|
me.invokeTriggers('afterFieldRender'); |
|
}, |
|
|
|
onMouseDown: function(){ |
|
var me = this; |
|
if(!me.hasFocus) { |
|
// On the next mouseup, prevent default. |
|
// 99% of the time, it will be the mouseup of the click into the field, and |
|
// We will be preventing deselection of selected text: https://code.google.com/p/chromium/issues/detail?id=4505 |
|
// Listener is on the doc in case the pointer moves out before user lets go. |
|
Ext.getDoc().on('mouseup', Ext.emptyFn, me, { single: true, preventDefault: true }); |
|
} |
|
}, |
|
|
|
applyTriggers: function(triggers) { |
|
var me = this, |
|
hideAllTriggers = me.getHideTrigger(), |
|
readOnly = me.readOnly, |
|
orderedTriggers = me.orderedTriggers = [], |
|
repeatTriggerClick = me.repeatTriggerClick, |
|
id, triggerCfg, trigger, triggerCls, i; |
|
|
|
//<debug> |
|
if (me.rendered) { |
|
Ext.Error.raise("Cannot set triggers after field has already been rendered."); |
|
} |
|
|
|
// don't warn if we have both triggerCls and triggers, because picker field |
|
// uses triggerCls to style the "picker" trigger. |
|
if ((me.triggerCls && !triggers) || me.trigger1Cls) { |
|
Ext.log.warn("Ext.form.field.Text: 'triggerCls' and 'trigger<n>Cls'" + |
|
" are deprecated. Use 'triggers' instead."); |
|
} |
|
//</debug> |
|
|
|
if (!triggers) { |
|
// For compatibility with 4.x, transform the trigger<n>Cls configs into the |
|
// new "triggers" config. |
|
triggers = {}; |
|
|
|
if (me.triggerCls && !me.trigger1Cls) { |
|
me.trigger1Cls = me.triggerCls; |
|
} |
|
|
|
// Assignment in conditional test is deliberate here |
|
for (i = 1; triggerCls = me['trigger' + i + 'Cls']; i++) { // jshint ignore:line |
|
triggers['trigger' + i] = { |
|
cls: triggerCls, |
|
extraCls: Ext.baseCSSPrefix + 'trigger-index-' + i, |
|
handler: 'onTrigger' + i + 'Click', |
|
compat4Mode: true, |
|
scope: me |
|
}; |
|
} |
|
} |
|
|
|
for(id in triggers) { |
|
if (triggers.hasOwnProperty(id)) { |
|
triggerCfg = triggers[id]; |
|
triggerCfg.field = me; |
|
triggerCfg.id = id; |
|
|
|
/* |
|
* An explicitly-configured 'triggerConfig.hideOnReadOnly : false' allows {@link #hideTrigger} analysis |
|
*/ |
|
if ((readOnly && triggerCfg.hideOnReadOnly !== false) || (hideAllTriggers && triggerCfg.hidden !== false)) { |
|
triggerCfg.hidden = true; |
|
} |
|
if (repeatTriggerClick && (triggerCfg.repeatClick !== false)) { |
|
triggerCfg.repeatClick = true; |
|
} |
|
|
|
trigger = triggers[id] = Ext.form.trigger.Trigger.create(triggerCfg); |
|
orderedTriggers.push(trigger); |
|
} |
|
} |
|
|
|
Ext.Array.sort(orderedTriggers, Ext.form.trigger.Trigger.weightComparator); |
|
|
|
return triggers; |
|
}, |
|
|
|
/** |
|
* Invokes a method on all triggers. |
|
* @param {String} methodName |
|
* @private |
|
*/ |
|
invokeTriggers: function(methodName, args) { |
|
var me = this, |
|
triggers = me.getTriggers(), |
|
id, trigger; |
|
|
|
if (triggers) { |
|
for (id in triggers) { |
|
if (triggers.hasOwnProperty(id)) { |
|
trigger = triggers[id]; |
|
// IE8 needs "|| []" if args is undefined |
|
trigger[methodName].apply(trigger, args || []); |
|
} |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Returns the trigger with the given id |
|
* @param {String} id |
|
* @return {Ext.form.trigger.Trigger} |
|
*/ |
|
getTrigger: function(id) { |
|
return this.getTriggers()[id]; |
|
}, |
|
|
|
updateHideTrigger: function(hideTrigger) { |
|
this.invokeTriggers(hideTrigger ? 'hide' : 'show'); |
|
}, |
|
|
|
/** |
|
* Sets the editable state of this field. |
|
* @param {Boolean} editable True to allow the user to directly edit the field text. |
|
* If false is passed, the user will only be able to modify the field using the trigger. |
|
*/ |
|
setEditable: function(editable) { |
|
var me = this; |
|
|
|
me.editable = editable; |
|
if (me.rendered) { |
|
me.setReadOnlyAttr(!editable || me.readOnly); |
|
} |
|
}, |
|
|
|
/** |
|
* Sets the read-only state of this field. |
|
* @param {Boolean} readOnly True to prevent the user changing the field and explicitly |
|
* hide the trigger(s). Setting this to true will supersede settings editable and |
|
* hideTrigger. Setting this to false will defer back to {@link #editable editable} and {@link #hideTrigger hideTrigger}. |
|
*/ |
|
setReadOnly: function(readOnly) { |
|
var me = this, |
|
triggers = me.getTriggers(), |
|
hideTriggers = me.getHideTrigger(), |
|
trigger, |
|
id; |
|
|
|
readOnly = !!readOnly; |
|
|
|
me.callParent([readOnly]); |
|
if (me.rendered) { |
|
me.setReadOnlyAttr(readOnly || !me.editable); |
|
} |
|
|
|
if (triggers) { |
|
for (id in triggers) { |
|
trigger = triggers[id]; |
|
/* |
|
* Controlled trigger visibility state is only managed fully when 'hideOnReadOnly' is falsy. |
|
* Truth table: |
|
* - If the trigger is configured/defaulted as 'hideOnReadOnly : true', it's readOnly-visibility |
|
* is determined solely by readOnly state of the Field. |
|
* - If 'hideOnReadOnly : false/undefined', the Fields.{link #hideTrigger hideTrigger} is honored. |
|
*/ |
|
if (trigger.hideOnReadOnly === true || (trigger.hideOnReadOnly !== false && !hideTriggers)) { |
|
trigger.setVisible(!readOnly); |
|
} |
|
} |
|
} |
|
}, |
|
|
|
// private. sets the readonly attribute of the input element |
|
setReadOnlyAttr: function(readOnly) { |
|
var me = this, |
|
readOnlyName = 'readonly', |
|
inputEl = me.inputEl.dom; |
|
|
|
if (readOnly) { |
|
inputEl.setAttribute(readOnlyName, readOnlyName); |
|
} else { |
|
inputEl.removeAttribute(readOnlyName); |
|
} |
|
}, |
|
|
|
/** |
|
* Performs any necessary manipulation of a raw String value to prepare it for 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); |
|
me.valueContainsPlaceholder = true; |
|
} |
|
|
|
//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.emptyUICls); |
|
} |
|
else { |
|
me.inputEl.removeCls(me.emptyUICls); |
|
} |
|
|
|
me.autoSize(); |
|
} |
|
}, |
|
|
|
afterFirstLayout: function() { |
|
this.callParent(); |
|
if (Ext.isIE && this.disabled) { |
|
var el = this.inputEl; |
|
if (el) { |
|
el.dom.unselectable = 'on'; |
|
} |
|
} |
|
}, |
|
|
|
//private |
|
toggleInvalidCls: function(hasError) { |
|
var method = hasError ? 'addCls' : 'removeCls'; |
|
|
|
this.callParent(); |
|
|
|
this.triggerWrap[method](this.triggerWrapInvalidCls); |
|
this.inputWrap[method](this.inputWrapInvalidCls); |
|
}, |
|
|
|
// private |
|
beforeFocus: function(){ |
|
var me = this, |
|
inputEl = me.inputEl, |
|
emptyText = me.emptyText, |
|
isEmpty; |
|
|
|
me.callParent(arguments); |
|
if ((emptyText && !Ext.supports.Placeholder) && (inputEl.dom.value === me.emptyText && me.valueContainsPlaceholder)) { |
|
me.setRawValue(''); |
|
isEmpty = true; |
|
inputEl.removeCls(me.emptyUICls); |
|
me.valueContainsPlaceholder = false; |
|
} else if (Ext.supports.Placeholder) { |
|
inputEl.removeCls(me.emptyUICls); |
|
} |
|
}, |
|
|
|
onFocus: function(e) { |
|
var me = this; |
|
|
|
me.callParent(arguments); |
|
if (me.selectOnFocus) { |
|
me.inputEl.dom.select(); |
|
} |
|
|
|
if (me.emptyText) { |
|
me.autoSize(); |
|
} |
|
|
|
me.addCls(me.fieldFocusCls); |
|
me.triggerWrap.addCls(me.triggerWrapFocusCls); |
|
me.inputWrap.addCls(me.inputWrapFocusCls); |
|
me.invokeTriggers('onFieldFocus', [e]); |
|
}, |
|
|
|
// @private |
|
onBlur: function(e) { |
|
var me = this; |
|
|
|
me.callParent(arguments); |
|
|
|
me.removeCls(me.fieldFocusCls); |
|
me.triggerWrap.removeCls(me.triggerWrapFocusCls); |
|
me.inputWrap.removeCls(me.inputWrapFocusCls); |
|
me.invokeTriggers('onFieldBlur', [e]); |
|
}, |
|
|
|
completeEdit: function(e) { |
|
this.callParent([e]); |
|
this.applyEmptyText(); |
|
}, |
|
|
|
// private |
|
filterKeys : function(e){ |
|
/* |
|
* Current only FF will fire keypress events for special keys. |
|
* |
|
* On European keyboards, the right alt key, Alt Gr, is used to type certain special characters. |
|
* JS detects a keypress of this as ctrlKey & altKey. As such, we check that alt isn't pressed |
|
* so we can still process these special characters. |
|
*/ |
|
if ((e.ctrlKey && !e.altKey) || e.isSpecialKey()) { |
|
return; |
|
} |
|
var charCode = String.fromCharCode(e.getCharCode()); |
|
if (!this.maskRe.test(charCode)) { |
|
e.stopEvent(); |
|
} |
|
}, |
|
|
|
getState: function() { |
|
return this.addPropertyToState(this.callParent(), 'value'); |
|
}, |
|
|
|
applyState: function(state) { |
|
this.callParent(arguments); |
|
if(state.hasOwnProperty('value')) { |
|
this.setValue(state.value); |
|
} |
|
}, |
|
|
|
/** |
|
* 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} The raw String value of the field |
|
*/ |
|
getRawValue: function() { |
|
var me = this, |
|
v = me.callParent(); |
|
if (v === me.emptyText && me.valueContainsPlaceholder) { |
|
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 {Object} 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.emptyUICls); |
|
me.valueContainsPlaceholder = false; |
|
} |
|
|
|
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 #blankText}`) |
|
* |
|
* 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 {Object} value The value to validate. The processed raw value will be used if nothing is passed. |
|
* @return {String[]} Array of any validation errors |
|
*/ |
|
getErrors: function(value) { |
|
value = arguments.length ? (value == null ? '' : value) : this.processRawValue(this.getRawValue()); |
|
|
|
var me = this, |
|
errors = me.callParent([value]), |
|
validator = me.validator, |
|
vtype = me.vtype, |
|
vtypes = Ext.form.field.VTypes, |
|
regex = me.regex, |
|
format = Ext.String.format, |
|
msg, trimmed, isBlank; |
|
|
|
if (Ext.isFunction(validator)) { |
|
msg = validator.call(me, value); |
|
if (msg !== true) { |
|
errors.push(msg); |
|
} |
|
} |
|
|
|
trimmed = me.allowOnlyWhitespace ? value : Ext.String.trim(value); |
|
|
|
if (trimmed.length < 1 || (value === me.emptyText && me.valueContainsPlaceholder)) { |
|
if (!me.allowBlank) { |
|
errors.push(me.blankText); |
|
} |
|
// If we are not configured to validate blank values, there cannot be any additional errors |
|
if (!me.validateBlank) { |
|
return errors; |
|
} |
|
isBlank = true; |
|
} |
|
|
|
// If a blank value has been allowed through, then exempt it from the minLength check. |
|
// It must be allowed to hit the vtype validation. |
|
if (!isBlank && 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=0] The index where the selection should start |
|
* @param {Number} [end] The index where the selection should end (defaults to the text length) |
|
*/ |
|
selectText: function (start, end) { |
|
var me = this, |
|
v = me.getRawValue(), |
|
len = v.length, |
|
el = me.inputEl.dom, |
|
range; |
|
|
|
if (len > 0) { |
|
start = start === undefined ? 0 : Math.min(start, len); |
|
end = end === undefined ? len : Math.min(end, len); |
|
|
|
if (el.setSelectionRange) { |
|
el.setSelectionRange(start, end); |
|
} else if (el.createTextRange) { |
|
range = el.createTextRange(); |
|
range.moveStart('character', start); |
|
range.moveEnd('character', end - len); |
|
range.select(); |
|
} |
|
} |
|
|
|
// TODO: Reinvestigate FF and Opera. |
|
}, |
|
|
|
// Template method, override in Combobox. |
|
getGrowWidth: function () { |
|
return this.inputEl.dom.value; |
|
}, |
|
|
|
/** |
|
* Automatically grows the field to accommodate 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, |
|
triggers, triggerId, triggerWidth, inputEl, width, value; |
|
|
|
if (me.grow && me.rendered && me.getSizeModel().width.auto) { |
|
inputEl = me.inputEl; |
|
triggers = me.getTriggers(); |
|
triggerWidth = 0; |
|
|
|
value = Ext.util.Format.htmlEncode( |
|
me.getGrowWidth() || (me.hasFocus ? '' : me.emptyText) || '' |
|
); |
|
value += me.growAppend; |
|
|
|
for (triggerId in triggers) { |
|
triggerWidth += triggers[triggerId].el.getWidth(); |
|
} |
|
|
|
width = inputEl.getTextWidth(value) + triggerWidth + |
|
// The element that has the border depends on theme - inputWrap (classic) |
|
// or triggerWrap (neptune) |
|
me.inputWrap.getBorderWidth('lr') + me.triggerWrap.getBorderWidth('lr'); |
|
|
|
width = Math.min(Math.max(width, me.growMin), me.growMax); |
|
|
|
me.bodyEl.setWidth(width); |
|
|
|
me.updateLayout(); |
|
|
|
me.fireEvent('autosize', me, width); |
|
} |
|
}, |
|
|
|
onDestroy: function(){ |
|
var me = this; |
|
|
|
me.invokeTriggers('destroy'); |
|
Ext.destroy(me.triggerRepeater); |
|
|
|
me.callParent(); |
|
}, |
|
|
|
onTriggerClick: Ext.emptyFn, |
|
|
|
privates: { |
|
/** |
|
* @private |
|
* @override |
|
*/ |
|
getTdType: function () { |
|
return 'textfield'; |
|
} |
|
}, |
|
|
|
deprecated: { |
|
5: { |
|
methods: { |
|
/** |
|
* Get the total width of the trigger button area. |
|
* @return {Number} The total trigger width |
|
* @deprecated 5.0 |
|
*/ |
|
getTriggerWidth: function() { |
|
var triggers = this.getTriggers(), |
|
width = 0, |
|
id; |
|
if (triggers && this.rendered) { |
|
for (id in triggers) { |
|
if (triggers.hasOwnProperty(id)) { |
|
width += triggers[id].el.getWidth(); |
|
} |
|
} |
|
} |
|
|
|
return width; |
|
} |
|
} |
|
} |
|
} |
|
|
|
});
|
|
|