@@ -18,6 +18,7 @@ var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
1818var TIME_REGEXP = / ^ ( \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 /**
@@ -870,13 +871,6 @@ var inputType = {
870871 'file' : noop
871872} ;
872873
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-
880874function testFlags ( validity , flags ) {
881875 var i , flag ;
882876 if ( flags ) {
@@ -890,25 +884,6 @@ function testFlags(validity, flags) {
890884 return false ;
891885}
892886
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-
912887function textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
913888 var validity = element . prop ( VALIDITY_STATE_PROPERTY ) ;
914889 var placeholder = element [ 0 ] . placeholder , noevent = { } ;
@@ -1060,20 +1035,13 @@ function createDateParser(regexp, mapping) {
10601035
10611036function createDateInputType ( type , regexp , parseDate , format ) {
10621037 return function dynamicDateInputType ( scope , element , attr , ctrl , $sniffer , $browser , $filter ) {
1038+ badInputChecker ( scope , element , attr , ctrl ) ;
10631039 textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
10641040
1041+ ctrl . $$parserName = type ;
10651042 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 ) ;
10771045 return undefined ;
10781046 } ) ;
10791047
@@ -1085,90 +1053,80 @@ function createDateInputType(type, regexp, parseDate, format) {
10851053 } ) ;
10861054
10871055 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+ } ;
10971059 }
10981060
10991061 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+ } ;
11091065 }
11101066 } ;
11111067}
11121068
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+ }
11141079
11151080function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1081+ badInputChecker ( scope , element , attr , ctrl ) ;
11161082 textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
11171083
1084+ ctrl . $$parserName = 'number' ;
11181085 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 ;
11271089 } ) ;
11281090
1129- addNativeHtml5Validators ( ctrl , 'number' , numberBadFlags , null , ctrl . $$validityState ) ;
1130-
11311091 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 ;
11331099 } ) ;
11341100
11351101 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 ) ;
11391104 } ;
1140-
1141- ctrl . $parsers . push ( minValidator ) ;
1142- ctrl . $formatters . push ( minValidator ) ;
11431105 }
11441106
11451107 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 ) ;
11491110 } ;
1150-
1151- ctrl . $parsers . push ( maxValidator ) ;
1152- ctrl . $formatters . push ( maxValidator ) ;
11531111 }
1154-
1155- ctrl . $formatters . push ( function ( value ) {
1156- return validate ( ctrl , 'number' , ctrl . $isEmpty ( value ) || isNumber ( value ) , value ) ;
1157- } ) ;
11581112}
11591113
11601114function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1115+ badInputChecker ( scope , element , attr , ctrl ) ;
11611116 textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
11621117
1118+ ctrl . $$parserName = 'url' ;
11631119 ctrl . $validators . url = function ( modelValue , viewValue ) {
11641120 var value = modelValue || viewValue ;
11651121 return ctrl . $isEmpty ( value ) || URL_REGEXP . test ( value ) ;
11661122 } ;
11671123}
11681124
11691125function emailInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1126+ badInputChecker ( scope , element , attr , ctrl ) ;
11701127 textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
11711128
1129+ ctrl . $$parserName = 'email' ;
11721130 ctrl . $validators . email = function ( modelValue , viewValue ) {
11731131 var value = modelValue || viewValue ;
11741132 return ctrl . $isEmpty ( value ) || EMAIL_REGEXP . test ( value ) ;
@@ -1204,7 +1162,7 @@ function parseConstantExpr($parse, context, name, expression, fallback) {
12041162 if ( isDefined ( expression ) ) {
12051163 parseFn = $parse ( expression ) ;
12061164 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 ' +
12081166 '`{1}`.' , name , expression ) ;
12091167 }
12101168 return parseFn ( context ) ;
@@ -1579,7 +1537,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
15791537 ctrl = this ;
15801538
15811539 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}" ,
15831541 $attr . ngModel , startingTag ( $element ) ) ;
15841542 }
15851543
@@ -1644,6 +1602,19 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
16441602 $animate . addClass ( $element , ( isValid ? VALID_CLASS : INVALID_CLASS ) + validationErrorKey ) ;
16451603 }
16461604
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+
16471618 /**
16481619 * @ngdoc method
16491620 * @name ngModel.NgModelController#$setValidity
@@ -1675,7 +1646,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
16751646 ctrl . $valid = true ;
16761647 ctrl . $invalid = false ;
16771648 }
1678- } else {
1649+ } else if ( ! $error [ validationErrorKey ] ) {
16791650 toggleValidCss ( false ) ;
16801651 ctrl . $invalid = true ;
16811652 ctrl . $valid = false ;
@@ -1864,16 +1835,27 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
18641835 parentForm . $setDirty ( ) ;
18651836 }
18661837
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+ }
18711846
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 ) ;
18741855 ctrl . $$runValidators ( modelValue , viewValue ) ;
1875- ctrl . $$writeModelToScope ( ) ;
18761856 }
1857+
1858+ ctrl . $$writeModelToScope ( ) ;
18771859 } ;
18781860
18791861 this . $$writeModelToScope = function ( ) {
0 commit comments