diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js index 4f06f0936c38..b4d500c6e76c 100644 --- a/src/ng/directive/form.js +++ b/src/ng/directive/form.js @@ -5,7 +5,8 @@ var nullFormCtrl = { $addControl: noop, $removeControl: noop, $setValidity: noop, - $setDirty: noop + $setDirty: noop, + $setPristine: noop }; /** @@ -37,7 +38,8 @@ function FormController(element, attrs) { var form = this, parentForm = element.parent().controller('form') || nullFormCtrl, invalidCount = 0, // used to easily determine if we are valid - errors = form.$error = {}; + errors = form.$error = {}, + controls = []; // init state form.$name = attrs.name; @@ -61,6 +63,8 @@ function FormController(element, attrs) { } form.$addControl = function(control) { + controls.push(control); + if (control.$name && !form.hasOwnProperty(control.$name)) { form[control.$name] = control; } @@ -73,6 +77,8 @@ function FormController(element, attrs) { forEach(errors, function(queue, validationToken) { form.$setValidity(validationToken, true, control); }); + + arrayRemove(controls, control); }; form.$setValidity = function(validationToken, isValid, control) { @@ -120,6 +126,29 @@ function FormController(element, attrs) { parentForm.$setDirty(); }; + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setPristine + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the form to its pristine state. + * + * 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. + * + * 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 () { + element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); + form.$dirty = false; + form.$pristine = true; + forEach(controls, function(control) { + control.$setPristine(); + }); + }; } diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index e17c12b35efc..695ca88ad079 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -978,6 +978,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ parentForm.$setValidity(validationErrorKey, isValid, this); }; + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setPristine + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Sets the control to its pristine state. + * + * This method can be called to remove the 'ng-dirty' class and set the control to its pristine + * state (ng-pristine class). + */ + this.$setPristine = function () { + this.$dirty = false; + this.$pristine = true; + $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); + }; /** * @ngdoc function diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js index 2fd55f607eef..9fe98570102f 100644 --- a/test/ng/directive/formSpec.js +++ b/test/ng/directive/formSpec.js @@ -430,4 +430,110 @@ describe('form', function() { expect(doc).toBeDirty(); }); }); + + + describe('$setPristine', function() { + + it('should reset pristine state of form and controls', 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'); + + input1Ctrl.$setViewValue('xx'); + input2Ctrl.$setViewValue('yy'); + scope.$apply(); + expect(form).toBeDirty(); + expect(input1).toBeDirty(); + expect(input2).toBeDirty(); + + formCtrl.$setPristine(); + expect(form).toBePristine(); + expect(formCtrl.$pristine).toBe(true); + expect(formCtrl.$dirty).toBe(false); + expect(input1).toBePristine(); + expect(input1Ctrl.$pristine).toBe(true); + expect(input1Ctrl.$dirty).toBe(false); + expect(input2).toBePristine(); + expect(input2Ctrl.$pristine).toBe(true); + expect(input2Ctrl.$dirty).toBe(false); + }); + + + it('should reset pristine state of anonymous form controls', function() { + + doc = $compile( + '')(scope); + + scope.$digest(); + + var form = doc, + formCtrl = scope.testForm, + input = form.find('input').eq(0), + inputCtrl = input.controller('ngModel'); + + inputCtrl.$setViewValue('xx'); + scope.$apply(); + expect(form).toBeDirty(); + expect(input).toBeDirty(); + + formCtrl.$setPristine(); + expect(form).toBePristine(); + expect(formCtrl.$pristine).toBe(true); + expect(formCtrl.$dirty).toBe(false); + expect(input).toBePristine(); + expect(inputCtrl.$pristine).toBe(true); + expect(inputCtrl.$dirty).toBe(false); + }); + + + it('should reset pristine state of nested forms', function() { + + doc = $compile( + '')(scope); + + scope.$digest(); + + var form = doc, + formCtrl = scope.testForm, + nestedForm = form.find('div'), + nestedFormCtrl = nestedForm.controller('form'), + nestedInput = form.find('input').eq(0), + nestedInputCtrl = nestedInput.controller('ngModel'); + + nestedInputCtrl.$setViewValue('xx'); + scope.$apply(); + expect(form).toBeDirty(); + expect(nestedForm).toBeDirty(); + expect(nestedInput).toBeDirty(); + + formCtrl.$setPristine(); + expect(form).toBePristine(); + expect(formCtrl.$pristine).toBe(true); + expect(formCtrl.$dirty).toBe(false); + expect(nestedForm).toBePristine(); + expect(nestedFormCtrl.$pristine).toBe(true); + expect(nestedFormCtrl.$dirty).toBe(false); + expect(nestedInput).toBePristine(); + expect(nestedInputCtrl.$pristine).toBe(true); + expect(nestedInputCtrl.$dirty).toBe(false); + }); + }); }); diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 01669b18dd56..4dcb79a38d12 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -117,6 +117,18 @@ describe('NgModelController', function() { }); }); + describe('setPristine', function() { + + it('should set control to its pristine state', function() { + ctrl.$setViewValue('edit'); + expect(ctrl.$dirty).toBe(true); + expect(ctrl.$pristine).toBe(false); + + ctrl.$setPristine(); + expect(ctrl.$dirty).toBe(false); + expect(ctrl.$pristine).toBe(true); + }); + }); describe('view -> model', function() {