Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ng-pattern not working with angular ui datepicker

I want to use ng-pattern to further enforce the date format on an input field using Angular UI datepicker. The problem is when I do this it's always showing invalid.

<p class="input-group">
    <input type="text" datepicker-popup="MM/dd/yyyy" is-open="opened" close-text="Close"
        ng-model="someDate" class="form-control" placeholder="mm/dd/yyyy"
        ng-pattern="/^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/" required />
    <span class="input-group-btn">
        <button type="button" class="btn btn-default" ng-click="open($event)">
            <i class="glyphicon glyphicon-calendar"></i>
        </button>
    </span>
</p>

If I apply the same ng-pattern to a plain input field (no datepicker), it works as expected.

There seems to be a conflict but I'm not sure what it is. Any ideas?

Update:

Here is a simple plunker to illustrate the issue as requested. After some further digging it does seem to be running the pattern against the underlying Date object. When I use a custom directive that formats the date, however, it runs it against the actual input.

The only documentation I see for ng-pattern is a brief mention here under input. Is there anything else that maybe I'm missing?

like image 642
aw04 Avatar asked May 20 '14 15:05

aw04


2 Answers

As I mentioned in my comment, what is happening is that the datepicker directive is changing the model value from a String to a Date object. When ngPattern tried to validate the Date object it will fail because the string value of a Date is not going to match the pattern that you are using.

What you can do is create your own directive that hooks into the $parsers of the ngModelController to run your pattern check and then call $setValidity() based on what the value is. $parsers is actually built for this type of functionality where you want to run your own custom validation on a ngModel value.

Following these bullets is a directive that will accomplish the functionality that you want. I want to explain the logic in the directive before I show you the code:

  • In order to add your own $parser you have to add a require for ngModel in your directive definition so that you can access the ngModelController
  • Because RegExp expects the pattern argument without the beginning and trailing slash, I removed them in the pattern directive argument
  • datepicker changes the model value from a String to a Date. Since datepicker uses unshift to add it's $parser function to the beginning of the $parsers array, we also need to use unshift to put our $parser before datepicker's $parser.
  • As you mentioned, the datepicker directive will take any value that it can parse into a date and use that for the model. In order to make sure that only dates which match the pattern are used, I'm returning undefined (as specified in the docs) for dates that don't match the pattern. I'm not doing this check for values that come in as a Date object since that means that it was chosen using the datepicker widget.
  • As I just alluded to in the previous bullet, I'm not doing the validity check if the value is already a Date object since that means that it was chosen using the datepicker widget and that means that it was a valid date.

directive code

app.directive('awDatepickerPattern',function() {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope,elem,attrs,ngModelCtrl) {
      var dRegex = new RegExp(attrs.awDatepickerPattern);

      ngModelCtrl.$parsers.unshift(function(value) {

        if (typeof value === 'string') {
          var isValid = dRegex.test(value);
          ngModelCtrl.$setValidity('date',isValid);
          if (!isValid) {
            return undefined;
          }
        }

        return value;
      });

    }
  };
});

html - make sure that you don't forget to remove the beginning and trailing slash in the regex definition:

aw-datepicker-pattern="^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$"

And here is an updated plunker with my code.

So why again didn't ng-pattern work?

In case you didn't notice the root cause for why ng-pattern doesn't work when used with the datepicker directive is because the datepicker directive adds it's own ngModelController $parser to the beginning of the $parsers array by using unshift, which changes the model value from a String to a Date.

like image 121
JoseM Avatar answered Oct 05 '22 18:10

JoseM


It seems that an alternative solution to the other workarounds posted here is to upgrade to AngularJS 1.4.5, released 2015-08-28:

https://github.com/angular/angular.js/blob/master/CHANGELOG.md#145-permanent-internship-2015-08-28

like image 21
Alexandros Vellis Avatar answered Oct 05 '22 19:10

Alexandros Vellis