33

I want to use a formatted number input to show thousand seperator dots to user when he types big numbers. Here is the directive code that I used: http://jsfiddle.net/LCZfd/3/

When I use input type="text" it works, but when I want to use input type="number" it's weirdly cleaning by something when user typing big numbers.

What is problem about input[number]?

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Murat Çorlu
  • 8,207
  • 5
  • 53
  • 78
  • Works fine on Chrome! Which browser are you testing this? – Fals Jun 02 '14 at 21:50
  • Sorry, I shared wrong jsfiddle url. I updated it now. – Murat Çorlu Jun 02 '14 at 22:19
  • I'm a little confused on what you're trying to accomplish, but take a look at this: http://jsfiddle.net/LCZfd/4/ – matthewpavkov Jun 02 '14 at 22:22
  • I use a form input that has `type="number"` attribute. User writes it big numbers. And I want to show that number with thousand separators when user writing. – Murat Çorlu Jun 02 '14 at 22:29
  • My previous comment (above) shows the `,` being added when appropriate in FF, Chrome, and IE11. Are you seeing the same? – matthewpavkov Jun 03 '14 at 19:54
  • There is 2 input box there. One is "text" type and it shows `,` being added. But other one that is "number" type, and it is empty. That's the problem. – Murat Çorlu Jun 03 '14 at 21:12
  • It's not possible with current browsers to implement this with a native `input type="number"` element. Would a solution that overlays the input field with a thousand separator dot value be sufficient? Otherwise, you'd have to build your own custom "input" element that behaves like you want it to. – S.B. Jun 04 '14 at 21:47
  • 1
    Something like this: http://jsfiddle.net/LCZfd/6/ – S.B. Jun 04 '14 at 21:53
  • input type="number" only supports actual numbers, which are numeric values [0-9], the decimal point (.), an optional minus (-) and the "e" for exponential notation. A comma is not allowed in a number! Just use input type="text" and have a helper function that parses the comma seperated text into and actual number when needed. – basilikum Jun 04 '14 at 21:55
  • It's interesting that I can write "1,000,000" in type=number by typing, but if I set that by a ng-model, it removes everything. This seems some buggy me. Is it normal to you? – Murat Çorlu Jun 04 '14 at 22:04
  • @S.B.: Interesting workaround. Maybe it is acceptable if I can't find a better solution. – Murat Çorlu Jun 04 '14 at 22:12
  • @MuratCorlu why is using "number" type so important to you? You can treat the Ang varaible numerically even if its 'text" type – Dave Alperovich Jun 05 '14 at 11:39
  • @DaveA I want to use `min` and `max` attributes to validate value ranges. If I use text input, I must implement these validations manually. Also, in mobile devices, `number` typed inputs typing with special numerical keyboard, and that is more usable for visitors. – Murat Çorlu Jun 05 '14 at 11:56
  • Use the following module. That may help you.. http://ngmodules.org/modules/angular-input-decimal-separator – Jeeva J Aug 19 '15 at 08:24

5 Answers5

43

As written in the comments, input type="number" doesn't support anything but digits, a decimal separator (usually , or . depending on the locale) and - or e. You may still enter whatever you want, but the browser will discard any unknown / incorrect character.

This leaves you with 2 options:

  • Use type="text" and pattern validation like pattern="[0-9]+([\.,][0-9]+)*" to limit what the user may enter while automatically formatting the value as you do in your example.
  • Put an overlay on top of the input field that renders the numbers how you want and still allows the user to use the custom type="number" input controls, like demonstrated here.

The latter solution uses an additional <label> tag that contains the current value and is hidden via CSS when you focus the input field.

Santhoshkumar
  • 780
  • 1
  • 16
  • 36
