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
                }   
            });
        }
    }   

})

4 Answers 4

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]*$/">

Sign up to request clarification or add additional context in comments.

1 Comment

This expects that user presses key at the last position of input. This is not certain.
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

2 Comments

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!
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.
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;
            }
          });
        }
    }   
});

6 Comments

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.
thx for your answer that helps me a lot :) what do you mean by using an expression instead of an array? a regex?
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).
Did a solution with a regex myself :) jsfiddle.net/kannix/Q5YKE/1 But i have one more question why do use $parse ? seems to work fine without it?
I was originally pulling the attributes from the DOM. It shouldn't be needed anymore
|
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);
                }
            });
        }
    }
}]);

2 Comments

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.
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!).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.