@@ -18,6 +18,7 @@ var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
1818var TIME_REGEXP = / ^ ( \d \d ) : ( \d \d ) (?: : ( \d \d ) ) ? $ / ;
1919var DEFAULT_REGEXP = / ( \s + | ^ ) d e f a u l t ( \s + | $ ) / ;
2020
21+ var $ngModelMinErr = new minErr ( 'ngModel' ) ;
2122var inputType = {
2223
2324 /**
@@ -885,13 +886,6 @@ var inputType = {
885886 'file' : noop
886887} ;
887888
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-
895889function testFlags ( validity , flags ) {
896890 var i , flag ;
897891 if ( flags ) {
@@ -905,25 +899,6 @@ function testFlags(validity, flags) {
905899 return false ;
906900}
907901
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-
927902function textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
928903 var validity = element . prop ( VALIDITY_STATE_PROPERTY ) ;
929904 var placeholder = element [ 0 ] . placeholder , noevent = { } ;
@@ -1074,25 +1049,20 @@ function createDateParser(regexp, mapping) {
10741049
10751050function createDateInputType ( type , regexp , parseDate , format ) {
10761051 return function dynamicDateInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
1052+ badInputChecker ( scope , element , attr , ctrl ) ;
10771053 textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
10781054 var timezone = ctrl && ctrl . $options && ctrl . $options . timezone ;
10791055
1056+ ctrl . $$parserName = type ;
10801057 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 ) ) {
10881060 var parsedDate = parseDate ( value ) ;
10891061 if ( timezone === 'UTC' ) {
10901062 parsedDate . setMinutes ( parsedDate . getMinutes ( ) - parsedDate . getTimezoneOffset ( ) ) ;
10911063 }
10921064 return parsedDate ;
10931065 }
1094-
1095- ctrl . $setValidity ( type , false ) ;
10961066 return undefined ;
10971067 } ) ;
10981068
@@ -1104,90 +1074,80 @@ function createDateInputType(type, regexp, parseDate, format) {
11041074 } ) ;
11051075
11061076 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+ } ;
11161080 }
11171081
11181082 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+ } ;
11281086 }
11291087 } ;
11301088}
11311089
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+ }
11331100
11341101function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1102+ badInputChecker ( scope , element , attr , ctrl ) ;
11351103 textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
11361104
1105+ ctrl . $$parserName = 'number' ;
11371106 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 ;
11461110 } ) ;
11471111
1148- addNativeHtml5Validators ( ctrl , 'number' , numberBadFlags , null , ctrl . $$validityState ) ;
1149-
11501112 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 ;
11521120 } ) ;
11531121
11541122 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 ) ;
11581125 } ;
1159-
1160- ctrl . $parsers . push ( minValidator ) ;
1161- ctrl . $formatters . push ( minValidator ) ;
11621126 }
11631127
11641128 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 ) ;
11681131 } ;
1169-
1170- ctrl . $parsers . push ( maxValidator ) ;
1171- ctrl . $formatters . push ( maxValidator ) ;
11721132 }
1173-
1174- ctrl . $formatters . push ( function ( value ) {
1175- return validate ( ctrl , 'number' , ctrl . $isEmpty ( value ) || isNumber ( value ) , value ) ;
1176- } ) ;
11771133}
11781134
11791135function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1136+ badInputChecker ( scope , element , attr , ctrl ) ;
11801137 textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
11811138
1139+ ctrl . $$parserName = 'url' ;
11821140 ctrl . $validators . url = function ( modelValue , viewValue ) {
11831141 var value = modelValue || viewValue ;
11841142 return ctrl . $isEmpty ( value ) || URL_REGEXP . test ( value ) ;
11851143 } ;
11861144}
11871145
11881146function emailInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1147+ badInputChecker ( scope , element , attr , ctrl ) ;
11891148 textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
11901149
1150+ ctrl . $$parserName = 'email' ;
11911151 ctrl . $validators . email = function ( modelValue , viewValue ) {
11921152 var value = modelValue || viewValue ;
11931153 return ctrl . $isEmpty ( value ) || EMAIL_REGEXP . test ( value ) ;
@@ -1223,7 +1183,7 @@ function parseConstantExpr($parse, context, name, expression, fallback) {
12231183 if ( isDefined ( expression ) ) {
12241184 parseFn = $parse ( expression ) ;
12251185 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 ' +
12271187 '`{1}`.' , name , expression ) ;
12281188 }
12291189 return parseFn ( context ) ;
@@ -1598,7 +1558,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
15981558 ctrl = this ;
15991559
16001560 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}" ,
16021562 $attr . ngModel , startingTag ( $element ) ) ;
16031563 }
16041564
@@ -1663,6 +1623,19 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
16631623 $animate . addClass ( $element , ( isValid ? VALID_CLASS : INVALID_CLASS ) + validationErrorKey ) ;
16641624 }
16651625
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+
16661639 /**
16671640 * @ngdoc method
16681641 * @name ngModel.NgModelController#$setValidity
@@ -1694,7 +1667,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
16941667 ctrl . $valid = true ;
16951668 ctrl . $invalid = false ;
16961669 }
1697- } else {
1670+ } else if ( ! $error [ validationErrorKey ] ) {
16981671 toggleValidCss ( false ) ;
16991672 ctrl . $invalid = true ;
17001673 ctrl . $valid = false ;
@@ -1883,16 +1856,27 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
18831856 parentForm . $setDirty ( ) ;
18841857 }
18851858
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+ }
18901867
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 ) ;
18931876 ctrl . $$runValidators ( modelValue , viewValue ) ;
1894- ctrl . $$writeModelToScope ( ) ;
18951877 }
1878+
1879+ ctrl . $$writeModelToScope ( ) ;
18961880 } ;
18971881
18981882 this . $$writeModelToScope = function ( ) {
0 commit comments