mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-24 10:20:23 +00:00
fix(forms): fix nesting issues and add tests
This commit is contained in:
parent
b6ae6e52f9
commit
089c0f8b0e
2 changed files with 119 additions and 83 deletions
|
|
@ -1,6 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
var nullFormCtrl = {
|
||||
$addControl: noop,
|
||||
$removeControl: noop,
|
||||
$setValidity: noop
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @name angular.module.ng.$compileProvider.directive.form.FormController
|
||||
|
|
@ -24,18 +30,23 @@
|
|||
* of `FormController`.
|
||||
*
|
||||
*/
|
||||
FormController.$inject = ['name', '$element'];
|
||||
function FormController(name, element) {
|
||||
FormController.$inject = ['name', '$element', '$attrs'];
|
||||
function FormController(name, element, attrs) {
|
||||
var form = this,
|
||||
parentForm = element.parent().inheritedData('$formController'),
|
||||
parentForm = element.parent().inheritedData('$formController') || nullFormCtrl,
|
||||
errors = form.$error = {};
|
||||
|
||||
// init state
|
||||
form.$name = attrs.name;
|
||||
form.$dirty = false;
|
||||
form.$pristine = true;
|
||||
form.$valid = true;
|
||||
form.$invalid = false;
|
||||
|
||||
// publish the form into scope
|
||||
name(this);
|
||||
|
||||
if (parentForm) {
|
||||
parentForm.$addControl(form);
|
||||
}
|
||||
parentForm.$addControl(form);
|
||||
|
||||
form.$addControl = function(control) {
|
||||
if (control.$name && !form.hasOwnProperty(control.$name)) {
|
||||
|
|
@ -71,26 +82,13 @@ function FormController(name, element) {
|
|||
form.$pristine = false;
|
||||
}
|
||||
|
||||
// init state
|
||||
form.$dirty = false;
|
||||
form.$pristine = true;
|
||||
form.$valid = true;
|
||||
form.$invalid = false;
|
||||
|
||||
function cleanupControlErrors(queue, validationToken, control) {
|
||||
if (queue) {
|
||||
control = control || this; // so that we can be used in forEach;
|
||||
for (var i = 0, length = queue.length; i < length; i++) {
|
||||
if (queue[i] === control) {
|
||||
queue.splice(i, 1);
|
||||
if (!queue.length) {
|
||||
delete errors[validationToken];
|
||||
|
||||
if (parentForm) {
|
||||
parentForm.$setValidity(validationToken, true, form);
|
||||
}
|
||||
}
|
||||
}
|
||||
arrayRemove(queue, control);
|
||||
if (!queue.length) {
|
||||
delete errors[validationToken];
|
||||
parentForm.$setValidity(validationToken, true, form);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,13 +96,10 @@ function FormController(name, element) {
|
|||
function addControlError(validationToken, control) {
|
||||
var queue = errors[validationToken];
|
||||
if (queue) {
|
||||
if (indexOf(queue, control)) return;
|
||||
if (includes(queue, control)) return;
|
||||
} else {
|
||||
errors[validationToken] = queue = [];
|
||||
|
||||
if (parentForm) {
|
||||
parentForm.$setValidity(validationToken, false, form);
|
||||
}
|
||||
parentForm.$setValidity(validationToken, false, form);
|
||||
}
|
||||
queue.push(control);
|
||||
}
|
||||
|
|
@ -222,6 +217,15 @@ var formDirective = [function() {
|
|||
formElement[value ? 'addClass' : 'removeClass']('ng-' + name);
|
||||
});
|
||||
});
|
||||
|
||||
var parentFormCtrl = formElement.parent().inheritedData('$formController');
|
||||
if (parentFormCtrl) {
|
||||
formElement.bind('$destroy', function() {
|
||||
parentFormCtrl.$removeControl(controller);
|
||||
if (attr.name) delete scope[attr.name];
|
||||
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,29 +94,6 @@ describe('form', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should chain nested forms', function() {
|
||||
doc = jqLite(
|
||||
'<ng:form name="parent">' +
|
||||
'<ng:form name="child">' +
|
||||
'<input ng:model="modelA" name="inputA">' +
|
||||
'</ng:form>' +
|
||||
'</ng:form>');
|
||||
$compile(doc)(scope);
|
||||
|
||||
var parent = scope.parent;
|
||||
var child = scope.child;
|
||||
var input = child.inputA;
|
||||
|
||||
input.$setValidity('MyError', false);
|
||||
expect(parent.$error.MyError).toEqual([child]);
|
||||
expect(child.$error.MyError).toEqual([input]);
|
||||
|
||||
input.$setValidity('MyError', true);
|
||||
expect(parent.$error.MyError).toBeUndefined();
|
||||
expect(child.$error.MyError).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should support two forms on a single scope', function() {
|
||||
doc = $compile(
|
||||
'<div>' +
|
||||
|
|
@ -152,38 +129,6 @@ describe('form', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should chain nested forms in repeater', function() {
|
||||
doc = jqLite(
|
||||
'<ng:form name=parent>' +
|
||||
'<ng:form ng:repeat="f in forms" name=child>' +
|
||||
'<input type=text ng:model=text name=text>' +
|
||||
'</ng:form>' +
|
||||
'</ng:form>');
|
||||
$compile(doc)(scope);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.forms = [1];
|
||||
});
|
||||
|
||||
var parent = scope.parent;
|
||||
var child = doc.find('input').scope().child;
|
||||
var input = child.text;
|
||||
|
||||
expect(parent).toBeDefined();
|
||||
expect(child).toBeDefined();
|
||||
expect(input).toBeDefined();
|
||||
|
||||
input.$setValidity('myRule', false);
|
||||
expect(input.$error.myRule).toEqual(true);
|
||||
expect(child.$error.myRule).toEqual([input]);
|
||||
expect(parent.$error.myRule).toEqual([child]);
|
||||
|
||||
input.$setValidity('myRule', true);
|
||||
expect(parent.$error.myRule).toBeUndefined();
|
||||
expect(child.$error.myRule).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should publish widgets', function() {
|
||||
doc = jqLite('<form name="form"><input type="text" name="w1" ng-model="some" /></form>');
|
||||
$compile(doc)(scope);
|
||||
|
|
@ -197,6 +142,93 @@ describe('form', function() {
|
|||
});
|
||||
|
||||
|
||||
describe('nested forms', function() {
|
||||
|
||||
it('should chain nested forms', function() {
|
||||
doc = jqLite(
|
||||
'<ng:form name="parent">' +
|
||||
'<ng:form name="child">' +
|
||||
'<input ng:model="modelA" name="inputA">' +
|
||||
'<input ng:model="modelB" name="inputB">' +
|
||||
'</ng:form>' +
|
||||
'</ng:form>');
|
||||
$compile(doc)(scope);
|
||||
|
||||
var parent = scope.parent,
|
||||
child = scope.child,
|
||||
inputA = child.inputA,
|
||||
inputB = child.inputB;
|
||||
|
||||
inputA.$setValidity('MyError', false);
|
||||
inputB.$setValidity('MyError', false);
|
||||
expect(parent.$error.MyError).toEqual([child]);
|
||||
expect(child.$error.MyError).toEqual([inputA, inputB]);
|
||||
|
||||
inputA.$setValidity('MyError', true);
|
||||
expect(parent.$error.MyError).toEqual([child]);
|
||||
expect(child.$error.MyError).toEqual([inputB]);
|
||||
|
||||
inputB.$setValidity('MyError', true);
|
||||
expect(parent.$error.MyError).toBeUndefined();
|
||||
expect(child.$error.MyError).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should deregister a child form when its DOM is removed', function() {
|
||||
doc = jqLite(
|
||||
'<ng:form name="parent">' +
|
||||
'<ng:form name="child">' +
|
||||
'<input ng:model="modelA" name="inputA" required>' +
|
||||
'</ng:form>' +
|
||||
'</ng:form>');
|
||||
$compile(doc)(scope);
|
||||
scope.$apply();
|
||||
|
||||
var parent = scope.parent,
|
||||
child = scope.child;
|
||||
|
||||
expect(parent.$error.required).toEqual([child]);
|
||||
doc.children().remove(); //remove child
|
||||
|
||||
expect(parent.child).toBeUndefined();
|
||||
expect(scope.child).toBeUndefined();
|
||||
expect(parent.$error.required).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should chain nested forms in repeater', function() {
|
||||
doc = jqLite(
|
||||
'<ng:form name=parent>' +
|
||||
'<ng:form ng:repeat="f in forms" name=child>' +
|
||||
'<input type=text ng:model=text name=text>' +
|
||||
'</ng:form>' +
|
||||
'</ng:form>');
|
||||
$compile(doc)(scope);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.forms = [1];
|
||||
});
|
||||
|
||||
var parent = scope.parent;
|
||||
var child = doc.find('input').scope().child;
|
||||
var input = child.text;
|
||||
|
||||
expect(parent).toBeDefined();
|
||||
expect(child).toBeDefined();
|
||||
expect(input).toBeDefined();
|
||||
|
||||
input.$setValidity('myRule', false);
|
||||
expect(input.$error.myRule).toEqual(true);
|
||||
expect(child.$error.myRule).toEqual([input]);
|
||||
expect(parent.$error.myRule).toEqual([child]);
|
||||
|
||||
input.$setValidity('myRule', true);
|
||||
expect(parent.$error.myRule).toBeUndefined();
|
||||
expect(child.$error.myRule).toBeUndefined();
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
describe('validation', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue