@@ -12,6 +12,7 @@ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\
12
12
var EMAIL_REGEXP = / ^ [ A - Z a - z 0 - 9 . _ % + - ] + @ [ A - Z a - z 0 - 9 . - ] + \. [ A - Z a - z ] { 2 , 6 } $ / ;
13
13
var NUMBER_REGEXP = / ^ \s * ( \- | \+ ) ? ( \d + | ( \d * ( \. \d * ) ) ) \s * $ / ;
14
14
var DATE_REGEXP = / ^ ( \d { 4 } ) - ( \d { 2 } ) - ( \d { 2 } ) $ / ;
15
+ var DATETIMELOCAL_REGEXP = / ^ ( \d { 4 } ) - ( \d \d ) - ( \d \d ) T ( \d \d ) : ( \d \d ) $ / ;
15
16
16
17
var inputType = {
17
18
@@ -156,6 +157,71 @@ var inputType = {
156
157
*/
157
158
'date' : dateInputType ,
158
159
160
+ /**
161
+ * @ngdoc inputType
162
+ * @name ng.directive:input.dateTimeLocal
163
+ *
164
+ * @description
165
+ * HTML5 or text input with datetime validation and transformation. In browsers that do not yet support
166
+ * the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601
167
+ * local datetime format (yyyy-MM-ddTHH:mm), for example: `2010-12-28T14:57`. Will also accept a valid ISO
168
+ * datetime string or Date object as model input, but will always output a Date object to the model.
169
+ *
170
+ * @param {string } ngModel Assignable angular expression to data-bind to.
171
+ * @param {string= } name Property name of the form under which the control is published.
172
+ * @param {string= } min Sets the `min` validation error key if the value entered is less than `min`.
173
+ * @param {string= } max Sets the `max` validation error key if the value entered is greater than `max`.
174
+ * @param {string= } required Sets `required` validation error key if the value is not entered.
175
+ * @param {string= } ngRequired Adds `required` attribute and `required` validation constraint to
176
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
177
+ * `required` when you want to data-bind to the `required` attribute.
178
+ * @param {string= } ngChange Angular expression to be executed when input changes due to user
179
+ * interaction with the input element.
180
+ *
181
+ * @example
182
+ <doc:example>
183
+ <doc:source>
184
+ <script>
185
+ function Ctrl($scope) {
186
+ $scope.value = new Date(2010, 11, 28, 14, 57); // or '2010-12-28T14:57';
187
+ }
188
+ </script>
189
+ <form name="myForm" ng-controller="Ctrl as dateCtrl">
190
+ Pick a date between in 2013:
191
+ <input type="datetime-local" name="input" ng-model="value"
192
+ placeholder="yyyy-MM-ddTHH:mm" min="2001-01-01T00:00" max="2013-12-31T00:00" required />
193
+ <span class="error" ng-show="myForm.input.$error.required">
194
+ Required!</span>
195
+ <span class="error" ng-show="myForm.input.$error.datetimelocal">
196
+ Not a valid date!</span>
197
+ <tt>value = {{value}}</tt><br/>
198
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
199
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
200
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
201
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
202
+ </form>
203
+ </doc:source>
204
+ <doc:scenario>
205
+ it('should initialize to model', function() {
206
+ expect(binding('value')).toEqual('2010-12-28T14:57');
207
+ expect(binding('myForm.input.$valid')).toEqual('true');
208
+ });
209
+
210
+ it('should be invalid if empty', function() {
211
+ input('value').enter('');
212
+ expect(binding('value')).toEqual('');
213
+ expect(binding('myForm.input.$valid')).toEqual('false');
214
+ });
215
+
216
+ it('should be invalid if over max', function() {
217
+ input('value').enter('2015-01-01T23:59');
218
+ expect(binding('value')).toEqual('');
219
+ expect(binding('myForm.input.$valid')).toEqual('false');
220
+ });
221
+ </doc:scenario>
222
+ </doc:example>
223
+ */
224
+ 'datetime-local' : dateTimeLocalInputType ,
159
225
/**
160
226
* @ngdoc inputType
161
227
* @name ng.directive:input.number
@@ -603,7 +669,77 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
603
669
}
604
670
}
605
671
606
- function dateInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
672
+ function dateTimeLocalInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
673
+ textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
674
+
675
+ ctrl . $parsers . push ( function ( value ) {
676
+ if ( ctrl . $isEmpty ( value ) ) {
677
+ ctrl . $setValidity ( 'datetimelocal' , true ) ;
678
+ return value ;
679
+ }
680
+
681
+ if ( DATETIMELOCAL_REGEXP . test ( value ) ) {
682
+ ctrl . $setValidity ( 'datetimelocal' , true ) ;
683
+ return new Date ( getTime ( value ) ) ;
684
+ }
685
+
686
+ ctrl . $setValidity ( 'datetimelocal' , false ) ;
687
+ return undefined ;
688
+ } ) ;
689
+
690
+ ctrl . $formatters . push ( function ( value ) {
691
+ if ( isDate ( value ) ) {
692
+ return $filter ( 'date' ) ( value , 'yyyy-MM-ddTHH:mm' ) ;
693
+ }
694
+ return ctrl . $isEmpty ( value ) ? '' : '' + value ;
695
+ } ) ;
696
+
697
+ if ( attr . min ) {
698
+ var minValidator = function ( value ) {
699
+ var valid = ctrl . $isEmpty ( value ) ||
700
+ ( getTime ( value ) >= getTime ( attr . min ) ) ;
701
+ ctrl . $setValidity ( 'min' , valid ) ;
702
+ return valid ? value : undefined ;
703
+ } ;
704
+
705
+ ctrl . $parsers . push ( minValidator ) ;
706
+ ctrl . $formatters . push ( minValidator ) ;
707
+ }
708
+
709
+ if ( attr . max ) {
710
+ var maxValidator = function ( value ) {
711
+ var valid = ctrl . $isEmpty ( value ) ||
712
+ ( getTime ( value ) <= getTime ( attr . max ) ) ;
713
+ ctrl . $setValidity ( 'max' , valid ) ;
714
+ return valid ? value : undefined ;
715
+ } ;
716
+
717
+ ctrl . $parsers . push ( maxValidator ) ;
718
+ ctrl . $formatters . push ( maxValidator ) ;
719
+ }
720
+
721
+ function getTime ( iso ) {
722
+ if ( isDate ( iso ) ) {
723
+ return + iso ;
724
+ }
725
+
726
+ if ( isString ( iso ) ) {
727
+ DATETIMELOCAL_REGEXP . lastIndex = 0 ;
728
+ var parts = DATETIMELOCAL_REGEXP . exec ( iso ) ,
729
+ yyyy = + parts [ 1 ] ,
730
+ MM = + parts [ 2 ] - 1 ,
731
+ dd = + parts [ 3 ] ,
732
+ HH = + parts [ 4 ] ,
733
+ mm = + parts [ 5 ] ;
734
+
735
+ return + new Date ( yyyy , MM , dd , HH , mm ) ;
736
+ }
737
+
738
+ return NaN ;
739
+ }
740
+ }
741
+
742
+ function dateInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
607
743
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
608
744
609
745
ctrl . $parsers . push ( function ( value ) {
@@ -623,13 +759,7 @@ function dateInputType(scope, element, attr, ctrl, $sniffer, $browser) {
623
759
624
760
ctrl . $formatters . push ( function ( value ) {
625
761
if ( isDate ( value ) ) {
626
- var year = value . getFullYear ( ) ,
627
- month = value . getMonth ( ) + 1 ,
628
- day = value . getDate ( ) ;
629
-
630
- month = ( month < 10 ? '0' : '' ) + month ;
631
- day = ( day < 10 ? '0' : '' ) + day ;
632
- return year + '-' + month + '-' + day ;
762
+ return $filter ( 'date' ) ( value , 'yyyy-MM-dd' ) ;
633
763
}
634
764
return ctrl . $isEmpty ( value ) ? '' : '' + value ;
635
765
} ) ;
@@ -950,14 +1080,14 @@ function checkboxInputType(scope, element, attr, ctrl) {
950
1080
</doc:scenario>
951
1081
</doc:example>
952
1082
*/
953
- var inputDirective = [ '$browser' , '$sniffer' , function ( $browser , $sniffer ) {
1083
+ var inputDirective = [ '$browser' , '$sniffer' , '$filter' , function ( $browser , $sniffer , $filter ) {
954
1084
return {
955
1085
restrict : 'E' ,
956
1086
require : '?ngModel' ,
957
1087
link : function ( scope , element , attr , ctrl ) {
958
1088
if ( ctrl ) {
959
1089
( inputType [ lowercase ( attr . type ) ] || inputType . text ) ( scope , element , attr , ctrl , $sniffer ,
960
- $browser ) ;
1090
+ $browser , $filter ) ;
961
1091
}
962
1092
}
963
1093
} ;
0 commit comments