S.B.
  • 2,920
  • 1
  • 17
  • 25
  • Nice solution! But it seems the decimal is not getting entered. Any tip on how we can achieve that would be great. Thanks – Anoop Thiruonam Dec 06 '16 at 05:55
  • The goal was to get thousands separators into the numbers, instead of decimals. It's a lot more tricky to support both, because when you don't know the locale of the user, you don't know if they are entering a decimal or a thousands separator. Example 2 of my answer should work for both. – S.B. Dec 06 '16 at 09:32
  • Great solution! For the $parsers and $formatters solution, use $setViewValue and $render to prevent element from getting out of sync with the view. (You can see this if you try repeatingedly adding a double comma in your jsfiddle). See more https://github.com/angular/angular.js/issues/12544. – Chris Chudzicki Nov 01 '19 at 16:02
8

All these years later, there still isn't an HTML5 solution out of the box for this.

I am using <input type="tel"> or <input type="text"> ("tel" brings up a numeric keyboard in Android and iOS, which in some cases is a bonus.)

Then I needed a directive to:

  • filter out non-numeric characters
  • add thousand-separator commas as the user types
  • use $parsers and keyup to set elem.val() and $formatters to set the display...
  • ...while behind the scenes, assign ng-model a floating point number

The directive example below does this, and it accepts negatives and floating point numbers unless you specify you want only positive or integers.

It's not the full solution I would like, but I think it bridges the gap.

HTML

<input type="text" ng-model="someNumber" number-input />

JAVASCRIPT

myApp.directive('numberInput', function($filter) {
  return {
    require: 'ngModel',
    link: function(scope, elem, attrs, ngModelCtrl) {

      ngModelCtrl.$formatters.push(function(modelValue) {
        return setDisplayNumber(modelValue, true);
      });

      // it's best to change the displayed text using elem.val() rather than
      // ngModelCtrl.$setViewValue because the latter will re-trigger the parser
      // and not necessarily in the correct order with the changed value last.
      // see http://radify.io/blog/understanding-ngmodelcontroller-by-example-part-1/
      // for an explanation of how ngModelCtrl works.
      ngModelCtrl.$parsers.push(function(viewValue) {
        setDisplayNumber(viewValue);
        return setModelNumber(viewValue);
      });

      // occasionally the parser chain doesn't run (when the user repeatedly 
      // types the same non-numeric character)
      // for these cases, clean up again half a second later using "keyup"
      // (the parser runs much sooner than keyup, so it's better UX to also do it within parser
      // to give the feeling that the comma is added as they type)
      elem.bind('keyup focus', function() {
        setDisplayNumber(elem.val());
      });
      function setDisplayNumber(val, formatter) {
        var valStr, displayValue;

        if (typeof val === 'undefined') {
          return 0;
        }

        valStr = val.toString();
        displayValue = valStr.replace(/,/g, '').replace(/[A-Za-z]/g, '');
        displayValue = parseFloat(displayValue);
        displayValue = (!isNaN(displayValue)) ? displayValue.toString() : '';

        // handle leading character -/0
        if (valStr.length === 1 && valStr[0] === '-') {
          displayValue = valStr[0];
        } else if (valStr.length === 1 && valStr[0] === '0') {
          displayValue = '';
        } else {
          displayValue = $filter('number')(displayValue);
        }
        // handle decimal
        if (!attrs.integer) {
          if (displayValue.indexOf('.') === -1) {
            if (valStr.slice(-1) === '.') {
              displayValue += '.';
            } else if (valStr.slice(-2) === '.0') {
              displayValue += '.0';
            } else if (valStr.slice(-3) === '.00') {
              displayValue += '.00';
            }
          } // handle last character 0 after decimal and another number
          else {
            if (valStr.slice(-1) === '0') {
              displayValue += '0';
            }
          }
        }

        if (attrs.positive && displayValue[0] === '-') {
          displayValue = displayValue.substring(1);
        }

        if (typeof formatter !== 'undefined') {
          return (displayValue === '') ? 0 : displayValue;
        } else {
          elem.val((displayValue === '0') ? '' : displayValue);
        }
      }
      function setModelNumber(val) {
        var modelNum = val.toString().replace(/,/g, '').replace(/[A-Za-z]/g, '');
        modelNum = parseFloat(modelNum);
        modelNum = (!isNaN(modelNum)) ? modelNum : 0;
        if (modelNum.toString().indexOf('.') !== -1) {
          modelNum = Math.round((modelNum + 0.00001) * 100) / 100;
        }
        if (attrs.positive) {
          modelNum = Math.abs(modelNum);
        }
        return modelNum;
      }
    }
  };
});

