0

Doing a quick POC in AngularJS to only allow specific input into a text box.

The goal is to check the value every time the user types a new character, if it fails the regular expression check, we need to either reject the character or roll it back to the previous value.

The way I see it, here are my 2 options: 1. Bind to keypress event, check what the new value would be against a regex, and return false if it fails, preventing the character from being accepted into the text box 2. Bind to keyup event, check what the new value is against a regex, and if it fails, revert it to the previous value

How can I accomplish this from my directive?

var currencyRegEx = /^\$?\-?([1-9]{1}[0-9]{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))$|^\-?\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))$|^\(\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))\)$/;
app.directive("currencyInput", function () {
    return {
        restrict: "A",
        scope: {
        },
        require: 'ngModel',
        link: function (scope, element, attrs, ngModelCtrl) {
            $(element).bind('keypress', function (event) {
                // TODO: Get what new value would be
                var newValue = "...";
                return currencyRegEx.test(newValue);

            });
            $(element).bind('keyup', function (event) {
                var newValue = $(this).val();
                if (!currencyRegEx.test(newValue)) {                
                    // TODO: Revert to previous value
                }
            });
        }
    }
});

<input type="text" class="form-control" ng-model="item.paymentAmount" currency-input />

EDIT w/ SOLUTION Here is the current solution we have in place in order to prevent non-digit input and rollback invalid currency value.

First, we created a new property "scope.prevValue" to hold the last valid value entered by the user. Then, on "keypress" we check to make sure the user typed a digit, comma, or period. Finally, on "keyup", we check the new value against the currency regex and rollback if needed.

var currencyRegEx = /^\$?\-?([1-9]{1}[0-9]{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))$|^\-?\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))$|^\(\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))\)$/;
var digitRegex = /^[0-9]*$/;
app.directive("currencyInput", function () {
    return {
        restrict: "A",
        scope: {},
        require: 'ngModel',
        link: function (scope, element, attrs, ngModelCtrl) {
            scope.prevValue = '';

            $(element).on('keypress', function(event) {
                var validAlphaChars = ['.', ','];
                var enteredCharacter = String.fromCharCode(event.charCode != null ? event.charCode : event.keyCode);
                if (validAlphaChars.indexOf(enteredCharacter) < 0 && !digitRegex.test(enteredCharacter)) {
                    return false;
                }
            });

            $(element).on('keyup', function (event) {
                var newValue = $(element).val();
                if (newValue.length > 0 && !currencyRegEx.test(newValue)) {
                    $(element).val(scope.prevValue);
                    return false;
                } else {
                    scope.prevValue = $(element).val();
                }
            });
        }
    });

EDIT w/ SOLUTION #2 (using Steve_at_IDV's approach on accepted answer)

var currencyRegEx = /^\$?\-?([1-9]{1}[0-9]{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))$|^\-?\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))$|^\(\$?([1-9]{1}\d{0,2}(\,\d{3})*(\.\d{0,2})?|[1-9]{1}\d{0,}(\.\d{0,2})?|0(\.\d{0,2})?|(\.\d{1,2}))\)$/;
app.directive("currencyInput", function () {
    return {
        restrict: "A",
        scope: {},
        require: 'ngModel',
        link: function (scope, element, attrs, ngModelCtrl) {
            ngModelCtrl.$parsers.push(function (value) {
                if (value.length > 0 && value != '.' && !currencyRegEx.test(value)) {
                    var prevValue = ngModelCtrl.$modelValue;
                    ngModelCtrl.$setViewValue(prevValue)
                    ngModelCtrl.$render();
                    return prevValue;
                }

                return value;
            });
        }
    }
});
1
  • Glad you figured it out. Another technique using only ngModel (instead of jQuery directly) is below. Commented May 23, 2016 at 20:09

2 Answers 2

2

This would be a good time to use ngModelCtrl.$parsers instead of binding to keypresses manually. Try something like this in your link function:

ngModelCtrl.$parsers.push( function (value) {
    // do some validation logic...it fails
    if (validationFails) {
        var prevValue = ctrl.$modelValue;
        ctrl.$setViewValue(prevValue); // set view
        ctrl.$render(); // render view
        return prevValue; // set model
    }

    // otherwise we're good!
    return value;
} );

Here is a Plunker which demonstrates. The input field will reject a lowercase z from being entered.

See the $parsers section of https://docs.angularjs.org/api/ng/type/ngModel.NgModelController for more info.

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

8 Comments

Thanks for your reply. I gave this a try but it doesn't revert back to the old value when I return false.
Ah yes sorry $validators is to modify the validity of the form input, I'll modify the answer to use $parsers.
Try this new answer out and see if that helps ya.
Gave this a try, I am able to maintain the correct "prevValue" but it still shows the bad value in the UI. Can I prevent the model update from this function?
As I read the docs more, maybe you need to call ngModelCtrl.$setViewValue(prevValue) in the validationFails block before returning prevValue. I might set up a plunkr to test this.
|
0

Firstly, I think you shouldn't modify the input of the user. I personnaly find it bad on a UX point of view. It's better to indicate that the input is in an error state by bordering in red for example.

Secondly, there is a directive that can fit your need, ng-pattern.

<input type="text" 
       class="form-control" 
       ng-model="item.paymentAmount" 
       ng-pattern="currencyRegEx" />

Some similar questions :

1 Comment

Thanks for your reply. I tried using ng-pattern but it doesn't seem to be what I need. I agree that we shouldn't modify the user input, but this is a requirement for our project :(

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.