@@ -18,6 +18,7 @@ var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
18
18
var TIME_REGEXP = / ^ ( \d \d ) : ( \d \d ) (?: : ( \d \d ) ) ? $ / ;
19
19
var DEFAULT_REGEXP = / ( \s + | ^ ) d e f a u l t ( \s + | $ ) / ;
20
20
21
+ var $ngModelMinErr = new minErr ( 'ngModel' ) ;
21
22
var inputType = {
22
23
23
24
/**
@@ -885,13 +886,6 @@ var inputType = {
885
886
'file' : noop
886
887
} ;
887
888
888
- // A helper function to call $setValidity and return the value / undefined,
889
- // a pattern that is repeated a lot in the input validation logic.
890
- function validate ( ctrl , validatorName , validity , value ) {
891
- ctrl . $setValidity ( validatorName , validity ) ;
892
- return validity ? value : undefined ;
893
- }
894
-
895
889
function testFlags ( validity , flags ) {
896
890
var i , flag ;
897
891
if ( flags ) {
@@ -905,25 +899,6 @@ function testFlags(validity, flags) {
905
899
return false ;
906
900
}
907
901
908
- // Pass validity so that behaviour can be mocked easier.
909
- function addNativeHtml5Validators ( ctrl , validatorName , badFlags , ignoreFlags , validity ) {
910
- if ( isObject ( validity ) ) {
911
- ctrl . $$hasNativeValidators = true ;
912
- var validator = function ( value ) {
913
- // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can
914
- // perform the required validation)
915
- if ( ! ctrl . $error [ validatorName ] &&
916
- ! testFlags ( validity , ignoreFlags ) &&
917
- testFlags ( validity , badFlags ) ) {
918
- ctrl . $setValidity ( validatorName , false ) ;
919
- return ;
920
- }
921
- return value ;
922
- } ;
923
- ctrl . $parsers . push ( validator ) ;
924
- }
925
- }
926
-
927
902
function textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
928
903
var validity = element . prop ( VALIDITY_STATE_PROPERTY ) ;
929
904
var placeholder = element [ 0 ] . placeholder , noevent = { } ;
@@ -1074,25 +1049,20 @@ function createDateParser(regexp, mapping) {
1074
1049
1075
1050
function createDateInputType ( type , regexp , parseDate , format ) {
1076
1051
return function dynamicDateInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
1052
+ badInputChecker ( scope , element , attr , ctrl ) ;
1077
1053
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1078
1054
var timezone = ctrl && ctrl . $options && ctrl . $options . timezone ;
1079
1055
1056
+ ctrl . $$parserName = type ;
1080
1057
ctrl . $parsers . push ( function ( value ) {
1081
- if ( ctrl . $isEmpty ( value ) ) {
1082
- ctrl . $setValidity ( type , true ) ;
1083
- return null ;
1084
- }
1085
-
1086
- if ( regexp . test ( value ) ) {
1087
- ctrl . $setValidity ( type , true ) ;
1058
+ if ( ctrl . $isEmpty ( value ) ) return null ;
1059
+ if ( regexp . test ( value ) ) {
1088
1060
var parsedDate = parseDate ( value ) ;
1089
1061
if ( timezone === 'UTC' ) {
1090
1062
parsedDate . setMinutes ( parsedDate . getMinutes ( ) - parsedDate . getTimezoneOffset ( ) ) ;
1091
1063
}
1092
1064
return parsedDate ;
1093
1065
}
1094
-
1095
- ctrl . $setValidity ( type , false ) ;
1096
1066
return undefined ;
1097
1067
} ) ;
1098
1068
@@ -1104,90 +1074,80 @@ function createDateInputType(type, regexp, parseDate, format) {
1104
1074
} ) ;
1105
1075
1106
1076
if ( attr . min ) {
1107
- var minValidator = function ( value ) {
1108
- var valid = ctrl . $isEmpty ( value ) ||
1109
- ( parseDate ( value ) >= parseDate ( attr . min ) ) ;
1110
- ctrl . $setValidity ( 'min' , valid ) ;
1111
- return valid ? value : undefined ;
1112
- } ;
1113
-
1114
- ctrl . $parsers . push ( minValidator ) ;
1115
- ctrl . $formatters . push ( minValidator ) ;
1077
+ ctrl . $validators . min = function ( value ) {
1078
+ return ctrl . $isEmpty ( value ) || isUndefined ( attr . min ) || parseDate ( value ) >= parseDate ( attr . min ) ;
1079
+ } ;
1116
1080
}
1117
1081
1118
1082
if ( attr . max ) {
1119
- var maxValidator = function ( value ) {
1120
- var valid = ctrl . $isEmpty ( value ) ||
1121
- ( parseDate ( value ) <= parseDate ( attr . max ) ) ;
1122
- ctrl . $setValidity ( 'max' , valid ) ;
1123
- return valid ? value : undefined ;
1124
- } ;
1125
-
1126
- ctrl . $parsers . push ( maxValidator ) ;
1127
- ctrl . $formatters . push ( maxValidator ) ;
1083
+ ctrl . $validators . max = function ( value ) {
1084
+ return ctrl . $isEmpty ( value ) || isUndefined ( attr . max ) || parseDate ( value ) <= parseDate ( attr . max ) ;
1085
+ } ;
1128
1086
}
1129
1087
} ;
1130
1088
}
1131
1089
1132
- var numberBadFlags = [ 'badInput' ] ;
1090
+ function badInputChecker ( scope , element , attr , ctrl ) {
1091
+ var node = element [ 0 ] ;
1092
+ var nativeValidation = ctrl . $$hasNativeValidators = isObject ( node . validity ) ;
1093
+ if ( nativeValidation ) {
1094
+ ctrl . $parsers . push ( function ( value ) {
1095
+ var validity = element . prop ( VALIDITY_STATE_PROPERTY ) || { } ;
1096
+ return validity . badInput || validity . typeMismatch ? undefined : value ;
1097
+ } ) ;
1098
+ }
1099
+ }
1133
1100
1134
1101
function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1102
+ badInputChecker ( scope , element , attr , ctrl ) ;
1135
1103
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1136
1104
1105
+ ctrl . $$parserName = 'number' ;
1137
1106
ctrl . $parsers . push ( function ( value ) {
1138
- var empty = ctrl . $isEmpty ( value ) ;
1139
- if ( empty || NUMBER_REGEXP . test ( value ) ) {
1140
- ctrl . $setValidity ( 'number' , true ) ;
1141
- return value === '' ? null : ( empty ? value : parseFloat ( value ) ) ;
1142
- } else {
1143
- ctrl . $setValidity ( 'number' , false ) ;
1144
- return undefined ;
1145
- }
1107
+ if ( ctrl . $isEmpty ( value ) ) return null ;
1108
+ if ( NUMBER_REGEXP . test ( value ) ) return parseFloat ( value ) ;
1109
+ return undefined ;
1146
1110
} ) ;
1147
1111
1148
- addNativeHtml5Validators ( ctrl , 'number' , numberBadFlags , null , ctrl . $$validityState ) ;
1149
-
1150
1112
ctrl . $formatters . push ( function ( value ) {
1151
- return ctrl . $isEmpty ( value ) ? '' : '' + value ;
1113
+ if ( ! ctrl . $isEmpty ( value ) ) {
1114
+ if ( ! isNumber ( value ) ) {
1115
+ throw $ngModelMinErr ( 'numfmt' , 'Expected `{0}` to be a number' , value ) ;
1116
+ }
1117
+ value = value . toString ( ) ;
1118
+ }
1119
+ return value ;
1152
1120
} ) ;
1153
1121
1154
1122
if ( attr . min ) {
1155
- var minValidator = function ( value ) {
1156
- var min = parseFloat ( attr . min ) ;
1157
- return validate ( ctrl , 'min' , ctrl . $isEmpty ( value ) || value >= min , value ) ;
1123
+ ctrl . $validators . min = function ( value ) {
1124
+ return ctrl . $isEmpty ( value ) || isUndefined ( attr . min ) || value >= parseFloat ( attr . min ) ;
1158
1125
} ;
1159
-
1160
- ctrl . $parsers . push ( minValidator ) ;
1161
- ctrl . $formatters . push ( minValidator ) ;
1162
1126
}
1163
1127
1164
1128
if ( attr . max ) {
1165
- var maxValidator = function ( value ) {
1166
- var max = parseFloat ( attr . max ) ;
1167
- return validate ( ctrl , 'max' , ctrl . $isEmpty ( value ) || value <= max , value ) ;
1129
+ ctrl . $validators . max = function ( value ) {
1130
+ return ctrl . $isEmpty ( value ) || isUndefined ( attr . max ) || value <= parseFloat ( attr . max ) ;
1168
1131
} ;
1169
-
1170
- ctrl . $parsers . push ( maxValidator ) ;
1171
- ctrl . $formatters . push ( maxValidator ) ;
1172
1132
}
1173
-
1174
- ctrl . $formatters . push ( function ( value ) {
1175
- return validate ( ctrl , 'number' , ctrl . $isEmpty ( value ) || isNumber ( value ) , value ) ;
1176
- } ) ;
1177
1133
}
1178
1134
1179
1135
function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1136
+ badInputChecker ( scope , element , attr , ctrl ) ;
1180
1137
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1181
1138
1139
+ ctrl . $$parserName = 'url' ;
1182
1140
ctrl . $validators . url = function ( modelValue , viewValue ) {
1183
1141
var value = modelValue || viewValue ;
1184
1142
return ctrl . $isEmpty ( value ) || URL_REGEXP . test ( value ) ;
1185
1143
} ;
1186
1144
}
1187
1145
1188
1146
function emailInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1147
+ badInputChecker ( scope , element , attr , ctrl ) ;
1189
1148
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1190
1149
1150
+ ctrl . $$parserName = 'email' ;
1191
1151
ctrl . $validators . email = function ( modelValue , viewValue ) {
1192
1152
var value = modelValue || viewValue ;
1193
1153
return ctrl . $isEmpty ( value ) || EMAIL_REGEXP . test ( value ) ;
@@ -1223,7 +1183,7 @@ function parseConstantExpr($parse, context, name, expression, fallback) {
1223
1183
if ( isDefined ( expression ) ) {
1224
1184
parseFn = $parse ( expression ) ;
1225
1185
if ( ! parseFn . constant ) {
1226
- throw new minErr ( 'ngModel' ) ( 'constexpr' , 'Expected constant expression for `{0}`, but saw ' +
1186
+ throw minErr ( 'ngModel' ) ( 'constexpr' , 'Expected constant expression for `{0}`, but saw ' +
1227
1187
'`{1}`.' , name , expression ) ;
1228
1188
}
1229
1189
return parseFn ( context ) ;
@@ -1598,7 +1558,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1598
1558
ctrl = this ;
1599
1559
1600
1560
if ( ! ngModelSet ) {
1601
- throw minErr ( 'ngModel' ) ( 'nonassign' , "Expression '{0}' is non-assignable. Element: {1}" ,
1561
+ throw $ngModelMinErr ( 'nonassign' , "Expression '{0}' is non-assignable. Element: {1}" ,
1602
1562
$attr . ngModel , startingTag ( $element ) ) ;
1603
1563
}
1604
1564
@@ -1663,6 +1623,19 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1663
1623
$animate . addClass ( $element , ( isValid ? VALID_CLASS : INVALID_CLASS ) + validationErrorKey ) ;
1664
1624
}
1665
1625
1626
+ this . $$clearValidity = function ( ) {
1627
+ forEach ( ctrl . $error , function ( val , key ) {
1628
+ var validationKey = snake_case ( key , '-' ) ;
1629
+ $animate . removeClass ( $element , VALID_CLASS + validationKey ) ;
1630
+ $animate . removeClass ( $element , INVALID_CLASS + validationKey ) ;
1631
+ } ) ;
1632
+
1633
+ invalidCount = 0 ;
1634
+ $error = ctrl . $error = { } ;
1635
+
1636
+ parentForm . $$clearControlValidity ( ctrl ) ;
1637
+ } ;
1638
+
1666
1639
/**
1667
1640
* @ngdoc method
1668
1641
* @name ngModel.NgModelController#$setValidity
@@ -1694,7 +1667,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1694
1667
ctrl . $valid = true ;
1695
1668
ctrl . $invalid = false ;
1696
1669
}
1697
- } else {
1670
+ } else if ( ! $error [ validationErrorKey ] ) {
1698
1671
toggleValidCss ( false ) ;
1699
1672
ctrl . $invalid = true ;
1700
1673
ctrl . $valid = false ;
@@ -1883,16 +1856,27 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1883
1856
parentForm . $setDirty ( ) ;
1884
1857
}
1885
1858
1886
- var modelValue = viewValue ;
1887
- forEach ( ctrl . $parsers , function ( fn ) {
1888
- modelValue = fn ( modelValue ) ;
1889
- } ) ;
1859
+ var hasBadInput , modelValue = viewValue ;
1860
+ for ( var i = 0 ; i < ctrl . $parsers . length ; i ++ ) {
1861
+ modelValue = ctrl . $parsers [ i ] ( modelValue ) ;
1862
+ if ( isUndefined ( modelValue ) ) {
1863
+ hasBadInput = true ;
1864
+ break ;
1865
+ }
1866
+ }
1890
1867
1891
- if ( ctrl . $modelValue !== modelValue &&
1892
- ( isUndefined ( ctrl . $$invalidModelValue ) || ctrl . $$invalidModelValue != modelValue ) ) {
1868
+ var parserName = ctrl . $$parserName || 'parse' ;
1869
+ if ( hasBadInput ) {
1870
+ ctrl . $$invalidModelValue = ctrl . $modelValue = undefined ;
1871
+ ctrl . $$clearValidity ( ) ;
1872
+ ctrl . $setValidity ( parserName , false ) ;
1873
+ } else if ( ctrl . $modelValue !== modelValue &&
1874
+ ( isUndefined ( ctrl . $$invalidModelValue ) || ctrl . $$invalidModelValue != modelValue ) ) {
1875
+ ctrl . $setValidity ( parserName , true ) ;
1893
1876
ctrl . $$runValidators ( modelValue , viewValue ) ;
1894
- ctrl . $$writeModelToScope ( ) ;
1895
1877
}
1878
+
1879
+ ctrl . $$writeModelToScope ( ) ;
1896
1880
} ;
1897
1881
1898
1882
this . $$writeModelToScope = function ( ) {
0 commit comments