📜 ⬆️ ⬇️

Filtering Input Characters in Ext.form.field.Number

I think everyone who wrote a WEB-application with active use of JavaScript on the client side, faced with the problem of the decimal separator. And the solution to this problem is not trivial, as it may seem at first glance. ExtJS uses an easy-to-implement and control approach: the character field is indicated in the numeric field, which is considered a separator, and the input of other characters, excluding digits and "-", is prohibited. However, this approach, it seems to me, has one major drawback: when several layouts are used, the decimal separator on the numeric keypad corresponds to different characters. How to fix this is described below.

Let's start over, or how the filtering is arranged in the standard Ext.form.field.Number



According to the documentation, the component has the disableKeyFilter property, which is responsible for filtering the input characters and is inherited from the Ext.form.field.Text text field. Let's look for the property in the source text field. Its only mention in the code is in the initEvents method, where the filterKeys handler is hung on the keypress event. Now let's “walk” along the hierarchy of classes from the text field to the number field and look for the overridden filterKeys method, and not finding anything, we will pick the found one. Inside the method, in principle, there is nothing special: filtering is to check the entered character against a regular expression, which can be specified when configuring the component. Now let's look at the documentation of the number field and see that the maskRe parameter cannot be set during configuration, i.e. one can only ask how it will be processed incomprehensibly. We climb into the sources of the numeric field and in the initComponent () method we see:
if (me.disableKeyFilter !== true) { allowed = me.baseChars + ''; if (me.allowDecimals) { allowed += me.decimalSeparator; } if (me.minValue < 0) { allowed += '-'; } allowed = Ext.String.escapeRegex(allowed); me.maskRe = new RegExp('[' + allowed + ']'); if (me.autoStripChars) { me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi'); } } 

In other words, if the filtering is deliberately not turned off, the component creates maskRe itself on the basis of the specified settings. In the standard version, the input character must be one of the following '.-0123456789'. Here, in fact, the whole filter.

Plugin stock


Who does not know how to write plugins, go to read the documentation . For the rest, there is nothing difficult in the code below.
 Ext.define('Ext.plugin.form.field.NumberInputFilter', { alias : 'plugin.numberinputfilter', extend : 'Ext.AbstractPlugin', init : function(field) { //   ,        if (!(field && field.isXType('numberfield'))) { return; } Ext.apply(field, { //    , //         Ext.form.field.Text filterKeys : function(e){ if (e.ctrlKey && !e.altKey) { 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(); } } }); } }); 

Decimal separator substitution

Due to the fact that the most common delimiters are the dot "." and comma ",", so it will be default delimiters. Add the configuration property allowedDecimalSeparators, a constructor that sets the default value of this property, as well as the processing itself in the filterKeys () method.
 Ext.define('Ext.plugin.form.field.NumberInputFilter', { alias : 'plugin.numberinputfilter', extend : 'Ext.AbstractPlugin', constructor : function(cfg) { cfg = cfg || {}; //     Ext.applyIf(cfg, { allowedDecimalSeparators : ',.' }); Ext.apply(this, cfg); }, init : function(field) { //   ,        if (!(field && field.isXType('numberfield'))) { return; } Ext.apply(field, { //     filterKeys : function(e){ if (e.ctrlKey && !e.altKey) { 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; } // begin hack if (charCode != this.decimalSeparator && this.allowedDecimalSeparators.indexOf(charCode) != -1) { //      , //     , //      charCode = this.decimalSeparator; if (Ext.isIE) { //  IE       e.browserEvent.keyCode = charCode.charCodeAt(0); } else if (Ext.isGecko) { //  gecko-   e.stopEvent(); //         var newEvent = document.createEvent('KeyEvents'); //     , // ..    ,   //      newEvent.initKeyEvent( e.browserEvent.type, e.browserEvent.bubbles, true, //cancellable e.browserEvent.view, e.browserEvent.ctrlKey, e.browserEvent.altKey, e.browserEvent.shiftKey, e.browserEvent.metaKey, 0, // keyCode charCode.charCodeAt(0) // charCode ); e.getTarget().dispatchEvent(newEvent); //  ,     . return; } else if (Ext.isWebKit) { //   e.stopEvent(); //  webkit initKeyboardEvent  ,   TextEvent if (this.maskRe.test(charCode)) { var newEvent = document.createEvent('TextEvent'); newEvent.initTextEvent( 'textInput', e.browserEvent.bubbles, true, e.browserEvent.view, charCode ); e.getTarget().dispatchEvent(newEvent); } return; } } // end hack if(!this.maskRe.test(charCode)){ e.stopEvent(); } } }); } }); 

Now any of the supported delimiters will be replaced with the correct one. However, separators can be entered anywhere and any number. Correct this misunderstanding.
Pseudomask support for input value

By supporting pseudo-masks, we mean the impossibility of introducing an invalid character, as well as a valid character in an invalid place. For example, the minus sign "-" is not at the beginning of a number, or several decimal separators in a string. As it was written above, character filtering is performed through a regular expression. However, to support the input mask, you need something more, so add another checkValue () method to the field, which will take the new character as an argument and check the resulting value for pseudomask matching.
Conventionally, the string representation of a numerical value can be divided into substrings "before" and "after" the decimal separator. So we will check:
 checkValue : function(newChar) { //    input  var raw = this.getRawValue(); if (Ext.isEmpty(raw)) { //   ,    : // -   // -   "-",     // -   return (newChar == this.decimalSeparator || (this.minValue < 0) && newChar == '-') || /^\d$/.test(newChar); } //    ,... if (raw.length == this.maxLength) { // ...       return false; } if (newChar == this.decimalSeparator && (!this.allowDecimals || raw.indexOf(this.decimalSeparator) != -1)) { // ...   ,    , //        return false; } //    raw += newChar; raw = raw.split(new RegExp(Ext.String.escapeRegex(this.decimalSeparator))); return (!raw[0] || this.intRe.test(raw[0])) && (!raw[1] || this.decRe.test(raw[1])); } 

The above code is commented on in some detail, intRe and decRe remain unclear. These are regular expressions for checking the integer and fractional parts of a number, respectively, which will be formed when the plug-in is connected in another field added to the updateDecimalPrecision () method.
 //     decimalPrecision   //       updateDecimalPrecision : function(prec, force) { if (prec == this.decimalPrecision && force !== true) { return; } if (!Ext.isNumber(prec) || prec < 1) { //   ,     this.allowDecimals = false; } else { this.decimalPrecision = prec; } //      var intRe = '^'; if (this.minValue < 0) { intRe += '-?'; } // integerPrecision -  decimalPrecision   , //       intRe += '\\d' + (Ext.isNumber(this.integerPrecision) ? '{1,' + this.integerPrecision + '}' : '+') + '$'; this.intRe = new RegExp(intRe); if (this.allowDecimals) { //      this.decRe = new RegExp('^\\d{1,' + this.decimalPrecision + '}$'); } else { delete this.decRe; } } 

The described method should be called at the field at the end of the init () method of the created plugin.
The resulting plugin is fully functional, however, it is not without some unpleasant features: for example, the entered value, the decimal part of which completely occupies the digits allocated to it, cannot be deleted by selecting it. To combat this state of affairs, we will slightly improve the checkValue () code by adding processing of the selected part of the text to the field. As a result, the code will look like this.
 checkValue : function(newChar) { //    input  var raw = this.getRawValue(); //  dom- var el = this.inputEl.dom; //       var start = getSelectionStart(el); var end = getSelectionEnd(el); if (start != end) { //       raw = raw.substring(0, start) + raw.substring(end); } if (Ext.isEmpty(raw)) { //   ,    : // -   // -   "-",     // -   return (newChar == this.decimalSeparator || (this.minValue < 0) && newChar == '-') || /^\d$/.test(newChar); } //    ,... if (raw.length == this.maxLength) { // ...       return false; } if (newChar == this.decimalSeparator && (!this.allowDecimals || raw.indexOf(this.decimalSeparator) != -1)) { // ...   ,    , //        return false; } //    raw = raw.substring(0, start) + newChar + raw.substring(start); raw = raw.split(new RegExp(Ext.String.escapeRegex(this.decimalSeparator))); return (!raw[0] || this.intRe.test(raw[0])) && (!raw[1] || this.decRe.test(raw[1])); } 

That's all. Just in case I give the full code of the plugin and a demo.

 Ext.define('Ext.plugin.form.field.NumberInputFilter', { alias: 'plugin.numberinputfilter', extend: 'Ext.AbstractPlugin', constructor : function(cfg) { cfg = cfg || {}; Ext.applyIf(cfg, { allowedDecimalSeparators : ',.' }); Ext.apply(this, cfg); }, init : function(field) { if (!(field && field.isXType('numberfield'))) { return; } Ext.apply(field, { allowedDecimalSeparators : this.allowedDecimalSeparators, checkValue : function(newChar) { var raw = this.getRawValue(); var el = this.inputEl.dom; //    http://javascript.nwbox.com/cursor_position/ //     cursor.js var start = getSelectionStart(el); var end = getSelectionEnd(el); if (start != end) { //       raw = raw.substring(0, start) + raw.substring(end); } if (Ext.isEmpty(raw)) { return (newChar == this.decimalSeparator || (this.minValue < 0) && newChar == '-') || /^\d$/.test(newChar); } if (raw.length == this.maxLength) { return false; } if (newChar == this.decimalSeparator && (!this.allowDecimals || raw.indexOf(this.decimalSeparator) != -1)) { return false; } //    raw = raw.substring(0, start) + newChar + raw.substring(start); raw = raw.split(new RegExp(Ext.String.escapeRegex(this.decimalSeparator))); return (!raw[0] || this.intRe.test(raw[0])) && (!raw[1] || this.decRe.test(raw[1])); }, filterKeys : function(e){ if (e.ctrlKey && !e.altKey) { 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; } // begin hack if (charCode != this.decimalSeparator && this.allowedDecimalSeparators.indexOf(charCode) != -1) { //      , //     , //      charCode = this.decimalSeparator; if (Ext.isIE) { //  IE       e.browserEvent.keyCode = charCode.charCodeAt(0); } else if (Ext.isGecko) { //  gecko-   e.stopEvent(); //         var newEvent = document.createEvent('KeyEvents'); //     , // ..    ,   //      newEvent.initKeyEvent( e.browserEvent.type, e.browserEvent.bubbles, true, //cancellable e.browserEvent.view, e.browserEvent.ctrlKey, e.browserEvent.altKey, e.browserEvent.shiftKey, e.browserEvent.metaKey, 0, // keyCode charCode.charCodeAt(0) // charCode ); e.getTarget().dispatchEvent(newEvent); //  ,     . return; } else if (Ext.isWebKit) { //   e.stopEvent(); //  webkit initKeyboardEvent  ,   TextEvent if (this.checkValue(charCode)) { var newEvent = document.createEvent('TextEvent'); newEvent.initTextEvent( 'textInput', e.browserEvent.bubbles, true, e.browserEvent.view, charCode ); e.getTarget().dispatchEvent(newEvent); } return; } } if (!this.checkValue(charCode)) { e.stopEvent(); } // end hack }, updateDecimalPrecision : function(prec, force) { if (prec == this.decimalPrecision && force !== true) { return; } if (!Ext.isNumber(prec) || prec < 1) { this.allowDecimals = false; } else { this.decimalPrecision = prec; } var intRe = '^'; if (this.minValue < 0) { intRe += '-?'; } intRe += '\\d' + (Ext.isNumber(this.integerPrecision) ? '{1,' + this.integerPrecision + '}' : '+') + '$'; this.intRe = new RegExp(intRe); if (this.allowDecimals) { this.decRe = new RegExp('^\\d{1,' + this.decimalPrecision + '}$'); } else { delete this.decRe; } }, fixPrecision : function(value) { // support decimalSeparators if (Ext.isString(value)) { value = value.replace(new RegExp('[' + Ext.String.escapeRegex(this.allowedDecimalSeparators + this.decimalSeparator) + ']'), '.'); } // end hack var me = this, nan = isNaN(value), precision = me.decimalPrecision; if (nan || !value) { return nan ? '' : value; } else if (!me.allowDecimals || precision <= 0) { precision = 0; } return parseFloat(Ext.Number.toFixed(parseFloat(value), precision)); } }); field.updateDecimalPrecision(field.decimalPrecision, true); } }); Ext.onReady(function() { Ext.create('Ext.window.Window', { renderTo : Ext.getBody(), width : 300, height : 230, minWidth : 300, minHeight : 230, closable : false, bodyStyle : 'padding:5px', layout : 'border', title : 'NumberInputFilterPlugin - Demo', items : [{ region : 'north', xtype : 'fieldset', defaults : { xtype : 'numberfield', hideTrigger : true, msgTarget : 'side', autoFitErrors : true }, title : 'without plugin', items : [{ fieldLabel : 'simple' },{ fieldLabel : 'autoStripChars', autoStripChars : true }] },{ region : 'center', xtype : 'fieldset', title : 'with plugin', defaults : { xtype : 'numberfield', hideTrigger : true, msgTarget : 'side', autoFitErrors : true }, layout : 'anchor', items : [{ fieldLabel : 'non negative', minValue : 0, plugins : Ext.create('plugin.numberinputfilter') },{ fieldLabel : '"@,./#" as decimal separators', plugins : Ext.create('plugin.numberinputfilter', { allowedDecimalSeparators : '@,./#' }) }] }] }).show(); Ext.tip.QuickTipManager.init(); }); 

')
UPD: How to make filtering work in firefox> 12. The implementation is taken from here , how to determine the version of firefox, I think no one needs to show. If there are problems with this, you can see it here . So, a piece of code from filterKeys:
 ... } else if (Ext.isGecko) { //  gecko-   e.stopEvent(); // https://bugzilla.mozilla.org/show_bug.cgi?id=749185 //  firefoxVersion  . if (firefoxVersion < 12) { //         var newEvent = document.createEvent('KeyEvents'); //     , // ..    ,   //      newEvent.initKeyEvent( e.browserEvent.type, e.browserEvent.bubbles, true, //cancellable e.browserEvent.view, e.browserEvent.ctrlKey, e.browserEvent.altKey, e.browserEvent.shiftKey, e.browserEvent.metaKey, 0, // keyCode charCode.charCodeAt(0) // charCode ); e.getTarget().dispatchEvent(newEvent); } else if (this.checkValue(charCode)) { // http://forums.mozillazine.org/viewtopic.php?p=12198605&sid=3723622be9117f663d16d522fe03deb5#p12198605 var tgt = e.getTarget(); if ('selectionStart' in tgt) { if (tgt.selectionStart == tgt.textLength) { tgt.value += charCode; } else { var lastpos = tgt.selectionStart; tgt.value = tgt.value.substr(0, lastpos) + charCode + tgt.value.substr(lastpos); tgt.selectionStart = lastpos + 1; tgt.selectionEnd = lastpos + 1; } } } return; } else if (Ext.isWebKit) { ... 

Source: https://habr.com/ru/post/137966/


All Articles