diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js index 06ffad868d0c..eb8bdf8f0156 100644 --- a/src/ng/directive/form.js +++ b/src/ng/directive/form.js @@ -6,7 +6,8 @@ var nullFormCtrl = { $removeControl: noop, $setValidity: noop, $setDirty: noop, - $setPristine: noop + $setPristine: noop, + $updateControlsDirtyAfterState: noop }; /** @@ -180,6 +181,7 @@ function FormController(element, attrs) { element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); form.$dirty = true; form.$pristine = false; + form.$updateControlsDirtyAfterState(); parentForm.$setDirty(); }; @@ -205,9 +207,28 @@ function FormController(element, attrs) { forEach(controls, function(control) { control.$setPristine(); }); + form.$updateControlsDirtyAfterState(); }; -} + /** + * @ngdoc function + * @name ng.directive:form.FormController#$updateControlsDirtyAfterState + * @methodOf ng.directive:form.FormController + * + * @description + * Update the form controls dirtyAfter state. + * + * This method is called when an input dirty state changes + * or when the form is set to its pristine state. + */ + form.$updateControlsDirtyAfterState = function() { + var dirtyAfter = false; + for (var i = controls.length - 1; i >= 0; i--) { + controls[i].$dirtyAfter = dirtyAfter; + dirtyAfter = dirtyAfter || controls[i].$dirty; + } + }; +} /** * @ngdoc directive diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 706844ebc37d..f6f4e21d913c 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -831,6 +831,7 @@ var VALID_CLASS = 'ng-valid', * * @property {boolean} $pristine True if user has not interacted with the control yet. * @property {boolean} $dirty True if user has already interacted with the control. + * @property {boolean} $dirtyAfter True if user has already interacted with any control positioned after this control. * @property {boolean} $valid True if there is no error. * @property {boolean} $invalid True if at least one error on the control. * @@ -931,6 +932,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ this.$viewChangeListeners = []; this.$pristine = true; this.$dirty = false; + this.$dirtyAfter = false; this.$valid = true; this.$invalid = false; this.$name = $attr.name; diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js index dde6f0a026c8..bac035e9a18e 100644 --- a/test/ng/directive/formSpec.js +++ b/test/ng/directive/formSpec.js @@ -593,4 +593,51 @@ describe('form', function() { expect(nestedInputCtrl.$dirty).toBe(false); }); }); + + describe('$updateControlsDirtyAfterState', function() { + + it('should update controls dirty state when a control becomes dirty or when the form is set pristine', function() { + + doc = $compile( + '
')(scope); + + scope.$digest(); + + var form = doc, + formCtrl = scope.testForm, + input1 = form.find('input').eq(0), + input1Ctrl = input1.controller('ngModel'), + input2 = form.find('input').eq(1), + input2Ctrl = input2.controller('ngModel'), + input3 = form.find('input').eq(2), + input3Ctrl = input3.controller('ngModel'); + + input1Ctrl.$setViewValue('first'); + scope.$apply(); + expect(input1Ctrl.$dirtyAfter).toBe(false); + expect(input2Ctrl.$dirtyAfter).toBe(false); + expect(input3Ctrl.$dirtyAfter).toBe(false); + + input2Ctrl.$setViewValue('second'); + scope.$apply(); + expect(input1Ctrl.$dirtyAfter).toBe(true); + expect(input2Ctrl.$dirtyAfter).toBe(false); + expect(input3Ctrl.$dirtyAfter).toBe(false); + + input3Ctrl.$setViewValue('third'); + scope.$apply(); + expect(input1Ctrl.$dirtyAfter).toBe(true); + expect(input2Ctrl.$dirtyAfter).toBe(true); + expect(input3Ctrl.$dirtyAfter).toBe(false); + + formCtrl.$setPristine(); + expect(input1Ctrl.$dirtyAfter).toBe(false); + expect(input2Ctrl.$dirtyAfter).toBe(false); + expect(input3Ctrl.$dirtyAfter).toBe(false); + }); + }); });