ivarni
ivarni

Reputation: 17878

Can I use ngModelController's $formatter to fully control how an input field is rendered?

I am trying to make an input that simulates type="number" but I want to add my own buttons to it. Basically I've made

<form name="fasdf">
    <button class="btn" ng-click="decrement()">&#9668;</button>
    <input type="text" ng-model="model" display-as-number />
    <button class="btn" ng-click="increment()">&#9658;</button>
</form>

I'm using input type="text" because I don't want the user agent to add it's own buttons to the field.

It should be possible to use the input itself to enter a value as well as using the buttons to increment or decrement the value.

angular.module('app',[])
.controller('Ctrl', ['$scope', function($scope) {

  $scope.model = 0;

  $scope.decrement = function() {
    $scope.model--;
  };

  $scope.increment = function() {
    $scope.model++;
  };

}])

The value is initially 0 but when the user starts entering text I want any leading zeros to be stripped as they type (well, actually the suits want that but you get my drift).

My theory was that I could use a $formatter and/or a $parser to do this by making a directive that accesses the ngModelController.

angular.module('app')
.directive('displayAsNumber', [function() {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, element, attrs, controller) {

        controller.$formatters.push(function (modelValue) {
            var value = parseInt(modelValue, 10);
            return value;
        });

        controller.$parsers.push(function (viewValue) {
            var value = parseInt(viewValue, 10);
            return value;
        });          
    };
}]);

But while the $formatter is being run every time I type something in the input, it is not overriding the actual value of the input field, in other words it is not stripping any leading zeros.

I can get this working by listening on DOM events (although I can't change the value on each keyup as that will move the cursor to the end of the field) by replacing the directive body with

element.on('focus', function() {
  var value = parseInt(element.val(), 10);
  if (value === 0)
    element.val('');
});

element.on('focusout', function() {
  if ('' === element.val().trim())
    element.val(0);
  else
    element.val(parseInt(element.val(), 10));
});

The resulting behaviour is good enough (it blanks out the field on focus if the value is 0 and strips leading 0s on focusout.

Question is, is there any way I could have achieved this behaviour without adding event-listeners to element? How do I properly control how the input is rendered using ngModelController?

Plnkr: http://plnkr.co/edit/YOkFGNGamfA1Xb9pOP1U?p=preview

Upvotes: 2

Views: 2945

Answers (1)

ivarni
ivarni

Reputation: 17878

So, yeah, after looking at other new questions I saw this and realized I was simply missing a call to controller.$render()

controller.$parsers.push(function (viewValue) {
  var value = parseInt(viewValue, 10);
  controller.$viewValue = value;
  controller.$render();
  return value;
});

Fixes my problem. I'll go away now.

Upvotes: 3

Related Questions