From f019b5c57a98a03afffb1b051c4cac15ae3a86c9 Mon Sep 17 00:00:00 2001 From: Shahar Talmi Date: Wed, 25 Jun 2014 01:39:20 +0300 Subject: [PATCH] fix(input): improve html5 validation for ngModelOptions This is a minor adjustment over the recent html5 validation fix in order to make it work correctly with ngModelOptions. The gist is that `$commitViewValue` now runs the validation pipeline even if the view value remains empty in order to refresh the validity state. --- src/ng/directive/input.js | 38 +++++++++++++++++----------------- test/ng/directive/inputSpec.js | 28 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index db59a1c2e3e1..06b13dc57b8b 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -939,13 +939,12 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { // If a control is suffering from bad input, browsers discard its value, so it may be // necessary to revalidate even if the control's value is the same empty value twice in // a row. - var revalidate = validity && ctrl.$$hasNativeValidators; - if (ctrl.$viewValue !== value || (value === '' && revalidate)) { + if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { if (scope.$$phase) { - ctrl.$setViewValue(value, event, revalidate); + ctrl.$setViewValue(value, event); } else { scope.$apply(function() { - ctrl.$setViewValue(value, event, revalidate); + ctrl.$setViewValue(value, event); }); } } @@ -1812,23 +1811,24 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` * usually handles calling this in response to input events. */ - this.$commitViewValue = function(revalidate) { + this.$commitViewValue = function() { var viewValue = ctrl.$viewValue; $timeout.cancel(pendingDebounce); - if (!revalidate && ctrl.$$lastCommittedViewValue === viewValue) { + if (ctrl.$$lastCommittedViewValue !== viewValue) { + // change to dirty + if (ctrl.$pristine) { + ctrl.$dirty = true; + ctrl.$pristine = false; + $animate.removeClass($element, PRISTINE_CLASS); + $animate.addClass($element, DIRTY_CLASS); + parentForm.$setDirty(); + } + } else if (viewValue !== '' || !ctrl.$$hasNativeValidators) { return; } ctrl.$$lastCommittedViewValue = viewValue; - // change to dirty - if (ctrl.$pristine) { - ctrl.$dirty = true; - ctrl.$pristine = false; - $animate.removeClass($element, PRISTINE_CLASS); - $animate.addClass($element, DIRTY_CLASS); - parentForm.$setDirty(); - } var modelValue = viewValue; forEach(ctrl.$parsers, function(fn) { @@ -1881,14 +1881,14 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * @param {string} value Value from the view. * @param {string} trigger Event that triggered the update. */ - this.$setViewValue = function(value, trigger, revalidate) { + this.$setViewValue = function(value, trigger) { ctrl.$viewValue = value; if (!ctrl.$options || ctrl.$options.updateOnDefault) { - ctrl.$$debounceViewValueCommit(trigger, revalidate); + ctrl.$$debounceViewValueCommit(trigger); } }; - this.$$debounceViewValueCommit = function(trigger, revalidate) { + this.$$debounceViewValueCommit = function(trigger) { var debounceDelay = 0, options = ctrl.$options, debounce; @@ -1907,10 +1907,10 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ $timeout.cancel(pendingDebounce); if (debounceDelay) { pendingDebounce = $timeout(function() { - ctrl.$commitViewValue(revalidate); + ctrl.$commitViewValue(); }, debounceDelay); } else { - ctrl.$commitViewValue(revalidate); + ctrl.$commitViewValue(); } }; diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 9c9eca38c709..7b71f4b1d6c2 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -2228,6 +2228,18 @@ describe('input', function() { expect(inputElm).toBeInvalid(); }); + it('should invalidate number with ngModelOptions if suffering from bad input', function() { + compileInput('', { + valid: false, + badInput: true + }); + + changeInputValueTo('10a'); + browserTrigger(inputElm, 'blur'); + expect(scope.age).toBeUndefined(); + expect(inputElm).toBeInvalid(); + }); it('should validate number if transition from bad input to empty string', function() { var validity = { @@ -2243,6 +2255,22 @@ describe('input', function() { expect(inputElm).toBeValid(); }); + it('should validate when bad input number with ngModelOptions changes to empty', function() { + var validity = { + valid: false, + badInput: true + }; + compileInput('', validity); + changeInputValueTo('10a'); + browserTrigger(inputElm, 'blur'); + validity.badInput = false; + validity.valid = true; + changeInputValueTo(''); + browserTrigger(inputElm, 'blur'); + expect(scope.age).toBeNull(); + expect(inputElm).toBeValid(); + }); describe('min', function() {