https://jsfiddle.net/benlk/4dto9738/

georgeawg
  • 48,608
  • 13
  • 72
  • 95
BenK
  • 111
  • 3
  • 3
6

You need to add the step attribute to your number input.

<input type="number" step="0.01" />

This will allow floating points.

http://jsfiddle.net/LCZfd/1/

Also, I'd recommend reviewing the bug thread on number inputs in Firefox. You may want to consider not using this input type, as it was just finally supported in this release of FF.

matthewpavkov
  • 2,918
  • 4
  • 21
  • 37
  • Sorry about that I shared wrong jsfiddle url. I updated question with true one. So my problem is not about floating points. I want to use thousand seperators like "1,000,000" – Murat Çorlu Jun 02 '14 at 22:20
2

You cannot use values with , because type=number only takes numbers, adding a comma makes it a string.

See http://jsfiddle.net/LCZfd/5

You're better off making your own controls if you want commas. One with a true value (the number) and a display value (the string).

Jahed
  • 484
  • 2
  • 8
  • It's interesting that I can write "1,000,000" in `type=number` by typing, but if I set that by a ng-model, it removes everything. This seems some buggy me. Is it normal to you? – Murat Çorlu Jun 04 '14 at 22:00
  • 1
    Yeah, it's weird. Entering "50,000" manually works. Internally it sets value and valueAsNumber to 50000 but it'll show 50,000 and pressing the up/down arrows removes the commas. – Jahed Jun 04 '14 at 22:36
2

you can try this, I modified the directive I saw here... How do I restrict an input to only accept numbers? ...

here's the modified directive I made... This directive uses the keyup event to modify the input on the fly...

.directive('numericOnly', function($filter) {
 return {
  require: 'ngModel',
  link: function(scope, element, attrs, modelCtrl) {

       element.bind('keyup', function (inputValue, e) {
         var strinput = modelCtrl.$$rawModelValue;
         //filter user input
         var transformedInput = strinput ? strinput.replace(/[^,\d.-]/g,'') : null;
         //remove trailing 0
         if(transformedInput.charAt(0) <= '0'){
           transformedInput = null;
           modelCtrl.$setViewValue(transformedInput);
           modelCtrl.$render();
         }else{
           var decimalSplit = transformedInput.split(".")
           var intPart = decimalSplit[0];
           var decPart = decimalSplit[1];
           //remove previously formated number
           intPart = intPart.replace(/,/g, "");
           //split whole number into array of 3 digits
           if(intPart.length > 3){
             var intDiv = Math.floor(intPart.length / 3);
             var strfraction = [];
             var i = intDiv,
                 j = 3;

             while(intDiv > 0){
               strfraction[intDiv] = intPart.slice(intPart.length-j,intPart.length - (j - 3));
               j=j+3;
               intDiv--;
             }
             var k = j-3;
             if((intPart.length-k) > 0){
               strfraction[0] = intPart.slice(0,intPart.length-k);
             }
           }
           //join arrays
           if(strfraction == undefined){ return;}
             var currencyformat = strfraction.join(',');
             //check for leading comma
             if(currencyformat.charAt(0)==','){
               currencyformat = currencyformat.slice(1);
             }

             if(decPart ==  undefined){
               modelCtrl.$setViewValue(currencyformat);
               modelCtrl.$render();
               return;
             }else{
               currencyformat = currencyformat + "." + decPart.slice(0,2);
               modelCtrl.$setViewValue(currencyformat);
               modelCtrl.$render();
             }
         }
        });
  }

};

you use it like this ...

<input type="text" ng-model="amountallocated" id="amountallocated" numeric-only />
Community
  • 1
  • 1
sireken
  • 29
  • 4