diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js index 1fc3b6f1cff1..2677921c0d74 100644 --- a/src/ng/directive/form.js +++ b/src/ng/directive/form.js @@ -9,7 +9,8 @@ var nullFormCtrl = { $setValidity: noop, $setDirty: noop, $setPristine: noop, - $setSubmitted: noop + $setSubmitted: noop, + $$setSubmitted: noop }, PENDING_CLASS = 'ng-pending', SUBMITTED_CLASS = 'ng-submitted'; @@ -274,12 +275,25 @@ FormController.prototype = { * @name form.FormController#$setSubmitted * * @description - * Sets the form to its submitted state. + * Sets the form to its `$submitted` state. This will also set `$submitted` on all child and + * parent forms of the form. */ $setSubmitted: function() { + var rootForm = this; + while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) { + rootForm = rootForm.$$parentForm; + } + rootForm.$$setSubmitted(); + }, + + $$setSubmitted: function() { this.$$animate.addClass(this.$$element, SUBMITTED_CLASS); this.$submitted = true; - this.$$parentForm.$setSubmitted(); + forEach(this.$$controls, function(control) { + if (control.$$setSubmitted) { + control.$$setSubmitted(); + } + }); } }; @@ -338,16 +352,21 @@ addSetValidityMethod({ * @restrict EAC * * @description - * Nestable alias of {@link ng.directive:form `form`} directive. HTML - * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a - * sub-group of controls needs to be determined. - * - * Note: the purpose of `ngForm` is to group controls, - * but not to be a replacement for the `
` tag with all of its capabilities - * (e.g. posting to the server, ...). - * - * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into - * related scope, under this name. + * Helper directive that makes it possible to create control groups inside a + * {@link ng.directive:form `form`} directive. + * These "child forms" can be used, for example, to determine the validity of a sub-group of + * controls. + * + *
+ * **Note**: `ngForm` cannot be used as a replacement for ``, because it lacks its + * [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element). + * Specifically, you cannot submit `ngForm` like a `` tag. That means, + * you cannot send data to the server with `ngForm`, or integrate it with + * {@link ng.directive:ngSubmit `ngSubmit`}. + *
+ * + * @param {string=} ngForm|name Name of the form. If specified, the form controller will + * be published into the related scope, under this name. * */ diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js index b48ff1084468..2d996bb359e1 100644 --- a/test/ng/directive/formSpec.js +++ b/test/ng/directive/formSpec.js @@ -539,6 +539,113 @@ describe('form', function() { expect(parent.$submitted).toBeTruthy(); }); + it('should set $submitted to true on child forms when parent is submitted', function() { + doc = jqLite( + '' + + '' + + '' + + '' + + '' + + ''); + $compile(doc)(scope); + + var parent = scope.parent, + child = scope.child; + + parent.$setSubmitted(); + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + }); + + + it('should not propagate $submitted state on removed child forms when parent is submitted', function() { + doc = jqLite( + '' + + '' + + '' + + '' + + '' + + '' + + ''); + $compile(doc)(scope); + + var parent = scope.parent, + child = scope.child, + grandchild = scope.grandchild, + ggchild = scope.greatgrandchild; + + parent.$removeControl(child); + + parent.$setSubmitted(); + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).not.toBeTruthy(); + expect(grandchild.$submitted).not.toBeTruthy(); + + parent.$addControl(child); + + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).not.toBeTruthy(); + expect(grandchild.$submitted).not.toBeTruthy(); + + parent.$setSubmitted(); + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + + parent.$removeControl(child); + + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + + parent.$setPristine(); // sets $submitted to false + expect(parent.$submitted).not.toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + + grandchild.$setPristine(); + expect(grandchild.$submitted).not.toBeTruthy(); + + child.$setSubmitted(); + expect(parent.$submitted).not.toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + + child.$setPristine(); + expect(parent.$submitted).not.toBeTruthy(); + expect(child.$submitted).not.toBeTruthy(); + expect(grandchild.$submitted).not.toBeTruthy(); + + // Test upwards submission setting + grandchild.$setSubmitted(); + expect(parent.$submitted).not.toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + }); + + + it('should set $submitted to true on child and parent forms when form is submitted', function() { + doc = jqLite( + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''); + $compile(doc)(scope); + + var parent = scope.parent, + child = scope.child, + grandchild = scope.grandchild; + + child.$setSubmitted(); + + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + }); it('should deregister a child form when its DOM is removed', function() { doc = jqLite(