Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

input type="number" does not allow numbers with leading decimals on some browsers #5680

Open
joeenzminger opened this issue Jan 8, 2014 · 7 comments

Comments

@joeenzminger
Copy link

Fiddle: http://jsfiddle.net/NEhG9/3/

The fiddle uses 1.2.1, however, I have reproduced it using 1.2.6

It affects IE9 and Firefox
It does not affect Chrome or Safari

I haven't tested IE8- or IE10+

The desired behavior is to allow the leading decimal to be entered, but do not update the model or set the input element's validation to valid until a valid number is entered.

Users can enter numbers with leading decimals by entering the number first, then scrolling back and entering the decimal.

joeenzminger added a commit to joeenzminger/angular.js that referenced this issue Jan 8, 2014
add check for leading decimal.  Some browsers swallow leading decimals on
input type="number".

fixes angular#5680
@ghost ghost assigned tbosch Jan 9, 2014
@btford btford removed the gh: issue label Aug 20, 2014
@rnicholus
Copy link

Instead of modifying the angular source, I'm using a custom validator that I attach to number fields to work around this issue. This appears to consistently mark the field as invalid until a valid number is entered, cross-browser. This also seems to address the issue in IE9 where leading decimals are removed.

module.directive('tolerantNumber', function() {
    return {
        require: 'ngModel',
        priority: 1,
        link: function(scope, element, attrs, ctrl) {
            var ngNumberParser = ctrl.$parsers.splice(0, 1)[0];

            ctrl.$parsers.push(function(value) {
                if (value === '.') {
                    ctrl.$setValidity('number', false);
                    return undefined;
                }
                return ngNumberParser(value);
            });
        }
    };
});

Use:

<input type="number" tolerant-number>

I'm not sold on the directive name though. Oh well.

@rnicholus
Copy link

After closer inspection, the above code doesn't solve additional problems I'm seeing in IE10+. I just ended up contributing my own number validator directive and switched to a text input. This appears to be the only obvious way to properly validate numbers cross-browser.

@joeenzminger
Copy link
Author

Ray, can you reference the commit with your number validator? I'd like to take a look at it.

@rnicholus
Copy link

The commit is in a private project, but I'll post it below. I wrote a few unit tests that seem to verify it, and some brief cross-browser testing looks good:

module.directive('numberValidator', function() {
    return {
        require: 'ngModel',
        link: function(scope, element, attrs, ctrl) {
            ctrl.$parsers.push(function(value) {
                if (ctrl.$isEmpty(value)) {
                    ctrl.$setValidity('number', true);
                    return null;
                }
                // http://stackoverflow.com/a/1830844/486979
                else if (!isNaN(parseFloat(value)) && isFinite(value)) {
                    ctrl.$setValidity('number', true);
                    return value;
                }
                else {
                    ctrl.$setValidity('number', false);
                }
            });
        }
    };
});

@rnicholus
Copy link

Here are my tests:

describe('number validator', function() {
    var $scope, $compile, el;

    beforeEach(function() {
        module('module');
        module('test.templates');

        inject(function(_$rootScope_, _$translate_, _$compile_) {
            $scope = _$rootScope_.$new();
            $compile = _$compile_;

            $scope.test = {num: null};
            el = '<input type="text" ng-model="test.num" number-validator>';
            el = $compile(el)($scope);
            $scope.$digest();
        });
    });

    it('declares valid numbers to be valid', function() {
        el.val('1').trigger('input');
        expect(el.hasClass('ng-valid')).toBe(true);

        el.val('.1').trigger('input');
        expect(el.hasClass('ng-valid')).toBe(true);

        el.val('1.0').trigger('input');
        expect(el.hasClass('ng-valid')).toBe(true);

        el.val('-1.0').trigger('input');
        expect(el.hasClass('ng-valid')).toBe(true);
    });

    it('declares invalid numbers to be invalid', function() {
        el.val('-').trigger('input');
        expect(el.hasClass('ng-invalid')).toBe(true);

        el.val('.').trigger('input');
        expect(el.hasClass('ng-invalid')).toBe(true);

        el.val('abc').trigger('input');
        expect(el.hasClass('ng-invalid')).toBe(true);
    });
});

@stewones
Copy link

stewones commented Sep 1, 2015

+1

@willmendesneto
Copy link

I had a similar problem and update the input[type="number"] example on angular docs for works with decimals precision and I'm using this approach to solve it.

...
.directive('stringToNumber', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModel) {
      var decimalPrecision = attrs.decimalPrecision || 0;
      ngModel.$parsers.push(function(value) {
        return value;
      });

      function updateElementValue() {
        var viewValue = parseFloat(ngModel.$viewValue || 0);
        element.val(decimalPrecision !== null ? viewValue.toFixed(decimalPrecision) : viewValue);
      }

      ngModel.$formatters.push(function(value) {
        return parseFloat(value, 10);
      });

      ngModel.$viewChangeListeners.push(updateElementValue);

      ngModel.$render = function() {
        updateElementValue();
      }
    }
  };
});
<input type="number" 
           string-to-number 
           decimal-precision="2" 
           ng-model="model.value" 
           step="0.10" />

Here is the plunker with this snippet

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants