4

I neeed an input field where I can enter only the values 1,2 or 3 so i'm trying to build a directive which prevents all changes to the model if it doesn't match these values.
eg the value is 1 and I change it to 5 it should be still 1.

I've put together a small fiddle http://jsfiddle.net/kannix/Q5YKE/ but it's most likely wrong to use the $parsers.

app.directive('myvalidator', function () {
    return {
        require: 'ngModel',
        link: function (scope, elm, attrs, ctrl) {
            var validValues = [1,2,3];
            ctrl.$parsers.push(function (value) {
                if (validValues.indexOf(value) === -1){
                    //what to do here? should refuse wrong value and leave the old one
                }   
            });
        }
    }   

})
timgeb
  • 76,762
  • 20
  • 123
  • 145
kannix
  • 5,107
  • 6
  • 28
  • 47

4 Answers4

24

I recently wrote a directive just for this. It takes a regExp object that validates the incoming key presses and only permits them if are valid:

// forces keystrokes that satisfy the regExp passed
app.directive("regExpRequire", function() {

    var regexp;
    return {
        restrict: "A",
        link: function(scope, elem, attrs) {
            regexp = eval(attrs.regExpRequire);

            var char;
            elem.on("keypress", function(event) {
                char = String.fromCharCode(event.which)
                if(!regexp.test(elem.val() + char))
                    event.preventDefault();
            })
        }
    }

})

Template usage: <input type="text" reg-exp-require="/^[a-zA-Z]$/">

Or in your case: <input type="text" reg-exp-require="/^[1-3]*$/">

Ian Haggerty
  • 1,741
  • 16
  • 22
6

I'd recommend using the ng-pattern-restrict directive.

Get the library and simply decorate your input like so:

<input type="text" pattern="[0-9]+" ng-pattern-restrict />

GitHub: AlphaGit/ng-pattern-restrict

Mobiletainment
  • 22,201
  • 9
  • 82
  • 98
  • Geez, I wish I found this earlier. I'm amazed at how many hours I've already spent trying to prevent a keypress from occurring via a directive. Thanks! – Adam Plocher Mar 01 '17 at 08:56
  • Btw, the pattern should probably be `^[0-9]*$` otherwise you will never be able to delete the last digit of text since you're requiring one or more numbers. – Adam Plocher Mar 01 '17 at 08:59
4

You could always listen to the keypress event and prevent the character from making it through. Here is a plunker

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
  $scope.validValues = ['a','1','2'];
});

app.directive('myValidator', function ($parse) {
    return {
        scope: {
          validValues: '=validValues'
        },
        link: function (scope, elm, attrs) {
          elm.bind('keypress', function(e){
            var char = String.fromCharCode(e.which||e.charCode||e.keyCode), matches = [];
            angular.forEach(scope.validValues, function(value, key){
              if(char === value) matches.push(char);
            }, matches);
            if(matches.length == 0){
              e.preventDefault();
              return false;
            }
          });
        }
    }   
});
Brian Lewis
  • 5,739
  • 1
  • 21
  • 28
  • A cleaner approach would be to use an expression instead of iterating through an Array. I was just trying to keep you from having to change your code up too much. – Brian Lewis Aug 14 '13 at 21:42
  • thx for your answer that helps me a lot :) what do you mean by using an expression instead of an array? a regex? – kannix Aug 14 '13 at 22:00
  • Yes. A regular expression should perform a little better and will make things a lot cleaner(depending on how complex your expression is... I've seen some ugly ones). – Brian Lewis Aug 14 '13 at 22:06
  • Did a solution with a regex myself :) http://jsfiddle.net/kannix/Q5YKE/1/ But i have one more question why do use $parse ? seems to work fine without it? – kannix Aug 14 '13 at 22:16
  • I was originally pulling the attributes from the DOM. It shouldn't be needed anymore – Brian Lewis Aug 14 '13 at 22:21
  • Can still be pasted to. – Envil Feb 24 '17 at 02:19
2

I've actually had to build onto and modify the answer form Ian Haggerty. His code worked well, until I started to test it in different ways. I was specifically trying to test for values less than 100, but I was getting some strange results.

If I had 100 in my input, then tried to insert a decimal to make it 10.0, Ian's fix didn't account for this and said it wasn't matching my regex (even though I allow up to two decimals). Turns out that it always appended the character that I pressed at the END of the string it was testing, even though I was inserting it in the middle.

My change was to store the original value on "keypress", then on "keyup" (or "change" if you prefer), it does the checking of the new value. If invalid, then it reverts back to the original.

Unfortunately it does update the model briefly, but at least it lets you type characters in the middle or beginning of the input value and still match against the regex correctly. In Angular 1.3, we can probably use ng-model-options="{debounce:250}" to counter this. Having any code that relies on this model change be indempotent helps immensely.

usage: <input ... validate-with="/^([\d]{1,2}\.[\d]{1,2}|\.?[\d]{1,2}|100)?$/" />

.directive("validateWith", [function(){
    return {
        restrict: "A",
        link: function($scope, $el, $attrs){
            var regexp = eval($attrs.validateWith);
            var origVal;
            // store the current value as it was before the change was made
            $el.on("keypress", function(e){
                origVal = $el.val();
            });

            // after the change is made, validate the new value
            // if invalid, just change it back to the original
            $el.on("keyup", function(e){
                if(!regexp.test($el.val())){
                    $el.val(origVal);
                }
            });
        }
    }
}]);
coblr
  • 3,008
  • 3
  • 23
  • 31
  • Actually, while this updates the value in the input field, the model has become corrupted in my code. This is closer to what I'm looking for, but needs to update the model again on keyup, not just input value. – coblr Jun 05 '14 at 20:57
  • Ah yes - nice addition. It's amazing what you don't think of when scratching things together. This would probably be more user friendly too (how annoying is it a key press does nothing!). – Ian Haggerty Jun 26 '14 at 22:37