|
11 | 11 | var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
|
12 | 12 | var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;
|
13 | 13 | var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
|
| 14 | +var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; |
14 | 15 |
|
15 | 16 | var inputType = {
|
16 | 17 |
|
@@ -89,6 +90,71 @@ var inputType = {
|
89 | 90 | */
|
90 | 91 | 'text': textInputType,
|
91 | 92 |
|
| 93 | + /** |
| 94 | + * @ngdoc inputType |
| 95 | + * @name ng.directive:input.date |
| 96 | + * |
| 97 | + * @description |
| 98 | + * HTML5 or text input with date validation and transformation. In browsers that do not yet support |
| 99 | + * the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601 |
| 100 | + * date format (yyyy-MM-dd), for example: `2009-01-06`. Will also accept a valid ISO date or Date object |
| 101 | + * as model input, but will always output a Date object to the model. |
| 102 | + * |
| 103 | + * @param {string} ngModel Assignable angular expression to data-bind to. |
| 104 | + * @param {string=} name Property name of the form under which the control is published. |
| 105 | + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. |
| 106 | + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. |
| 107 | + * @param {string=} required Sets `required` validation error key if the value is not entered. |
| 108 | + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to |
| 109 | + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of |
| 110 | + * `required` when you want to data-bind to the `required` attribute. |
| 111 | + * @param {string=} ngChange Angular expression to be executed when input changes due to user |
| 112 | + * interaction with the input element. |
| 113 | + * |
| 114 | + * @example |
| 115 | + <doc:example> |
| 116 | + <doc:source> |
| 117 | + <script> |
| 118 | + function Ctrl($scope) { |
| 119 | + $scope.value = '2013-10-22'; |
| 120 | + } |
| 121 | + </script> |
| 122 | + <form name="myForm" ng-controller="Ctrl as dateCtrl"> |
| 123 | + Pick a date between in 2013: |
| 124 | + <input type="date" name="input" ng-model="value" |
| 125 | + placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> |
| 126 | + <span class="error" ng-show="myForm.input.$error.required"> |
| 127 | + Required!</span> |
| 128 | + <span class="error" ng-show="myForm.input.$error.date"> |
| 129 | + Not a valid date!</span> |
| 130 | + <tt>value = {{value}}</tt><br/> |
| 131 | + <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> |
| 132 | + <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> |
| 133 | + <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> |
| 134 | + <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> |
| 135 | + </form> |
| 136 | + </doc:source> |
| 137 | + <doc:scenario> |
| 138 | + it('should initialize to model', function() { |
| 139 | + expect(binding('value')).toEqual('2013-10-22'); |
| 140 | + expect(binding('myForm.input.$valid')).toEqual('true'); |
| 141 | + }); |
| 142 | +
|
| 143 | + it('should be invalid if empty', function() { |
| 144 | + input('value').enter(''); |
| 145 | + expect(binding('value')).toEqual(''); |
| 146 | + expect(binding('myForm.input.$valid')).toEqual('false'); |
| 147 | + }); |
| 148 | +
|
| 149 | + it('should be invalid if over max', function() { |
| 150 | + input('value').enter('2015-01-01'); |
| 151 | + expect(binding('value')).toEqual(''); |
| 152 | + expect(binding('myForm.input.$valid')).toEqual('false'); |
| 153 | + }); |
| 154 | + </doc:scenario> |
| 155 | + </doc:example> |
| 156 | + */ |
| 157 | + 'date': dateInputType, |
92 | 158 |
|
93 | 159 | /**
|
94 | 160 | * @ngdoc inputType
|
@@ -530,6 +596,80 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
530 | 596 | }
|
531 | 597 | }
|
532 | 598 |
|
| 599 | +function dateInputType(scope, element, attr, ctrl, $sniffer, $browser) { |
| 600 | + textInputType(scope, element, attr, ctrl, $sniffer, $browser); |
| 601 | + |
| 602 | + ctrl.$parsers.push(function(value) { |
| 603 | + if(ctrl.$isEmpty(value)) { |
| 604 | + ctrl.$setValidity('date', true); |
| 605 | + return value; |
| 606 | + } |
| 607 | + |
| 608 | + if(DATE_REGEXP.test(value)) { |
| 609 | + ctrl.$setValidity('date', true); |
| 610 | + return new Date(getTime(value)); |
| 611 | + } |
| 612 | + |
| 613 | + ctrl.$setValidity('date', false); |
| 614 | + return undefined; |
| 615 | + }); |
| 616 | + |
| 617 | + ctrl.$formatters.push(function(value) { |
| 618 | + if(isDate(value)) { |
| 619 | + var year = value.getFullYear(), |
| 620 | + month = value.getMonth() + 1, |
| 621 | + day = value.getDate(); |
| 622 | + |
| 623 | + month = (month < 10 ? '0' : '') + month; |
| 624 | + day = (day < 10 ? '0' : '') + day; |
| 625 | + return year + '-' + month + '-' + day; |
| 626 | + } |
| 627 | + return ctrl.$isEmpty(value) ? '' : '' + value; |
| 628 | + }); |
| 629 | + |
| 630 | + if(attr.min) { |
| 631 | + var minValidator = function(value) { |
| 632 | + var valid = ctrl.$isEmpty(value) || |
| 633 | + (getTime(value) >= getTime(attr.min)); |
| 634 | + ctrl.$setValidity('min', valid); |
| 635 | + return valid ? value : undefined; |
| 636 | + }; |
| 637 | + |
| 638 | + ctrl.$parsers.push(minValidator); |
| 639 | + ctrl.$formatters.push(minValidator); |
| 640 | + } |
| 641 | + |
| 642 | + if(attr.max) { |
| 643 | + var maxValidator = function(value) { |
| 644 | + var valid = ctrl.$isEmpty(value) || |
| 645 | + (getTime(value) <= getTime(attr.max)); |
| 646 | + ctrl.$setValidity('max', valid); |
| 647 | + return valid ? value : undefined; |
| 648 | + }; |
| 649 | + |
| 650 | + ctrl.$parsers.push(maxValidator); |
| 651 | + ctrl.$formatters.push(maxValidator); |
| 652 | + } |
| 653 | + |
| 654 | + function getTime(iso) { |
| 655 | + if(isDate(iso)) { |
| 656 | + return +iso; |
| 657 | + } |
| 658 | + |
| 659 | + if(isString(iso)) { |
| 660 | + DATE_REGEXP.lastIndex = 0; |
| 661 | + var parts = DATE_REGEXP.exec(iso), |
| 662 | + yyyy = +parts[1], |
| 663 | + mm = +parts[2] - 1, |
| 664 | + dd = +parts[3], |
| 665 | + time = new Date(yyyy, mm, dd); |
| 666 | + return +time; |
| 667 | + } |
| 668 | + |
| 669 | + return NaN; |
| 670 | + } |
| 671 | +} |
| 672 | + |
533 | 673 | function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
534 | 674 | textInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
535 | 675 |
|
|
0 commit comments