Deric Lima
Deric Lima

Reputation: 2092

Angular restrict input only to number on pasting

I have this directive that runs in <input type='text'> to restrict the user input to type only numbers. Works fine!

My problem is that if the user copy/paste some string, angular is accepting the value and sending the string to the backend.

angular.module('autonumeric', []).directive('ngNumeric', [function () {
'use strict';
// Declare a empty options object
var options = {};
return {
    // Require ng-model in the element attribute for watching changes.
    require: '?ngModel',
    // This directive only works when used in element's attribute (e.g: cr-numeric)
    restrict: 'A',
    compile: function (tElm, tAttrs) {

        var isTextInput = tElm.is('input:text');

        return function (scope, elm, attrs, controller) {
            // Get instance-specific options.
            var opts = angular.extend({}, options, scope.$eval(attrs.crNumeric));

            // Helper method to update autoNumeric with new value.
            var updateElement = function (element, newVal) {
                // Only set value if value is numeric
                if ($.isNumeric(newVal))
                    element.autoNumeric('set', newVal);
            };    

            // Initialize element as autoNumeric with options.
            elm.autoNumeric(opts);

            // if element has controller, wire it (only for <input type="text" />)
            if (controller && isTextInput) {
                // render element as autoNumeric
                controller.$render = function () {
                    updateElement(elm, controller.$viewValue);
                }
                // watch for external changes to model and re-render element
                scope.$watch(tAttrs.ngModel, function (current, old) {
                    //controller.$render();
                    updateElement(elm, controller.$viewValue);
                });
                // Detect changes on element and update model.
                elm.on('change', function (e) {
                    scope.$apply(function () {
                        controller.$setViewValue(elm.autoNumeric('get'));
                    });
                });
            }
            else {
                // Listen for changes to value changes and re-render element.
                // Useful when binding to a readonly input field.
                if (isTextInput) {
                    attrs.$observe('value', function (val) {
                        updateElement(elm, val);
                    });
                }
            }
        }
    } // compile
} // return
}]);

I tried to use replace with a regex replace(/[^0-9.]/g, '') to remove all characters, but is not working. Does someone have some idea how to improve this directive to accept only numbers even on copying/pasting?

Upvotes: 2

Views: 3439

Answers (2)

Tympanix
Tympanix

Reputation: 113

Generally I think you're much better off by using <input type='number'>. Also remember that your backend should be robust and you shouldn't trust what comes from the client, even with your fancy directive.

If you however insist on using a text input maybe you can get some inspiration from this directive.

app.directive('ngNumeric', [function () {
  return {
    restrict: 'E',
    require: 'ngModel',
    scope: {
      model: '=ngModel'
    },
    compile: compile,
    replace: true,
    template: '<input type="text" ng-change="change()">'
  }

  function compile() {
    return link
  }

  function link(scope, element) {
    scope.change = function() {
    var numeric = element.val().replace(/[^0-9]/g, '')
    console.log('I changed', element.val())
    element.val(numeric)
    scope.model = numeric
    }
  }
}]);

Demo on Plunker

Upvotes: 0

Mistalis
Mistalis

Reputation: 18279

I've written a similar directive that only allows floats (and also convert , to .). It is simplest that your, so I think you could use it:

angular.module('myMod').directive('floatOnly', function() {
    return {
        require: 'ngModel',
        restrict: 'A',
        link: function(scope, element, attrs, modelCtrl) {
            modelCtrl.$parsers.push(function(inputValue) {
                if(inputValue === undefined) return '';

                // Remove forbidden characters
                cleanInputValue = inputValue.replace(',', '.') // change commas to dots
                                            .replace(/[^\d.]/g, '') // keep numbers and dots
                                            .replace(/\./, "x") // change the first dot in X
                                            .replace(/\./g, "") // remove all dots
                                            .replace(/x/, "."); // change X to dot
                if(cleanInputValue != inputValue) {
                    modelCtrl.$setViewValue(cleanInputValue);
                    modelCtrl.$render();
                }
                return cleanInputValue;
            });
        }
    }
});

You can use it as follow:

<input type="text" ng-model="myFloat" float-only/>

Demo on JSFiddle

Upvotes: 1

Related Questions