Skip to content
This repository has been archived by the owner on Jan 24, 2019. It is now read-only.

Improvements to the mask module #16

Closed
pocesar opened this issue May 3, 2013 · 15 comments
Closed

Improvements to the mask module #16

pocesar opened this issue May 3, 2013 · 15 comments
Labels

Comments

@pocesar
Copy link
Contributor

pocesar commented May 3, 2013

Right now the mask module is very limited to simple regexes and digits. Would be nice to have a clearer way to (just brainstorming):

  • Add an option to use regex, might be hard to parse it and display a proper input placeholder
Usage example:

<input ui-mask="/[a-f0-9-]{32}/">
  • Add a optional group of characters
Usage example, using backticks to make it optional:

<input ui-mask="A`-AAA`-999">

Making it able to write B-718 as B-XYZ-819
  • Add a switch-case functionality
Usage example:
<input ui-mask ui-options="{switch: model[0]}" ng-model="model" ui-when="{'1':'(99) 99999-9999',else:'(99) 9999-9999'}">

Then for example, when the mask starts with 1, apply a diferent mask, else, apply
another mask (here in Brazil, the state of São Paulo got phone numbers that are 
(11) 99999-9999 and the rest of states are (21) 9999-9999
  • Make it possible to pass in a function to determine the mask (guess it would fix the above two problems)
Usage example:

<input ui-mask="func()">

[...]
angular.controller('TestCtrl', function($scope){
  $scope.func = function(){
    return function(){
      //(re-)apply mask depending on value, would this be called everytime a model is changed?
    }
  };
});
@ProLoser
Copy link
Member

ProLoser commented May 8, 2013

Take a look at #21.

You bring up a valid point and what I consider to be a much better route. I think we should (mainly) support regex, with a simpler alternative syntax. I think the ui-options or ui-on or ui-switch, etc is too much bloat.

Instead, it should be similar to ui-route, which allows you to use plaintext, regex (I think), and curly braces. By leveraging braces, you can insert methods or variables or switches or whatever you want to modify the pattern.

<input ui-mask="(999) 999-9999">
<input ui-mask="/\([0-9]{3}\) [0-9]{3}-[0-9]{4}/">
<input ui-mask="{{ isRegex && '/\([0-9]{3}\) [0-9]{3}-[0-9]{4}/' || '(999) 999-9999' }}">
<input ui-mask="{{ myMask(isPhone) }}">
...
<script>
$scope.myMask = function(isPhone) {
  if (isPhone)
    return '/\([0-9]{3}\) [0-9]{3}-[0-9]{4}/'
  else
    return '(999) 999-9999'
}
</script>

@pocesar
Copy link
Contributor Author

pocesar commented May 8, 2013

I guess even returning an object with the regex pattern itself and the mask, to make it easier to display to the user. parsing a regex for the possible. so the $myMask would be:

$scope.myMask = function(isPhone) {
  if (isPhone)
    return {'(___) ___-____': '/\([0-9]{3}\) [0-9]{3}-[0-9]{4}/'}
  else
    return '(999) 999-9999'
}

this way, there will be no need to parse the regex for the placeholders, I guess, but only in this instance (when using functions instead of string/regex masks)

@shaungrady
Copy link
Contributor

Using RegExp as a Mask

There are a couple more key components than just the placeholder that have to be processed from the mask.

  • maskCaretMap - An array of integers of positions within the input that the user is permitted to place the caret—if the caret is placed in an invalid position, the caret gets moved to the closest valid position (moved backwards if clicking into the input or deleting characters, moved forward if typing or otherwise adding content to the input).
  • maskPatterns - An array of single-character-matching RegExp patterns. This is the backbone of the directive that does all the work. It's easiest to explain by referencing the line number of the forEach that unmasks the value: https://github.com/angular-ui/ui-utils/blob/master/modules/mask/mask.js#L144

Given a mask of (999) 999-9999, maskCaretMap looks like [1, 2, 3, 6, 7, 8, 10, 11, 12, 13, 14] and maskPatterns looks like [/\d/, /\d/, /\d/, /\d/, /\d/, /\d/, /\d/, /\d/, /\d/, /\d/].

What maskPatterns really powers is stripping out characters that don't fit the mask. When a user types a character, the directive unmasks the input value to get the raw value and strip out any chars that don't match the mask, then remasks it to put back in the placeholder components. So if a user types a character that doesn't get matched by one of the RegExp patterns in maskPatterns, it gets stripped out before the user ever sees it appear in the input (except older IEs).

Now, the biggest hurdle is allowing a user to specify a RegExp-based mask is preserving that as-you-type masking and stripping of invalid chars. You still need some way to check on a character-by-character basis if that char is valid or not. Parsing a RegExp pattern, I'm sure you'll agree, would be... unfeasible.

I did a lot of thinking about this when I was in the process of rewriting the directive, and the closest I got to a solution was this: http://plnkr.co/edit/qKCgHmJqOg7iXczmup5i?p=preview Click on the MM: __ DD: __ YYYY: ____ mask and try entering a month or day of 44 or 99 or something else that's invalid. The mask being passed to the plugin looks like ["MM: ", /[01]/, /\d/, " DD: ", /[0-3]/, /\d/, " YYYY: ", /[12]/, /[90]/, /\d/, /\d/], and you can see the code to support that style of mask (commented out) here: https://github.com/angular-ui/ui-utils/blob/master/modules/mask/mask.js#L174

However, as I further considered this options, I began to ask myself, "at what point does this begin to overreach it's intended purpose?" Masking is not intended to be input validation—there are plenty of ways to validate inputs, and shoehorning that into a masking directive feels wrong. There isn't a clear line between the two, but I think the real intent behind input masking is to assist the user in entering the correct information the first time without confusion, possibly before a proper validator tells them it's wrong. I think this is the right decision, but I think there will always be those who will want the masking directive to do comprehensive validation on top of basic masking.

Optional Character Groups

This is interesting and I had never thought of that type of functionality. My first blush impression is that allowing that in the middle of a mask would result is some strange and undesirable UX behavior. I can see it being more appropriate for the end of a mask, such as for a phone extension. But, the big question is, how do you communicate to a user that one portion of the mask is optional and the other is not? In the case of phone extensions, I think you'd be better served by breaking it into two input fields, one for the phone, the other for the extension.

Are there any solid use cases for optional character groups out there? I'd like to think about this more, but I can't think of when using two inputs or swapping the mask wouldn't be a better solution.

Switch Case Functionality

This would be better accomplished in the controller. Set the mask in the scope, put a $watch on the NgModelController.$viewValue property of the input and change the mask based on the $viewValue.

Func-Based Mask

I don't understand, can you clarify this please?

@shaungrady
Copy link
Contributor

Ah, option character group makes sense in the context of #21 (comment)

@pocesar
Copy link
Contributor Author

pocesar commented May 8, 2013

@shaungrady you shed a light on my understanding of the mask directive.

well, the function based return a function that can adapt depending on the input. it would merely be a shortcut for a key down event I guess, I was just brainstorming, trying to make a concise idea out of it. It would regenerate the mask as the user types. since the user can't "skip" the carret, would make sense to make it realtime as it types. For example, the phone example I used, São Paulo area code is 11, so as soon the user fills the mask (11) the mask changes to (11) 99999-9999, if its (19), it changes back to (19) 9999-9999

and btw, that regexp mask using an array of regex is really clever! that solves a lot of problems with complicated patterns and still make the mask functional.

@pocesar
Copy link
Contributor Author

pocesar commented May 24, 2013

The new mask module is really nice, just a couple of "out the box" functionality, since the maskDefinitions can't be changed without patching the source:

      var maskDefinitions = {
          '9': /\d/,
          '1': /[0-1]/,
          '2': /[0-2]/,
          '3': /[0-3]/,
          '4': /[0-4]/,
          '5': /[0-5]/,
          '6': /[0-6]/,
          '7': /[0-7]/,
          '8': /[0-8]/,
          '0': /0/,
          'a': /[a-z]/,
          'A': /[a-zA-Z]/,
          '*': /[a-zA-Z0-9]/
      };

would be nice to have a way to add maskDefinitions too, either by a global setting, or a setting on the parent ui module

@ProLoser
Copy link
Member

@pocesar help us out by making a pull request 😄

@pocesar
Copy link
Contributor Author

pocesar commented May 25, 2013

will do, but first I got a question, how can I add "configs" to angular directives? (without HTML attrs, classes, etc)

this way I can add some stuff that I got in mind, that you guys can see if have it's way to the master branch

and how can I make the ui-mask directive use the viewValue instead of the modelValue? I want to submit my form with my data formatted

@ghost
Copy link

ghost commented Jan 9, 2014

well, the function based return a function that can adapt depending on the input. it would merely be a shortcut for a key down event I guess, I was just brainstorming, trying to make a concise idea out of it. It would regenerate the mask as the user types. since the user can't "skip" the carret, would make sense to make it realtime as it types. For example, the phone example I used, São Paulo area code is 11, so as soon the user fills the mask (11) the mask changes to (11) 99999-9999, if its (19), it changes back to (19) 9999-9999

and btw, that regexp mask using an array of regex is really clever! that solves a lot of problems with complicated patterns and still make the mask functional.

Anyone has an alternative to use for the exampe case of São Paulo phones?
I can't figure out a solution for this...

@pocesar
Copy link
Contributor Author

pocesar commented Jan 9, 2014

@wesleycoder get the mask from a scope/controller variable, check the input and change the mask as needed like

<input type="text" ui-mask="{{mask}}" ng-keyup="onKeyUp()" ng-model="myinput">
$scope.myinput = '';
var defaultMask  = '(99) 9999-9999';
$scope.mask = defaultMask;
$scope.onKeyUp = function(){
  if ($scope.myinput.slice(0,3) == '119') { // (11) 9 means mobile, or instead, you could use a regex
   $scope.mask = '(99) 99999-9999';
  } else {
    $scope.mask = defaultMask;
  }
};

Of course, you could just wrap everything in a directive. I'm just being verbose here (done the above code from my head, didn't test it tho).

@saulovenancio
Copy link

are these improvement ready or just possible to be created on future?
tks.

@javierpavon2000
Copy link

any news about regular expression for ui-mask?
Thanks

@PowerKiKi
Copy link
Contributor

UI.Utils modules was split in individuals repositories. If still valid, please consider re-submitting on its dedicated repository: https://github.com/angular-ui/ui-mask

See the README for details.

@manar-mk
Copy link

manar-mk commented Sep 8, 2016

Hi, there a question about using regexp instead of your maskDefinitions. I know it is possible, but don't know how. <input ui-mask="^\d{0,7}$"> for 7 digits to input. Is there another tag or option?

@shaungrady
Copy link
Contributor

It's not possible, no. That's not how the directive functions, at a fundamental level.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

8 participants