From 9b8355f44176b2f71474bf4acd558cd7eb3151a7 Mon Sep 17 00:00:00 2001 From: martin Date: Fri, 15 Jan 2016 04:03:21 +0500 Subject: [PATCH] fix(form): make ngForm $pristine after nested control.$setPristine() When calling $setPristine on the nested form or control, form becomes $pristine of all the nested controls are pristine Closes #13715 --- src/ng/directive/form.js | 36 ++++++++++++++-- src/ng/directive/ngModel.js | 3 ++ test/ng/directive/formSpec.js | 74 ++++++++++++++++++++++++++++++++ test/ng/directive/ngModelSpec.js | 7 ++- 4 files changed, 115 insertions(+), 5 deletions(-) diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js index 8f856ffe33c7..c58c2292074a 100644 --- a/src/ng/directive/form.js +++ b/src/ng/directive/form.js @@ -5,6 +5,7 @@ var nullFormCtrl = { $addControl: noop, $$renameControl: nullFormRenameControl, + $$updatePristine: noop, $removeControl: noop, $setValidity: noop, $setDirty: noop, @@ -235,19 +236,46 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { * * This method can be called to remove the 'ng-dirty' class and set the form to its pristine * state (ng-pristine class). This method will also propagate to all the controls contained - * in this form. + * in this form and to the parent form. * * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after * saving or resetting it. */ form.$setPristine = function() { + form.$$setPristineSelf(); + forEach(controls, function(control) { + control.$setPristine(); + }); + }; + + // Private API: Sets the form to its pristine state. + // This method does not affect nested controls. + form.$$setPristineSelf = function() { $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); form.$dirty = false; form.$pristine = true; form.$submitted = false; - forEach(controls, function(control) { - control.$setPristine(); - }); + parentForm.$$updatePristine(); + }; + + // Private API: update form pristine-ness + form.$$updatePristine = function() { + var isPristine = true, + controlsLength = controls.length, + i; + + for (i = 0; i < controlsLength; i++) { + if (!controls[i].$pristine) { + isPristine = false; + break; + } + } + + if (isPristine) { + // All the nested controls are already pristine. + // Set pristine-ness only for the form itself. + form.$$setPristineSelf(); + } }; /** diff --git a/src/ng/directive/ngModel.js b/src/ng/directive/ngModel.js index a1d0d561b0e2..4794aee7c68c 100644 --- a/src/ng/directive/ngModel.js +++ b/src/ng/directive/ngModel.js @@ -364,12 +364,15 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * This method can be called to remove the `ng-dirty` class and set the control to its pristine * state (`ng-pristine` class). A model is considered to be pristine when the control * has not been changed from when first compiled. + * + * This method will also propagate to parent forms. */ this.$setPristine = function() { ctrl.$dirty = false; ctrl.$pristine = true; $animate.removeClass($element, DIRTY_CLASS); $animate.addClass($element, PRISTINE_CLASS); + parentForm.$$updatePristine(); }; /** diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js index 27d18f3032e0..a83e9451396c 100644 --- a/test/ng/directive/formSpec.js +++ b/test/ng/directive/formSpec.js @@ -812,6 +812,80 @@ describe('form', function() { expect(nestedInputCtrl.$pristine).toBe(true); expect(nestedInputCtrl.$dirty).toBe(false); }); + + it('should propagate pristine-ness to the parent form', function() { + doc = $compile( + '
' + + '
' + + '
')(scope); + + var parentForm = doc, + childForm = parentForm.find('div').eq(0), + childFormCtrl = scope.childForm; + + childFormCtrl.$setDirty(); + scope.$apply(); + expect(parentForm).not.toBePristine(); + + childFormCtrl.$setPristine(); + scope.$apply(); + expect(childForm).toBePristine(); + expect(parentForm).toBePristine(); + }); + + it('should be pristine if all the nested controls are pristine', function() { + doc = $compile( + '
' + + '' + + '' + + '
')(scope); + + var form = doc, + formCtrl = scope.form, + input1 = form.find('input').eq(0), + input2 = form.find('input').eq(1), + inputCtrl1 = input1.controller('ngModel'), + inputCtrl2 = input2.controller('ngModel'); + + inputCtrl1.$setDirty(); + inputCtrl2.$setDirty(); + scope.$apply(); + expect(form).not.toBePristine(); + + inputCtrl1.$setPristine(); + scope.$apply(); + expect(form).not.toBePristine(); + + inputCtrl2.$setPristine(); + scope.$apply(); + expect(form).toBePristine(); + }); + + it('should be pristine if all the nested forms are pristine', function() { + doc = $compile( + '
' + + '
' + + '
' + + '
')(scope); + + var form = doc, + formCtrl = scope.form, + childFormCtrl1 = scope.childForm1, + childFormCtrl2 = scope.childForm2; + + childFormCtrl1.$setDirty(); + childFormCtrl2.$setDirty(); + scope.$apply(); + expect(form).not.toBePristine(); + + childFormCtrl1.$setPristine(); + scope.$apply(); + expect(form).not.toBePristine(); + + childFormCtrl2.$setPristine(); + scope.$apply(); + expect(form).toBePristine(); + }); }); describe('$setUntouched', function() { diff --git a/test/ng/directive/ngModelSpec.js b/test/ng/directive/ngModelSpec.js index 7f4bfc205dfb..2b410c5ae71f 100644 --- a/test/ng/directive/ngModelSpec.js +++ b/test/ng/directive/ngModelSpec.js @@ -15,6 +15,7 @@ describe('ngModel', function() { $$setPending: jasmine.createSpy('$$setPending'), $setValidity: jasmine.createSpy('$setValidity'), $setDirty: jasmine.createSpy('$setDirty'), + $$updatePristine: jasmine.createSpy('$$updatePristine'), $$clearControlValidity: noop }; @@ -133,7 +134,6 @@ describe('ngModel', function() { }); describe('setPristine', function() { - it('should set control to its pristine state', function() { ctrl.$setViewValue('edit'); expect(ctrl.$dirty).toBe(true); @@ -143,6 +143,11 @@ describe('ngModel', function() { expect(ctrl.$dirty).toBe(false); expect(ctrl.$pristine).toBe(true); }); + + it('should set parent form to its pristine state', function() { + ctrl.$setPristine(); + expect(parentFormCtrl.$$updatePristine).toHaveBeenCalledOnce(); + }); }); describe('setDirty', function() {