13

I have a input to show a formatted number. Normally, when it has no focus, it should show a formmatted string, e.g. '$50,000.00'. But when it has focus, it should show the raw value, e.g. 50000 for editing.

Is there any built-in functions? Thanks!

Zach
  • 5,715
  • 12
  • 47
  • 62

5 Answers5

9

Here is a directive (formatOnBlur) which does what you want.
Note that only the element's display value is formatted (the model-value will always be unformatted).

The idea is that you register listeners for the focus and blur events and update the display value based on the focus-state of the element.

app.directive('formatOnBlur', function ($filter, $window) {
    var toCurrency = $filter('currency');

    return {
        restrict: 'A',
        require: '?ngModel',
        link: function (scope, elem, attrs, ctrl) {
            var rawElem = elem[0];
            if (!ctrl || !rawElem.hasOwnProperty('value')) return;

            elem.on('focus', updateView.bind(null, true));
            elem.on('blur',  updateView.bind(null, false));

            function updateView(hasFocus) {
                if (!ctrl.$modelValue) { return; }
                var displayValue = hasFocus ?
                        ctrl.$modelValue :
                        toCurrency(ctrl.$modelValue);
                rawElem.value = displayValue;
            }
            updateView(rawElem === $window.document.activeElement);
        }
    };
});

See, also, this short demo.

gkalpak
  • 47,844
  • 8
  • 105
  • 118
  • You need to add code to remove the jquery event handlers for if/when the element gets removed. `elem.on('$destroy', function() {elem.ff('focus', _; elem.off('blur', _);})` – Matthias Aug 22 '16 at 17:41
  • If you don't remove (deregister) the event handlers, then the handler functions are held in memory (and all enclosing closures and all referenced variables within those closures), which creates a memory leak. – Matthias Aug 22 '16 at 17:45
  • Aren't event listeners get automatically cleaned up, when removing the element? – gkalpak Aug 22 '16 at 21:35
  • 1
    I read a [post](http://stackoverflow.com/a/27016855/802138) about it, and I think I'm wrong -- directives like ng-if and ng-view call jqLite's `element.remove()` which deregisters jquery event handlers. – Matthias Aug 23 '16 at 17:42
7

You're looking for the ngModel.$parsers and ngModel.$formatters.

I've put together a simple demo:

http://jsfiddle.net/BuriB/nD2tk/

  angular.module('app', [])
      .controller('TestCntrl', function TestCntrl ($scope) {
        $scope.value = 50000;
      })
      .directive('numberFormatter', ['$filter', function ($filter) {
        var decimalCases = 2,
            whatToSet = function (str) {
              /**
               * TODO:
               * don't allow any non digits character, except decimal seperator character
               */
              return str ? Number(str) : '';
            },
            whatToShow = function (num) {
              return '$' + $filter('number')(num, decimalCases);
            };

        return {
          restrict: 'A',
          require: 'ngModel',
          link: function (scope, element, attr, ngModel) {
            ngModel.$parsers.push(whatToSet);
            ngModel.$formatters.push(whatToShow);

            element.bind('blur', function() {
              element.val(whatToShow(ngModel.$modelValue))
            });
            element.bind('focus', function () {
              element.val(ngModel.$modelValue);
            });
          }
        };
      }]);

See, also, this Working Demo by @Eric Hannum.

vijay
  • 10,276
  • 11
  • 64
  • 79
BuriB
  • 1,343
  • 9
  • 16
  • Actually, `ngModel.$parsers.push(whatToSet);` and `ngModel.$formatters.push(whatToShow);` have no effect. You could as well remove them (in this particular case). – gkalpak May 29 '14 at 11:19
  • set's the initial state. – BuriB May 29 '14 at 12:38
  • You are right - the `$formatter` sets the initial value. But other than that they don't do much and you could use `.val()` to set the initial value as well to be more consistent. But it might actually be a better practice to have them there. – gkalpak May 29 '14 at 15:23
  • 2
    Hey, your fiddle wasn't working for me for several reasons so I updated it. Here's the one that ended up working for me. http://jsfiddle.net/nD2tk/48/ – Eric Hannum Nov 22 '16 at 01:04
0

you can use ng-focus to apply a filter/transformation yo your model to make it the raw value and ng-blur to make it the formatted value as for the formatting part am afraid you'll have to build your own filter. i don't know of any existing one to perform this operation, although maybe there is.

Dayan Moreno Leon
  • 5,357
  • 2
  • 22
  • 24
0
myApp.directive('myField',function($filter){
  return {
            restrict: 'E',
            require: '?ngModel',
            scope : true,
            template: '<input type="text" ng-focus="clearFormat()" ng-blur="formatField()"/>',
            link : function (scope, element, attrs,ctrl){
                var field = element.find('input');
                ctrl.$render = function() {
                    field.val($filter('currency')(ctrl.$viewValue));
                };
                scope.clearFormat = function(){
                   field.val(ctrl.$viewValue);
                }
                scope.formatField = function(){
                   field.val($filter('currency')(ctrl.$viewValue));
                }
                function updateViewValue() {
                    scope.$apply(function() {
                        ctrl.$setViewValue(field.val());
                    });
                }

                field.on('change', updateViewValue);
            }
        };
})

In the html

 <my-field ng-model="amount"></my-field>

This will work only if you use angular 1.2 or above. Else you need to implement ng-focus and and ng-blur by your own

Mathews
  • 909
  • 1
  • 7
  • 13
0

  angular.module('app', [])
        .controller('TestCntrl', function TestCntrl($scope) {
            $scope.value = 50000.23;
        })
        .directive('numberFormatter', ['$filter', function ($filter) {
            return {
                restrict: 'A',
                require: 'ngModel',
                scope: { 'decimal': '=decimal' },
                link: function (scope, element, attr, ngModel) {
                    ngModel.$parsers.push(whatToSet);
                    ngModel.$formatters.push(whatToShow);
                    element.bind('blur', function () {
                        element.val(whatToShow(ngModel.$modelValue, scope.decimal))
                    });
                    element.bind('focus', function () {
                        element.val(ngModel.$modelValue);
                    });
                    function whatToSet(str) {
                        var num = 0
                        var numbe = num !== undefined ? num : 0;
                        var strr = str.toString();
                        strr = strr.replace(',', '.');
                        numbe = parseFloat(strr);
                        return numbe;
                    }
                    function whatToShow(num) {
                        if (num) {
                            return '$' + $filter('number')(num, scope.decimal);
                        } else {
                            return '';
                        }
                    };
                }
            };
        }]);
<!doctype html>
<html lang="en">
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
</head>
<body ng-app="app" ng-controller="TestCntrl">
    <input type="text" id="exampleInput" name="input" ng-model="value" number-formatter decimal="'0'" /><br />
    <strong>value in the model:</strong> {{value}}
</body>
</html>
ravi chandra
  • 131
  • 8