@@ -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,72 @@ 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 = '2010-12-28T14:57';
187
+ }
188
+ </script>
189
+ <form name="myForm" ng-controller="Ctrl as dateCtrl">
190
+ Pick a date in 2013:
191
+ <input type="datetime-local" name="input" ng-model="value"
192
+ placeholder="yyyy-MM-ddTHH:mm" min="2013-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 ,
225
+
159
226
/**
160
227
* @ngdoc inputType
161
228
* @name ng.directive:input.number
@@ -596,7 +663,77 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
596
663
}
597
664
}
598
665
599
- function dateInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
666
+ function dateTimeLocalInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
667
+ textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
668
+
669
+ ctrl . $parsers . push ( function ( value ) {
670
+ if ( ctrl . $isEmpty ( value ) ) {
671
+ ctrl . $setValidity ( 'datetimelocal' , true ) ;
672
+ return value ;
673
+ }
674
+
675
+ if ( DATETIMELOCAL_REGEXP . test ( value ) ) {
676
+ ctrl . $setValidity ( 'datetimelocal' , true ) ;
677
+ return new Date ( getTime ( value ) ) ;
678
+ }
679
+
680
+ ctrl . $setValidity ( 'datetimelocal' , false ) ;
681
+ return undefined ;
682
+ } ) ;
683
+
684
+ ctrl . $formatters . push ( function ( value ) {
685
+ if ( isDate ( value ) ) {
686
+ return $filter ( 'date' ) ( value , 'yyyy-MM-ddTHH:mm' ) ;
687
+ }
688
+ return ctrl . $isEmpty ( value ) ? '' : '' + value ;
689
+ } ) ;
690
+
691
+ if ( attr . min ) {
692
+ var minValidator = function ( value ) {
693
+ var valid = ctrl . $isEmpty ( value ) ||
694
+ ( getTime ( value ) >= getTime ( attr . min ) ) ;
695
+ ctrl . $setValidity ( 'min' , valid ) ;
696
+ return valid ? value : undefined ;
697
+ } ;
698
+
699
+ ctrl . $parsers . push ( minValidator ) ;
700
+ ctrl . $formatters . push ( minValidator ) ;
701
+ }
702
+
703
+ if ( attr . max ) {
704
+ var maxValidator = function ( value ) {
705
+ var valid = ctrl . $isEmpty ( value ) ||
706
+ ( getTime ( value ) <= getTime ( attr . max ) ) ;
707
+ ctrl . $setValidity ( 'max' , valid ) ;
708
+ return valid ? value : undefined ;
709
+ } ;
710
+
711
+ ctrl . $parsers . push ( maxValidator ) ;
712
+ ctrl . $formatters . push ( maxValidator ) ;
713
+ }
714
+
715
+ function getTime ( iso ) {
716
+ if ( isDate ( iso ) ) {
717
+ return + iso ;
718
+ }
719
+
720
+ if ( isString ( iso ) ) {
721
+ DATETIMELOCAL_REGEXP . lastIndex = 0 ;
722
+ var parts = DATETIMELOCAL_REGEXP . exec ( iso ) ,
723
+ yyyy = + parts [ 1 ] ,
724
+ MM = + parts [ 2 ] - 1 ,
725
+ dd = + parts [ 3 ] ,
726
+ HH = + parts [ 4 ] ,
727
+ mm = + parts [ 5 ] ;
728
+
729
+ return + new Date ( yyyy , MM , dd , HH , mm ) ;
730
+ }
731
+
732
+ return NaN ;
733
+ }
734
+ }
735
+
736
+ function dateInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
600
737
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
601
738
602
739
ctrl . $parsers . push ( function ( value ) {
@@ -616,13 +753,7 @@ function dateInputType(scope, element, attr, ctrl, $sniffer, $browser) {
616
753
617
754
ctrl . $formatters . push ( function ( value ) {
618
755
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 ;
756
+ return $filter ( 'date' ) ( value , 'yyyy-MM-dd' ) ;
626
757
}
627
758
return ctrl . $isEmpty ( value ) ? '' : '' + value ;
628
759
} ) ;
@@ -912,14 +1043,14 @@ function checkboxInputType(scope, element, attr, ctrl) {
912
1043
</doc:scenario>
913
1044
</doc:example>
914
1045
*/
915
- var inputDirective = [ '$browser' , '$sniffer' , function ( $browser , $sniffer ) {
1046
+ var inputDirective = [ '$browser' , '$sniffer' , '$filter' , function ( $browser , $sniffer , $filter ) {
916
1047
return {
917
1048
restrict : 'E' ,
918
1049
require : '?ngModel' ,
919
1050
link : function ( scope , element , attr , ctrl ) {
920
1051
if ( ctrl ) {
921
1052
( inputType [ lowercase ( attr . type ) ] || inputType . text ) ( scope , element , attr , ctrl , $sniffer ,
922
- $browser ) ;
1053
+ $browser , $filter ) ;
923
1054
}
924
1055
}
925
1056
} ;
0 commit comments