/** * @private */Ext.define('Ext.field.Input', { extend: 'Ext.Component', xtype: 'input', /** * @event masktap * @preventable * Fires whenever a mask is tapped. * @param {Ext.field.Input} this * @param {Ext.event.Event} e The event object. */ /** * @event focus * @preventable * Fires whenever the input get focus. * @param {Ext.event.Event} e The event object. */ /** * @event blur * @preventable * Fires whenever the input loses focus. * @param {Ext.event.Event} e The event object. */ /** * @event click * Fires whenever the input is clicked. * @param {Ext.event.Event} e The event object. */ /** * @event keyup * Fires whenever keyup is detected. * @param {Ext.event.Event} e The event object. */ /** * @event paste * Fires whenever paste is detected. * @param {Ext.event.Event} e The event object. */ /** * @event mousedown * Fires whenever the input has a mousedown occur. * @param {Ext.event.Event} e The event object. */ /** * @property {String} tag The el tag. * @private */ tag: 'input', cachedConfig: { /** * @cfg {String/Boolean} useMask * `true` to use a mask on this field. * @private * @accessor */ useMask: null, /** * @cfg {String} type The type attribute for input fields -- e.g. radio, text, password. * * If you want to use a `file` input, please use the {@link Ext.field.File} component instead. * @accessor */ type: 'text', /** * @cfg {Boolean} checked `true` if the checkbox should render initially checked. * @accessor */ checked: false }, config: { /** * @cfg {String} name The field's HTML name attribute. * __Note:__ This property must be set if this field is to be automatically included with * {@link Ext.form.Panel#method-submit form submit()}. * @accessor */ name: null, /** * @cfg {Mixed} value A value to initialize this field with. * @accessor */ value: null, /** * @cfg {Boolean} `true` if the field currently has focus. * @accessor */ isFocused: false, /** * @cfg {Number} tabIndex The `tabIndex` for this field. * * __Note:__ This only applies to fields that are rendered, not those which are built via `applyTo`. * @accessor */ tabIndex: null, /** * @cfg {String} placeHolder A string value displayed in the input (if supported) when the control is empty. * @accessor */ placeHolder: null, /** * @cfg {Number} [minValue=undefined] The minimum value that this Number field can accept (defaults to `undefined`, e.g. no minimum). * @accessor */ minValue: null, /** * @cfg {Number} [maxValue=undefined] The maximum value that this Number field can accept (defaults to `undefined`, e.g. no maximum). * @accessor */ maxValue: null, /** * @cfg {Number} [stepValue=undefined] The amount by which the field is incremented or decremented each time the spinner is tapped. * Defaults to `undefined`, which means that the field goes up or down by 1 each time the spinner is tapped. * @accessor */ stepValue: null, /** * @cfg {Number} [maxLength=0] The maximum number of permitted input characters. * @accessor */ maxLength: null, /** * @cfg {Boolean} [autoComplete=undefined] * `true` to set the field's DOM element `autocomplete` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset. * @accessor */ autoComplete: null, /** * @cfg {Boolean} [autoCapitalize=undefined] * `true` to set the field's DOM element `autocapitalize` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset * @accessor */ autoCapitalize: null, /** * `true` to set the field DOM element `autocorrect` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset. * @cfg {Boolean} autoCorrect * @accessor */ autoCorrect: null, /** * @cfg {Boolean} [readOnly=undefined] * `true` to set the field DOM element `readonly` attribute to `"true"`. Defaults to `undefined`, leaving the attribute unset. * @accessor */ readOnly: null, /** * @cfg {Number} [maxRows=undefined] * Sets the field DOM element `maxRows` attribute. Defaults to `undefined`, leaving the attribute unset. * @accessor */ maxRows: null, /** * @cfg {String} pattern The value for the HTML5 `pattern` attribute. * You can use this to change which keyboard layout will be used. * * Ext.define('Ux.field.Pattern', { * extend : 'Ext.field.Text', * xtype : 'patternfield', * * config : { * component : { * pattern : '[0-9]*' * } * } * }); * * Even though it extends {@link Ext.field.Text}, it will display the number keyboard. * * @accessor */ pattern: null, /** * @cfg {Boolean} [disabled=false] `true` to disable the field. * * Be aware that conformant with the [HTML specification](http://www.w3.org/TR/html401/interact/forms.html), * disabled Fields will not be {@link Ext.form.Panel#method-submit submitted}. * @accessor */ /** * @cfg {Mixed} startValue * The value that the Field had at the time it was last focused. This is the value that is passed * to the {@link Ext.field.Text#change} event which is fired if the value has been changed when the Field is blurred. * * __This will be `undefined` until the Field has been visited.__ Compare {@link #originalValue}. * @accessor */ startValue: false, /** * @cfg {Boolean} fastFocus * * Enable Fast Input Focusing on iOS, using this workaround will stop some touchstart events in order to prevent * delayed focus issues. * * @accessor */ fastFocus: false }, classCls: Ext.baseCSSPrefix + 'input', /** * @cfg {String/Number} originalValue The original value when the input is rendered. * @private */ /** * @private */ getTemplate: function() { var me = this, template = [], beforeTemplate = me.beforeTemplate, afterTemplate = me.afterTemplate; if (beforeTemplate) { // hook for subclasses to add elements before the inputElement template.push.apply(template, beforeTemplate); } template.push({ reference: 'inputBodyElement', cls: Ext.baseCSSPrefix + 'input-body-el', children: [{ reference: 'inputElement', tag: this.tag, cls: Ext.baseCSSPrefix + 'input-el' }, { reference: 'maskElement', classList: [ Ext.baseCSSPrefix + 'mask-el', Ext.baseCSSPrefix + 'hidden-display' ] }] }); if (afterTemplate) { // hook for subclasses to add elements after the inputElement template.push.apply(template, afterTemplate); } return template; }, initElement: function() { var me = this; me.callParent(); me.inputElement.on({ scope: me, keyup: 'onKeyUp', keydown: 'onKeyDown', focus: 'onFocus', blur: 'onBlur', input: 'onInput', paste: 'onPaste', tap: 'onInputTap' }); // Stock android has a delayed mousedown event that is dispatched // this prevents the mousedown from focus's an input when not intended (click a message box button or picker button that lays over an input) // we then force focus on touchend. if(Ext.browser.is.AndroidStock) { me.inputElement.dom.addEventListener("mousedown", function(e) { if(document.activeElement != e.target) { e.preventDefault(); } } ); me.inputElement.dom.addEventListener("touchend", function() { me.focus(); }); } me.maskElement.on({ scope: me, tap: 'onMaskTap' }); // Hack for IE10. Seems like keyup event is not fired for 'enter' keyboard button, so we use keypress event instead to handle enter. if(Ext.browser.is.ie && Ext.browser.version.major >=10){ me.inputElement.on({ scope: me, keypress: 'onKeyPress' }); } }, updateFastFocus: function(newValue) { // This is used to prevent 300ms delayed focus bug on iOS if (newValue) { if (this.getFastFocus() && Ext.os.is.iOS) { this.inputElement.on({ scope: this, touchstart: "onTouchStart" }); } } else { this.inputElement.un({ scope: this, touchstart: "onTouchStart" }); } }, /** * Manual Max Length processing is required for the stock "Browser" on Android * @private * @return {Boolean} 'true' if non-chrome browser is detected on Android */ useManualMaxLength: function() { return Boolean((Ext.os.is.Android && !Ext.browser.is.Chrome)); }, applyUseMask: function(useMask) { return !!useMask; }, updateUseMask: function(newUseMask) { this.maskElement[newUseMask ? 'show' : 'hide'](); }, updatePattern : function (pattern) { this.updateFieldAttribute('pattern', pattern); }, /** * Helper method to update a specified attribute on the `fieldEl`, or remove the attribute all together. * @private */ updateFieldAttribute: function(attribute, newValue) { var inputElement = this.inputElement; if (!Ext.isEmpty(newValue, true)) { inputElement.dom.setAttribute(attribute, newValue); } else { inputElement.dom.removeAttribute(attribute); } }, /** * Updates the type attribute with the {@link #type} configuration. * @private */ updateType: function(newType, oldType) { this.updateFieldAttribute('type', newType); }, /** * Updates the name attribute with the {@link #name} configuration. * @private */ updateName: function(newName) { this.updateFieldAttribute('name', newName); }, /** * Returns the field data value. * @return {Mixed} value The field value. */ getValue: function() { var inputElement = this.inputElement; if (inputElement) { this._value = inputElement.dom.value; } return this._value; }, /** * @private */ applyValue: function(value) { return (Ext.isEmpty(value)) ? '' : value; }, /** * Updates the {@link #value} configuration. * @private */ updateValue: function(newValue) { var inputElement = this.inputElement, validity = inputElement.dom.validity, field = inputElement.parent('.x-field'); // We need to check the values due to odd issues on mobile devices with autocomplete // Even though the value is equal setting it causes autocomplete to insert text that is wrong // https://sencha.jira.com/browse/EXTJS-18840 if (inputElement && inputElement.dom.value !== newValue) { inputElement.dom.value = newValue; } if (field && validity) { field.toggleCls(Ext.baseCSSPrefix + 'invalid', !validity.valid); } }, setValue: function(newValue) { var oldValue = this._value; this.updateValue(this.applyValue(newValue)); newValue = this.getValue(); if (String(newValue) != String(oldValue) && this.initialized) { this.onChange(this, newValue, oldValue); } return this; }, //<debug> /** * @private */ applyTabIndex: function(tabIndex) { if (tabIndex !== null && typeof tabIndex != 'number') { throw new Error("Ext.field.Field: [applyTabIndex] trying to pass a value which is not a number"); } return tabIndex; }, //</debug> /** * Updates the tabIndex attribute with the {@link #tabIndex} configuration * @private */ updateTabIndex: function(newTabIndex) { this.updateFieldAttribute('tabIndex', newTabIndex); }, /** * @private */ testAutoFn: function(value) { return [true, 'on'].indexOf(value) !== -1; }, //<debug> applyMaxLength: function(maxLength) { if (maxLength !== null && typeof maxLength != 'number') { throw new Error("Ext.field.Text: [applyMaxLength] trying to pass a value which is not a number"); } return maxLength; }, //</debug> /** * Updates the `maxlength` attribute with the {@link #maxLength} configuration. * @private */ updateMaxLength: function(newMaxLength) { if (!this.useManualMaxLength()) { this.updateFieldAttribute('maxlength', newMaxLength); } }, /** * Updates the `placeholder` attribute with the {@link #placeHolder} configuration. * @private */ updatePlaceHolder: function(newPlaceHolder) { this.updateFieldAttribute('placeholder', newPlaceHolder); }, /** * @private */ applyAutoComplete: function(autoComplete) { return this.testAutoFn(autoComplete); }, /** * Updates the `autocomplete` attribute with the {@link #autoComplete} configuration. * @private */ updateAutoComplete: function(newAutoComplete) { var value = newAutoComplete ? 'on' : 'off'; this.updateFieldAttribute('autocomplete', value); }, /** * @private */ applyAutoCapitalize: function(autoCapitalize) { return this.testAutoFn(autoCapitalize); }, /** * Updates the `autocapitalize` attribute with the {@link #autoCapitalize} configuration. * @private */ updateAutoCapitalize: function(newAutoCapitalize) { var value = newAutoCapitalize ? 'on' : 'off'; this.updateFieldAttribute('autocapitalize', value); }, /** * @private */ applyAutoCorrect: function(autoCorrect) { return this.testAutoFn(autoCorrect); }, /** * Updates the `autocorrect` attribute with the {@link #autoCorrect} configuration. * @private */ updateAutoCorrect: function(newAutoCorrect) { var value = newAutoCorrect ? 'on' : 'off'; this.updateFieldAttribute('autocorrect', value); }, /** * Updates the `min` attribute with the {@link #minValue} configuration. * @private */ updateMinValue: function(newMinValue) { this.updateFieldAttribute('min', newMinValue); }, /** * Updates the `max` attribute with the {@link #maxValue} configuration. * @private */ updateMaxValue: function(newMaxValue) { this.updateFieldAttribute('max', newMaxValue); }, /** * Updates the `step` attribute with the {@link #stepValue} configuration * @private */ updateStepValue: function(newStepValue) { this.updateFieldAttribute('step', newStepValue); }, /** * @private */ checkedRe: /^(true|1|on)/i, /** * Returns the `checked` value of this field * @return {Mixed} value The field value */ getChecked: function() { var inputElement = this.inputElement, checked; if (inputElement) { checked = inputElement.dom.checked; this._checked = checked; } return checked; }, /** * @private */ applyChecked: function(checked) { return !!this.checkedRe.test(String(checked)); }, setChecked: function(newChecked) { this.updateChecked(this.applyChecked(newChecked)); this._checked = newChecked; }, /** * Updates the `autocorrect` attribute with the {@link #autoCorrect} configuration * @private */ updateChecked: function(newChecked) { this.inputElement.dom.checked = newChecked; }, /** * Updates the `readonly` attribute with the {@link #readOnly} configuration * @private */ updateReadOnly: function(readOnly) { this.updateFieldAttribute('readonly', readOnly ? true : null); }, //<debug> /** * @private */ applyMaxRows: function(maxRows) { if (maxRows !== null && typeof maxRows !== 'number') { throw new Error("Ext.field.Input: [applyMaxRows] trying to pass a value which is not a number"); } return maxRows; }, //</debug> updateMaxRows: function(newRows) { this.updateFieldAttribute('rows', newRows); }, updateDisabled: function(disabled) { this.callParent(arguments); if (Ext.browser.is.Safari && !Ext.os.is.BlackBerry) { this.inputElement.dom.tabIndex = (disabled) ? -1 : 0; } this.inputElement.dom.disabled = (Ext.browser.is.Safari && !Ext.os.is.BlackBerry) ? false : disabled; if (!disabled) { this.blur(); } }, /** * 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. * @return {Boolean} */ isDirty: function() { if (this.getDisabled()) { return false; } return String(this.getValue()) !== String(this.originalValue); }, /** * Resets the current field value to the original value. */ reset: function() { this.setValue(this.originalValue); }, /** * @private */ onInputTap: function(e) { this.fireAction('inputtap', [this, e], 'doInputTap'); }, /** * @private */ doInputTap: function(me, e) { if (me.getDisabled()) { return false; } // Fast focus switching if (this.getFastFocus() && Ext.os.is.iOS) { me.focus(); } }, /** * @private */ onMaskTap: function(e) { this.fireAction('masktap', [this, e], 'doMaskTap'); }, /** * @private */ doMaskTap: function(me, e) { if (me.getDisabled()) { return false; } me.focus(); }, /** * @private */ showMask: function() { if (this.getUseMask()) { this.maskElement.setStyle('display', 'block'); } }, /** * @private */ hideMask: function() { if (this.getUseMask()) { this.maskElement.setStyle('display', 'none'); } }, /** * Attempts to set the field as the active input focus. * @return {Ext.field.Input} this */ focus: function() { var me = this, inputElement = me.inputElement; if (inputElement && inputElement.dom.focus) { inputElement.dom.focus(); } return me; }, /** * Attempts to forcefully blur input focus for the field. * @return {Ext.field.Input} this * @chainable */ blur: function() { var me = this, inputElement = this.inputElement; if (inputElement && inputElement.dom.blur) { inputElement.dom.blur(); } return me; }, /** * Attempts to forcefully select all the contents of the input field. * @return {Ext.field.Input} this * @chainable */ select: function() { var me = this, inputElement = me.inputElement; if (inputElement && inputElement.dom.setSelectionRange) { inputElement.dom.setSelectionRange(0, 9999); } return me; }, onFocus: function(e) { this.fireAction('focus', [e], 'doFocus'); }, /** * @private */ doFocus: function(e) { var me = this; me.hideMask(); if (!me.getIsFocused()) { me.setStartValue(me.getValue()); } me.setIsFocused(true); }, onTouchStart: function(e) { // This will prevent 300ms delayed focus from occurring on iOS if(document.activeElement != e.target) { e.preventDefault(); } }, onBlur: function(e) { this.fireAction('blur', [e], 'doBlur'); }, /** * @private */ doBlur: function(e) { var me = this, value = me.getValue(), startValue = me.getStartValue(); me.showMask(); me.setIsFocused(false); if (String(value) != String(startValue)) { me.onChange(me, value, startValue); } }, onClick: function(e) { this.fireEvent('click', e); }, onChange: function(me, value, startValue) { if (this.useManualMaxLength()) { this.trimValueToMaxLength(); } this.fireEvent('change', me, value, startValue); }, onPaste: function(e) { if (this.useManualMaxLength()) { this.trimValueToMaxLength(); } this.fireEvent('paste', e); }, onKeyUp: function(e) { if (this.useManualMaxLength()) { this.trimValueToMaxLength(); } this.fireEvent('keyup', e); }, onKeyDown: function() { // tell the class to ignore the input event. this happens when we want to listen to the field change // when the input autocompletes this.ignoreInput = true; }, onInput: function(e) { var me = this; me.fireEvent('input', me, me.inputElement.dom.value); // if we should ignore input, stop now. if (me.ignoreInput) { me.ignoreInput = false; return; } // set a timeout for 10ms to check if we want to stop the input event. // if not, then continue with the event (keyup) Ext.defer(function() { if (!me.ignoreInput) { me.fireEvent('keyup', e); me.ignoreInput = false; } }, 10); }, // Hack for IE10 mobile. Handle pressing 'enter' button and fire keyup event in this case. onKeyPress: function(e) { if(e.browserEvent.keyCode == 13){ this.fireEvent('keyup', e); } }, onMouseDown: function(e) { this.fireEvent('mousedown', e); }, trimValueToMaxLength: function() { var maxLength = this.getMaxLength(); if (maxLength) { var value = this.getValue(); if (value.length > this.getMaxLength()) { this.setValue(value.slice(0, maxLength)); } } }});