13

Question Update: How can I prevent all characters except for the ones specified in a char array from being typed into an input field using AngularJS (or jQuery)?


Old Question:

I have a simple <input type="text" /> field in my AngularJS application and I want the user to only be able to enter the following characters into the field:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~

I know that I can add ng-pattern="allowed" to the <input> and then set $scope.allowed to some regex pattern and that will mark the input invalid if any invalid characters are entered, but I also want to prevent restricted characters from being input into the field AT ALL.

So my question is composed of two questions:

  1. What regex pattern do I use to restrict the character set to the one I posted above?
  2. How do I prevent illegal characters from being entered in the field? (e.g. if you type a lowercase letter then it won't appear in the field to begin with, similarly if you try to paste in text containing any illegal characters they will be removed immediately)
Kyle V.
  • 4,752
  • 9
  • 47
  • 81
  • use `[0-9]` instead of `0123456789`. enclose all characters in character class that is defined by `[...]+` – Braj Aug 20 '14 at 19:42
  • @user3218114 so would that look like `$scope.allowed = /[0-9][*+,-./:;]+/;` – Kyle V. Aug 20 '14 at 19:45
  • 1
    Look in [demo](http://regex101.com/r/wO8eU8/6). All the special character should be escaped that are part of regex pattern such as `[`, `]`, `.`, `*`, `{`, `}`, `-` etc. – Braj Aug 20 '14 at 19:47
  • You have to put the dash (`-`) at the beginning or the end, otherwise JS will think it’s a range: `/[-0-9*+,./:;]+/`. – bfontaine Aug 20 '14 at 19:47
  • 1
    @user3218114 Testing your demo link that definitely looks like the pattern I want, now I need to figure out how to restrict entry based off of that pattern. Thanks! – Kyle V. Aug 20 '14 at 19:53
  • @user3218114 Actually I spoke too soon, testing the pattern in Angular reveals that the pattern should actually have a `^` in front or else it will count as valid if only the last character is valid (See Plunker: http://plnkr.co/edit/VkcubapAVoqTnZ2WrddJ?p=preview) – Kyle V. Aug 20 '14 at 20:12

3 Answers3

11

The Angular way to do this is to make use of Angular's ngModelController.$parsers See the documentation:

$parsers

Array of functions to execute, as a pipeline, whenever the control reads value from the DOM. Each function is called, in turn, passing the value through to the next. The last return value is used to populate the model. Used to sanitize / convert the value as well as validation. For validation, the parsers should update the validity state using $setValidity(), and return undefined for invalid values.

Here is an example of a re-usable directive using this approach:

app.directive('inputRestrictor', [
  function() {
    return {
      restrict: 'A',
      require: 'ngModel',
      link: function(scope, element, attr, ngModelCtrl) {
        var pattern = /[^0-9A-Z !\\"#$%&'()*+,\-.\/:;<=>?@\[\]^_`{|}~]*/g;

        function fromUser(text) {
          if (!text)
            return text;

          var transformedInput = text.replace(pattern, '');
          if (transformedInput !== text) {
            ngModelCtrl.$setViewValue(transformedInput);
            ngModelCtrl.$render();
          }
          return transformedInput;
        }
        ngModelCtrl.$parsers.push(fromUser);
      }
    };
  }
]);

Here is a plnkr demo.

*Regex pattern for the above directive taken from Viktor Bahtev answer.

You can obviously extend this directive to take an input parameter as well. I'll leave that for your exersize.

Beyers
  • 8,968
  • 43
  • 56
  • is it really necessary to write this monstruous character class when you can simply write (if you take a look at the ascii table): `[ -\`{-~]` – Casimir et Hippolyte Aug 27 '14 at 22:40
  • An other thing: your pattern can match an empty string since you use the `*` quantifier. Is it really what you want? (note that with this pattern you will replace nothing with nothing (that is useless) on each positions in the string until you encounter forbidden characters.) – Casimir et Hippolyte Aug 27 '14 at 22:48
  • 1
    @CasimiretHippolyte I think you are missing the point of my answer. The answer is more about how to filter input text using the recommended AngularJs approach (i.e. using a directive with $parsers), than on the actual RegEx pattern. I thought that was clear given the fact that I clearly indicated I used the pattern from the already accepted answer. Just a suggestion, why dont you post an answer with your super-lean pattern? – Beyers Aug 27 '14 at 23:07
  • Oh, you have only copy/paste the pattern of the accepted answer! Please excuse me, I am so sorry. – Casimir et Hippolyte Aug 27 '14 at 23:29
  • About "why dont you post an answer with your super-lean pattern?". It's simple, I always comment an answer on a question when I haven't posted an answer myself. When I post an answer I don't comment other answers (or in very rare situations). – Casimir et Hippolyte Aug 27 '14 at 23:35
  • 2
    @CasimiretHippolyte, I think you miss the significance. Beyers was applying the regex (all it really is) with an AngularJS Directive. This may not seem important until you have done significant work with AngularJS, but performance is better and applications much cleaner if you avoid the use of JQuery and create a purely AngularJS Answer. I don't believe the answer merits a down-vote, and while OP was happy with the hybrid answer, I find this useful and worthy of a vote-up. – Dave Alperovich Aug 28 '14 at 09:10
  • @DaveA Thank you Dave, you obviously grasped the intent of my answer. – Beyers Aug 28 '14 at 09:17
7

To 'restrict some characters from being typed in' what comes in my mind is to attach event handlers for 'keyup', 'change', 'paste' events on inputs and when they are triggered to 'clean' their values against your pattern. I implemented the logic as jQuery plugin but you can adapt it to angular, use better naming or whatever you want.

The plugin:

$.fn.restrictInputs = function(restrictPattern){
    var targets = $(this);

    // The characters inside this pattern are accepted
    // and everything else will be 'cleaned'
    // For example 'ABCdEfGhI5' become 'ABCEGI5'
    var pattern = restrictPattern || 
        /[^0-9A-Z !\\"#$%&'()*+,\-.\/:;<=>?@\[\]^_`{|}~]*/g; // default pattern

    var restrictHandler = function(){
        var val = $(this).val();
        var newVal = val.replace(pattern, '');

        // This condition is to prevent selection and keyboard navigation issues
        if (val !== newVal) {
            $(this).val(newVal);
        }
    };

    targets.on('keyup', restrictHandler);
    targets.on('paste', restrictHandler);
    targets.on('change', restrictHandler);
};

Usage:

$('input').restrictInputs();

// Or ...

$('.my-special-inputs-decorated-with-this-class').restrictInputs();

Here is a JsFiddle Demo

Note: you can try to change the implementation to accept string with forbidden characters instead of regular expression and create the regex dynamically. Also you may find others events which are appropriate for triggering the 'cleaning'.

Viktor Bahtev
  • 4,858
  • 2
  • 31
  • 40
  • 1
    hmmmm... this would definitely work, but is most likely to cause issues with Angular Model being updated. There are problems with JQuery and Angular playing nice together – Dave Alperovich Aug 27 '14 at 19:16
  • 1
    @DaveA I am not very familiar with AngularJS and the OP asked for Angular or jQuery solution so I went for jQuery. If you think that this solution can be adapted to Angular feel free to update my answer, to give suggestions or to post another answer :) – Viktor Bahtev Aug 27 '14 at 19:48
  • 1
    Viktor, I agree that you have a good implementation. It may in fact be usable with Angular. I will think this over! – Dave Alperovich Aug 27 '14 at 19:51
  • 1
    Victor, take a look at my Plunker. I added your regex to Alex's and I think I have a proof of your concept!http://plnkr.co/edit/PiW7yFotqTylDTY3d8Ae?p=preview – Dave Alperovich Aug 27 '14 at 20:10
  • +1 it works. Only citicism, your regex allows single quotes and spaces – Dave Alperovich Aug 27 '14 at 20:11
  • I came across a solution on my own that is identical to this one but used ng-keyup, ng-paste, ng-change, etc. so I will mark this as the correct answer. Thanks! – Kyle V. Aug 27 '14 at 20:24
5

Try to use regExp to filter unnecessary characters on ng-keypress by passing $event.

# It'll be more clear in plnk!

Alex Cross
  • 71
  • 4
  • This works except that I can copy+paste in uppercase characters to get around it. I was thinking validate the model on `ng-change` and just remove invalid characters from the model by replacing them with `""` but I don't know how to do a string replace on every char not included in the regex set... – Kyle V. Aug 20 '14 at 20:40
  • As I understand you don't want to pass capital letters so i suppose that is [plnk](http://plnkr.co/edit/dHIS0Ys02uZck0Cxxz4B) what you want. – Alex Cross Aug 20 '14 at 20:51
  • No I want to remove all characters that AREN'T in the list in the OP. – Kyle V. Aug 20 '14 at 21:10
  • @StickFigs, cut and paste has long been a "sticky" problem with model binding and validation. The paste event is NOT an event that AngularJS naturally listens to. Adjust Alex's answer to validate when the input loses focus. That is not ideal, but is probably as close as you will come to validating after a cut-paste. – Dave Alperovich Aug 27 '14 at 18:40
  • @AlexCross, a suggestion for future answers. Plunkers are Great, but include the vital snippets of code in your answer too. This makes a higher quality answer and one that is NOT dependent on this Plunker or the site as continuing. The best answers are a combination of 1) explanation, 2) code snippets, 3) Plnker / Fiddle – Dave Alperovich Aug 27 '14 at 18:43
  • @StickFigs, I tried Alex's Plunkr and was not able to paste any caps. Please take me through your work. The downside is it DOES NOT ALLOW any characters that are lower case to be pasted either. Can both of you confirm what I see? If so, LMK if this works for OP or not. – Dave Alperovich Aug 27 '14 at 19:29