@@ -18,6 +18,7 @@ var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
18
18
var TIME_REGEXP = / ^ ( \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
/**
@@ -870,13 +871,6 @@ var inputType = {
870
871
'file' : noop
871
872
} ;
872
873
873
- // A helper function to call $setValidity and return the value / undefined,
874
- // a pattern that is repeated a lot in the input validation logic.
875
- function validate ( ctrl , validatorName , validity , value ) {
876
- ctrl . $setValidity ( validatorName , validity ) ;
877
- return validity ? value : undefined ;
878
- }
879
-
880
874
function testFlags ( validity , flags ) {
881
875
var i , flag ;
882
876
if ( flags ) {
@@ -890,25 +884,6 @@ function testFlags(validity, flags) {
890
884
return false ;
891
885
}
892
886
893
- // Pass validity so that behaviour can be mocked easier.
894
- function addNativeHtml5Validators ( ctrl , validatorName , badFlags , ignoreFlags , validity ) {
895
- if ( isObject ( validity ) ) {
896
- ctrl . $$hasNativeValidators = true ;
897
- var validator = function ( value ) {
898
- // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can
899
- // perform the required validation)
900
- if ( ! ctrl . $error [ validatorName ] &&
901
- ! testFlags ( validity , ignoreFlags ) &&
902
- testFlags ( validity , badFlags ) ) {
903
- ctrl . $setValidity ( validatorName , false ) ;
904
- return ;
905
- }
906
- return value ;
907
- } ;
908
- ctrl . $parsers . push ( validator ) ;
909
- }
910
- }
911
-
912
887
function textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
913
888
var validity = element . prop ( VALIDITY_STATE_PROPERTY ) ;
914
889
var placeholder = element [ 0 ] . placeholder , noevent = { } ;
@@ -1060,20 +1035,13 @@ function createDateParser(regexp, mapping) {
1060
1035
1061
1036
function createDateInputType ( type , regexp , parseDate , format ) {
1062
1037
return function dynamicDateInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
1038
+ badInputChecker ( scope , element , attr , ctrl ) ;
1063
1039
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1064
1040
1041
+ ctrl . $$parserName = type ;
1065
1042
ctrl . $parsers . push ( function ( value ) {
1066
- if ( ctrl . $isEmpty ( value ) ) {
1067
- ctrl . $setValidity ( type , true ) ;
1068
- return null ;
1069
- }
1070
-
1071
- if ( regexp . test ( value ) ) {
1072
- ctrl . $setValidity ( type , true ) ;
1073
- return parseDate ( value ) ;
1074
- }
1075
-
1076
- ctrl . $setValidity ( type , false ) ;
1043
+ if ( ctrl . $isEmpty ( value ) ) return null ;
1044
+ if ( regexp . test ( value ) ) return parseDate ( value ) ;
1077
1045
return undefined ;
1078
1046
} ) ;
1079
1047
@@ -1085,90 +1053,80 @@ function createDateInputType(type, regexp, parseDate, format) {
1085
1053
} ) ;
1086
1054
1087
1055
if ( attr . min ) {
1088
- var minValidator = function ( value ) {
1089
- var valid = ctrl . $isEmpty ( value ) ||
1090
- ( parseDate ( value ) >= parseDate ( attr . min ) ) ;
1091
- ctrl . $setValidity ( 'min' , valid ) ;
1092
- return valid ? value : undefined ;
1093
- } ;
1094
-
1095
- ctrl . $parsers . push ( minValidator ) ;
1096
- ctrl . $formatters . push ( minValidator ) ;
1056
+ ctrl . $validators . min = function ( value ) {
1057
+ return ctrl . $isEmpty ( value ) || isUndefined ( attr . min ) || parseDate ( value ) >= parseDate ( attr . min ) ;
1058
+ } ;
1097
1059
}
1098
1060
1099
1061
if ( attr . max ) {
1100
- var maxValidator = function ( value ) {
1101
- var valid = ctrl . $isEmpty ( value ) ||
1102
- ( parseDate ( value ) <= parseDate ( attr . max ) ) ;
1103
- ctrl . $setValidity ( 'max' , valid ) ;
1104
- return valid ? value : undefined ;
1105
- } ;
1106
-
1107
- ctrl . $parsers . push ( maxValidator ) ;
1108
- ctrl . $formatters . push ( maxValidator ) ;
1062
+ ctrl . $validators . max = function ( value ) {
1063
+ return ctrl . $isEmpty ( value ) || isUndefined ( attr . max ) || parseDate ( value ) <= parseDate ( attr . max ) ;
1064
+ } ;
1109
1065
}
1110
1066
} ;
1111
1067
}
1112
1068
1113
- var numberBadFlags = [ 'badInput' ] ;
1069
+ function badInputChecker ( scope , element , attr , ctrl ) {
1070
+ var node = element [ 0 ] ;
1071
+ var nativeValidation = ctrl . $$hasNativeValidators = isObject ( node . validity ) ;
1072
+ if ( nativeValidation ) {
1073
+ ctrl . $parsers . push ( function ( value ) {
1074
+ var validity = element . prop ( VALIDITY_STATE_PROPERTY ) || { } ;
1075
+ return validity . badInput || validity . typeMismatch ? undefined : value ;
1076
+ } ) ;
1077
+ }
1078
+ }
1114
1079
1115
1080
function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1081
+ badInputChecker ( scope , element , attr , ctrl ) ;
1116
1082
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1117
1083
1084
+ ctrl . $$parserName = 'number' ;
1118
1085
ctrl . $parsers . push ( function ( value ) {
1119
- var empty = ctrl . $isEmpty ( value ) ;
1120
- if ( empty || NUMBER_REGEXP . test ( value ) ) {
1121
- ctrl . $setValidity ( 'number' , true ) ;
1122
- return value === '' ? null : ( empty ? value : parseFloat ( value ) ) ;
1123
- } else {
1124
- ctrl . $setValidity ( 'number' , false ) ;
1125
- return undefined ;
1126
- }
1086
+ if ( ctrl . $isEmpty ( value ) ) return null ;
1087
+ if ( NUMBER_REGEXP . test ( value ) ) return parseFloat ( value ) ;
1088
+ return undefined ;
1127
1089
} ) ;
1128
1090
1129
- addNativeHtml5Validators ( ctrl , 'number' , numberBadFlags , null , ctrl . $$validityState ) ;
1130
-
1131
1091
ctrl . $formatters . push ( function ( value ) {
1132
- return ctrl . $isEmpty ( value ) ? '' : '' + value ;
1092
+ if ( ! ctrl . $isEmpty ( value ) ) {
1093
+ if ( ! isNumber ( value ) ) {
1094
+ throw $ngModelMinErr ( 'numfmt' , 'Expected `{0}` to be a number' , value ) ;
1095
+ }
1096
+ value = value . toString ( ) ;
1097
+ }
1098
+ return value ;
1133
1099
} ) ;
1134
1100
1135
1101
if ( attr . min ) {
1136
- var minValidator = function ( value ) {
1137
- var min = parseFloat ( attr . min ) ;
1138
- return validate ( ctrl , 'min' , ctrl . $isEmpty ( value ) || value >= min , value ) ;
1102
+ ctrl . $validators . min = function ( value ) {
1103
+ return ctrl . $isEmpty ( value ) || isUndefined ( attr . min ) || value >= parseFloat ( attr . min ) ;
1139
1104
} ;
1140
-
1141
- ctrl . $parsers . push ( minValidator ) ;
1142
- ctrl . $formatters . push ( minValidator ) ;
1143
1105
}
1144
1106
1145
1107
if ( attr . max ) {
1146
- var maxValidator = function ( value ) {
1147
- var max = parseFloat ( attr . max ) ;
1148
- return validate ( ctrl , 'max' , ctrl . $isEmpty ( value ) || value <= max , value ) ;
1108
+ ctrl . $validators . max = function ( value ) {
1109
+ return ctrl . $isEmpty ( value ) || isUndefined ( attr . max ) || value <= parseFloat ( attr . max ) ;
1149
1110
} ;
1150
-
1151
- ctrl . $parsers . push ( maxValidator ) ;
1152
- ctrl . $formatters . push ( maxValidator ) ;
1153
1111
}
1154
-
1155
- ctrl . $formatters . push ( function ( value ) {
1156
- return validate ( ctrl , 'number' , ctrl . $isEmpty ( value ) || isNumber ( value ) , value ) ;
1157
- } ) ;
1158
1112
}
1159
1113
1160
1114
function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1115
+ badInputChecker ( scope , element , attr , ctrl ) ;
1161
1116
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1162
1117
1118
+ ctrl . $$parserName = 'url' ;
1163
1119
ctrl . $validators . url = function ( modelValue , viewValue ) {
1164
1120
var value = modelValue || viewValue ;
1165
1121
return ctrl . $isEmpty ( value ) || URL_REGEXP . test ( value ) ;
1166
1122
} ;
1167
1123
}
1168
1124
1169
1125
function emailInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1126
+ badInputChecker ( scope , element , attr , ctrl ) ;
1170
1127
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1171
1128
1129
+ ctrl . $$parserName = 'email' ;
1172
1130
ctrl . $validators . email = function ( modelValue , viewValue ) {
1173
1131
var value = modelValue || viewValue ;
1174
1132
return ctrl . $isEmpty ( value ) || EMAIL_REGEXP . test ( value ) ;
@@ -1204,7 +1162,7 @@ function parseConstantExpr($parse, context, name, expression, fallback) {
1204
1162
if ( isDefined ( expression ) ) {
1205
1163
parseFn = $parse ( expression ) ;
1206
1164
if ( ! parseFn . constant ) {
1207
- throw new minErr ( 'ngModel' ) ( 'constexpr' , 'Expected constant expression for `{0}`, but saw ' +
1165
+ throw minErr ( 'ngModel' ) ( 'constexpr' , 'Expected constant expression for `{0}`, but saw ' +
1208
1166
'`{1}`.' , name , expression ) ;
1209
1167
}
1210
1168
return parseFn ( context ) ;
@@ -1579,7 +1537,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1579
1537
ctrl = this ;
1580
1538
1581
1539
if ( ! ngModelSet ) {
1582
- throw minErr ( 'ngModel' ) ( 'nonassign' , "Expression '{0}' is non-assignable. Element: {1}" ,
1540
+ throw $ngModelMinErr ( 'nonassign' , "Expression '{0}' is non-assignable. Element: {1}" ,
1583
1541
$attr . ngModel , startingTag ( $element ) ) ;
1584
1542
}
1585
1543
@@ -1644,6 +1602,19 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1644
1602
$animate . addClass ( $element , ( isValid ? VALID_CLASS : INVALID_CLASS ) + validationErrorKey ) ;
1645
1603
}
1646
1604
1605
+ this . $$clearValidity = function ( ) {
1606
+ forEach ( ctrl . $error , function ( val , key ) {
1607
+ var validationKey = snake_case ( key , '-' ) ;
1608
+ $animate . removeClass ( $element , VALID_CLASS + validationKey ) ;
1609
+ $animate . removeClass ( $element , INVALID_CLASS + validationKey ) ;
1610
+ } ) ;
1611
+
1612
+ invalidCount = 0 ;
1613
+ $error = ctrl . $error = { } ;
1614
+
1615
+ parentForm . $$clearControlValidity ( ctrl ) ;
1616
+ } ;
1617
+
1647
1618
/**
1648
1619
* @ngdoc method
1649
1620
* @name ngModel.NgModelController#$setValidity
@@ -1675,7 +1646,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1675
1646
ctrl . $valid = true ;
1676
1647
ctrl . $invalid = false ;
1677
1648
}
1678
- } else {
1649
+ } else if ( ! $error [ validationErrorKey ] ) {
1679
1650
toggleValidCss ( false ) ;
1680
1651
ctrl . $invalid = true ;
1681
1652
ctrl . $valid = false ;
@@ -1864,16 +1835,27 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1864
1835
parentForm . $setDirty ( ) ;
1865
1836
}
1866
1837
1867
- var modelValue = viewValue ;
1868
- forEach ( ctrl . $parsers , function ( fn ) {
1869
- modelValue = fn ( modelValue ) ;
1870
- } ) ;
1838
+ var hasBadInput , modelValue = viewValue ;
1839
+ for ( var i = 0 ; i < ctrl . $parsers . length ; i ++ ) {
1840
+ modelValue = ctrl . $parsers [ i ] ( modelValue ) ;
1841
+ if ( isUndefined ( modelValue ) ) {
1842
+ hasBadInput = true ;
1843
+ break ;
1844
+ }
1845
+ }
1871
1846
1872
- if ( ctrl . $modelValue !== modelValue &&
1873
- ( isUndefined ( ctrl . $$invalidModelValue ) || ctrl . $$invalidModelValue != modelValue ) ) {
1847
+ var parserName = ctrl . $$parserName || 'parse' ;
1848
+ if ( hasBadInput ) {
1849
+ ctrl . $$invalidModelValue = ctrl . $modelValue = undefined ;
1850
+ ctrl . $$clearValidity ( ) ;
1851
+ ctrl . $setValidity ( parserName , false ) ;
1852
+ } else if ( ctrl . $modelValue !== modelValue &&
1853
+ ( isUndefined ( ctrl . $$invalidModelValue ) || ctrl . $$invalidModelValue != modelValue ) ) {
1854
+ ctrl . $setValidity ( parserName , true ) ;
1874
1855
ctrl . $$runValidators ( modelValue , viewValue ) ;
1875
- ctrl . $$writeModelToScope ( ) ;
1876
1856
}
1857
+
1858
+ ctrl . $$writeModelToScope ( ) ;
1877
1859
} ;
1878
1860
1879
1861
this . $$writeModelToScope = function ( ) {
0 commit comments