简体   繁体   中英

Input field caret position not getting set correctly

I have this directive down below for a <input type="text> field

myApp.directive('onlyDecimal', function () 
{
    return {
      require: '?ngModel',
      link: function(scope, element, attrs, ngModelCtrl) 
      {
            if(!ngModelCtrl) 
            {
                return; 
            }

            ngModelCtrl.$parsers.push(function(val) 
            {
                if (angular.isUndefined(val)) 
                {
                    var val = "";
                }

                var clean = "";

                if(val !== null)
                {
                    clean = val.replace(/[^0-9\.]/g, "");
                }

                var start   = element[0].selectionStart;
                var end     = element[0].selectionEnd + clean.length - val.length;

                var negativeCheck   = clean.split("-");
                var decimalCheck    = clean.split(".");

                if(!angular.isUndefined(negativeCheck[1])) 
                {
                    negativeCheck[1] = negativeCheck[1].slice(0, negativeCheck[1].length);
                    clean = negativeCheck[0] + '-' + negativeCheck[1];

                    if(negativeCheck[0].length > 0) 
                    {
                        clean = negativeCheck[0];
                    }
                }

                if(!angular.isUndefined(decimalCheck[1])) 
                {
                    decimalCheck[1] = decimalCheck[1].slice(0,2);
                    clean           = decimalCheck[0] + "." + decimalCheck[1];
                }

                if (val !== clean) 
                {
                    ngModelCtrl.$setViewValue(clean);
                    ngModelCtrl.$render();
                }

                element[0].setSelectionRange(start, end);

                return clean;
            });

            element.bind("keypress", function(event) 
            {
                if(event.keyCode === 32) 
                {
                    event.preventDefault();
                }
            });

            var decimalCount = 2;
            var decimalPoint = ".";

            ngModelCtrl.$render = function() 
            {
                if (ngModelCtrl.$modelValue != null && ngModelCtrl.$modelValue >= 0) 
                {
                    if (typeof decimalCount === "number") 
                    {
                        element.val(ngModelCtrl.$modelValue.toFixed(decimalCount).toString().replace(".", ","));
                    } 
                    else 
                    {
                        element.val(ngModelCtrl.$modelValue.toString().replace(".", ","));
                    }
                }
            }

            element.on("change", function(e) 
            {
                var floatValue = parseFloat(element.val().replace(",", "."));

                if (!isNaN(floatValue) && typeof decimalCount === "number") 
                {
                    if (decimalCount === 0) 
                    {
                        element.val(parseInt(floatValue));
                    } 
                    else 
                    {
                        var strValue = floatValue.toFixed(decimalCount);
                        element.val(strValue.replace(".", decimalPoint));
                    }
                }
            });
        }
    };
});

The purpose of this directive is to only allow numbers and 1 decimal in the field.

Let's say I have a value of 50.00 I then set the caret before the value which would be position 0 and I enter an invalid value of the key b . I set a console.log before I set the selection range and I get these values:

START: 0 END: 1
START: 0 END: 0

It runs twice and it seems to still move the caret to the next position.

There are at least two issues in your code that are causing problematic behavior:

  1. In ngModelCtrl.$render , you are checking the type of decimalCount to determine if the $modelValue is a number or a string. As soon as you start typing, ngModelCtrl.$modelValue becomes a string, but your logic still attempts to call .toFixed() on it, causing render to throw an exception, and preventing setSelectionRange from being called by the parser.

  2. Your logic that is swapping commas for decimals is not being used in your formatter. A value with a comma will come in, and the regex creating clean will remove it because it is expecting a decimal. Once you fix that, you will have to also fix the comparison between val and clean at the end to swap decimals back to commas in clean .

Overall, I would propose the following:

  1. Swap if (typeof decimalCount === "number") to if (typeof ngModelCtrl.$modelValue === "number")
  2. Replace all commas with decimals before generating clean
  3. Replace all decimals with commas in clean before comparing it back to the original val .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM