diff --git a/modules/angular2/src/forms/model.ts b/modules/angular2/src/forms/model.ts index eb490b6679..6ae5000576 100644 --- a/modules/angular2/src/forms/model.ts +++ b/modules/angular2/src/forms/model.ts @@ -1,4 +1,4 @@ -import {StringWrapper, isPresent} from 'angular2/src/facade/lang'; +import {StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang'; import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; import {StringMap, StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection'; import {Validators} from './validators'; @@ -21,6 +21,24 @@ export function isControl(c: Object): boolean { return c instanceof AbstractControl; } +function _find(c: AbstractControl, path: List| string) { + if (isBlank(path)) return null; + if (!(path instanceof List)) { + path = StringWrapper.split(path, new RegExp("/")); + } + if (ListWrapper.isEmpty(path)) return null; + + return ListWrapper.reduce(>path, (v, name) => { + if (v instanceof ControlGroup) { + return isPresent(v.controls[name]) ? v.controls[name] : null; + } else if (v instanceof ControlArray) { + var index = name; + return isPresent(v.at(index)) ? v.at(index) : null; + } else { + return null; + } + }, c); +} /** * Omitting from external API doc as this is really an abstract internal concept. @@ -31,7 +49,7 @@ export class AbstractControl { _errors: StringMap; _pristine: boolean; _touched: boolean; - _parent: any; /* ControlGroup | ControlArray */ + _parent: ControlGroup | ControlArray; validator: Function; _valueChanges: EventEmitter; @@ -78,6 +96,7 @@ export class AbstractControl { this._errors = this.validator(this); this._status = isPresent(this._errors) ? INVALID : VALID; + if (isPresent(this._parent) && !onlySelf) { this._parent.updateValidity({onlySelf: onlySelf}); } @@ -101,6 +120,21 @@ export class AbstractControl { } } + find(path: List| string): AbstractControl { return _find(this, path); } + + getError(errorCode: string, path: List = null) { + var c = this.find(path); + if (isPresent(c) && isPresent(c._errors)) { + return StringMapWrapper.get(c._errors, errorCode); + } else { + return null; + } + } + + hasError(errorCode: string, path: List = null) { + return isPresent(this.getError(errorCode, path)); + } + _updateValue(): void {} } @@ -168,7 +202,10 @@ export class ControlGroup extends AbstractControl { this.updateValidity({onlySelf: true}); } - addControl(name: string, c: AbstractControl) { this.controls[name] = c; } + addControl(name: string, c: AbstractControl) { + this.controls[name] = c; + c.setParent(this); + } removeControl(name: string) { StringMapWrapper.delete(this.controls, name); } @@ -187,18 +224,6 @@ export class ControlGroup extends AbstractControl { return c && this._included(controlName); } - find(path: string | List): AbstractControl { - if (!(path instanceof List)) { - path = StringWrapper.split(path, new RegExp("/")); - } - - return ListWrapper.reduce( - >path, (v, name) => v instanceof ControlGroup && isPresent(v.controls[name]) ? - v.controls[name] : - null, - this); - } - _setParentForControls() { StringMapWrapper.forEach(this.controls, (control, name) => { control.setParent(this); }); } diff --git a/modules/angular2/test/forms/model_spec.ts b/modules/angular2/test/forms/model_spec.ts index 018df5dbd0..7a100b9e0d 100644 --- a/modules/angular2/test/forms/model_spec.ts +++ b/modules/angular2/test/forms/model_spec.ts @@ -148,25 +148,6 @@ export function main() { }); }); - describe("find", () => { - var g; - beforeEach(() => { - g = new ControlGroup( - {"one": new Control("111"), "nested": new ControlGroup({"two": new Control("222")})}); - }); - - it("should return a control if it is present", () => { - expect(g.find(["nested", "two"]).value).toEqual("222"); - expect(g.find(["one"]).value).toEqual("111"); - expect(g.find("nested/two").value).toEqual("222"); - expect(g.find("one").value).toEqual("111"); - }); - - it("should return null otherwise", () => { - expect(g.find("invalid")).toBeNull(); - expect(g.find("one/invalid")).toBeNull(); - }); - }); describe("validator", () => { it("should run the validator with the initial value (valid)", () => { @@ -349,165 +330,212 @@ export function main() { // hard to test without hacking zones })); }); + + describe("getError", () => { + it("should return the error when it is present", () => { + var c = new Control("", Validators.required); + var g = new ControlGroup({"one": c}); + expect(g.getError("required", ["one"])).toEqual(true); + }); + + it("should return null otherwise", () => { + var c = new Control("not empty", Validators.required); + var g = new ControlGroup({"one": c}); + expect(g.getError("required", ["one"])).toEqual(null); + expect(g.getError("required", ["invalid"])).toEqual(null); + }); + }); + }); + }); + + describe("ControlArray", () => { + describe("adding/removing", () => { + var a; + var c1, c2, c3; + + beforeEach(() => { + a = new ControlArray([]); + c1 = new Control(1); + c2 = new Control(2); + c3 = new Control(3); + }); + + it("should support pushing", () => { + a.push(c1); + expect(a.length).toEqual(1); + expect(a.controls).toEqual([c1]); + }); + + it("should support removing", () => { + a.push(c1); + a.push(c2); + a.push(c3); + + a.removeAt(1); + + expect(a.controls).toEqual([c1, c3]); + }); + + it("should support inserting", () => { + a.push(c1); + a.push(c3); + + a.insert(1, c2); + + expect(a.controls).toEqual([c1, c2, c3]); + }); }); - describe("ControlArray", () => { - describe("adding/removing", () => { - var a; - var c1, c2, c3; - - beforeEach(() => { - a = new ControlArray([]); - c1 = new Control(1); - c2 = new Control(2); - c3 = new Control(3); - }); - - it("should support pushing", () => { - a.push(c1); - expect(a.length).toEqual(1); - expect(a.controls).toEqual([c1]); - }); - - it("should support removing", () => { - a.push(c1); - a.push(c2); - a.push(c3); - - a.removeAt(1); - - expect(a.controls).toEqual([c1, c3]); - }); - - it("should support inserting", () => { - a.push(c1); - a.push(c3); - - a.insert(1, c2); - - expect(a.controls).toEqual([c1, c2, c3]); - }); + describe("value", () => { + it("should be the reduced value of the child controls", () => { + var a = new ControlArray([new Control(1), new Control(2)]); + expect(a.value).toEqual([1, 2]); }); - describe("value", () => { - it("should be the reduced value of the child controls", () => { - var a = new ControlArray([new Control(1), new Control(2)]); - expect(a.value).toEqual([1, 2]); - }); + it("should be an empty array when there are no child controls", () => { + var a = new ControlArray([]); + expect(a.value).toEqual([]); + }); + }); - it("should be an empty array when there are no child controls", () => { - var a = new ControlArray([]); - expect(a.value).toEqual([]); - }); + describe("validator", () => { + it("should run the validator with the initial value (valid)", () => { + var a = new ControlArray( + [new Control(1, Validators.required), new Control(2, Validators.required)]); + + expect(a.valid).toBe(true); + expect(a.errors).toBe(null); }); - describe("validator", () => { - it("should run the validator with the initial value (valid)", () => { - var a = new ControlArray( - [new Control(1, Validators.required), new Control(2, Validators.required)]); + it("should run the validator with the initial value (invalid)", () => { + var a = new ControlArray([ + new Control(1, Validators.required), + new Control(null, Validators.required), + new Control(2, Validators.required) + ]); - expect(a.valid).toBe(true); - expect(a.errors).toBe(null); - }); - - it("should run the validator with the initial value (invalid)", () => { - var a = new ControlArray([ - new Control(1, Validators.required), - new Control(null, Validators.required), - new Control(2, Validators.required) - ]); - - expect(a.valid).toBe(false); - expect(a.errors).toEqual({"required": [a.controls[1]]}); - }); - - it("should run the validator when the value changes", () => { - var a = new ControlArray([]); - var c = new Control(null, Validators.required); - a.push(c); - expect(a.valid).toBe(false); - - c.updateValue("some value"); - - expect(a.valid).toBe(true); - expect(a.errors).toBe(null); - }); + expect(a.valid).toBe(false); + expect(a.errors).toEqual({"required": [a.controls[1]]}); }); - describe("dirty", () => { - var c, a; + it("should run the validator when the value changes", () => { + var a = new ControlArray([]); + var c = new Control(null, Validators.required); + a.push(c); + expect(a.valid).toBe(false); - beforeEach(() => { - c = new Control('value'); - a = new ControlArray([c]); - }); + c.updateValue("some value"); - it("should be false after creating a control", () => { expect(a.dirty).toEqual(false); }); + expect(a.valid).toBe(true); + expect(a.errors).toBe(null); + }); + }); - it("should be false after changing the value of the control", () => { - c.markAsDirty(); + describe("dirty", () => { + var c, a; - expect(a.dirty).toEqual(true); - }); + beforeEach(() => { + c = new Control('value'); + a = new ControlArray([c]); }); - describe("valueChanges", () => { - var a, c1, c2; + it("should be false after creating a control", () => { expect(a.dirty).toEqual(false); }); - beforeEach(() => { - c1 = new Control("old1"); - c2 = new Control("old2"); - a = new ControlArray([c1, c2]); - }); + it("should be false after changing the value of the control", () => { + c.markAsDirty(); - it("should fire an event after the value has been updated", - inject([AsyncTestCompleter], (async) => { - ObservableWrapper.subscribe(a.valueChanges, (value) => { - expect(a.value).toEqual(['new1', 'old2']); - expect(value).toEqual(['new1', 'old2']); - async.done(); - }); - c1.updateValue("new1"); - })); - - it("should fire an event after the control's observable fired an event", - inject([AsyncTestCompleter], (async) => { - var controlCallbackIsCalled = false; - - ObservableWrapper.subscribe(c1.valueChanges, - (value) => { controlCallbackIsCalled = true; }); - - ObservableWrapper.subscribe(a.valueChanges, (value) => { - expect(controlCallbackIsCalled).toBe(true); - async.done(); - }); - - c1.updateValue("new1"); - })); - - it("should fire an event when a control is removed", - inject([AsyncTestCompleter], (async) => { - ObservableWrapper.subscribe(a.valueChanges, (value) => { - expect(value).toEqual(['old1']); - async.done(); - }); - - a.removeAt(1); - })); - - it("should fire an event when a control is added", - inject([AsyncTestCompleter], (async) => { - a.removeAt(1); - - ObservableWrapper.subscribe(a.valueChanges, (value) => { - expect(value).toEqual(['old1', 'old2']); - async.done(); - }); - - a.push(c2); - })); + expect(a.dirty).toEqual(true); }); }); + + describe("valueChanges", () => { + var a, c1, c2; + + beforeEach(() => { + c1 = new Control("old1"); + c2 = new Control("old2"); + a = new ControlArray([c1, c2]); + }); + + it("should fire an event after the value has been updated", + inject([AsyncTestCompleter], (async) => { + ObservableWrapper.subscribe(a.valueChanges, (value) => { + expect(a.value).toEqual(['new1', 'old2']); + expect(value).toEqual(['new1', 'old2']); + async.done(); + }); + c1.updateValue("new1"); + })); + + it("should fire an event after the control's observable fired an event", + inject([AsyncTestCompleter], (async) => { + var controlCallbackIsCalled = false; + + ObservableWrapper.subscribe(c1.valueChanges, + (value) => { controlCallbackIsCalled = true; }); + + ObservableWrapper.subscribe(a.valueChanges, (value) => { + expect(controlCallbackIsCalled).toBe(true); + async.done(); + }); + + c1.updateValue("new1"); + })); + + it("should fire an event when a control is removed", + inject([AsyncTestCompleter], (async) => { + ObservableWrapper.subscribe(a.valueChanges, (value) => { + expect(value).toEqual(['old1']); + async.done(); + }); + + a.removeAt(1); + })); + + it("should fire an event when a control is added", inject([AsyncTestCompleter], (async) => { + a.removeAt(1); + + ObservableWrapper.subscribe(a.valueChanges, (value) => { + expect(value).toEqual(['old1', 'old2']); + async.done(); + }); + + a.push(c2); + })); + }); + }); + + describe("find", () => { + it("should return null when path is null", () => { + var g = new ControlGroup({}); + expect(g.find(null)).toEqual(null); + }); + + it("should return null when path is empty", () => { + var g = new ControlGroup({}); + expect(g.find([])).toEqual(null); + }); + + it("should return null when path is invalid", () => { + var g = new ControlGroup({}); + expect(g.find(["one", "two"])).toEqual(null); + }); + + it("should return a child of a control group", () => { + var g = new ControlGroup( + {"one": new Control("111"), "nested": new ControlGroup({"two": new Control("222")})}); + + expect(g.find(["nested", "two"]).value).toEqual("222"); + expect(g.find(["one"]).value).toEqual("111"); + expect(g.find("nested/two").value).toEqual("222"); + expect(g.find("one").value).toEqual("111"); + }); + + it("should return an element of an array", () => { + var g = new ControlGroup({"array": new ControlArray([new Control("111")])}); + + expect(g.find(["array", 0]).value).toEqual("111"); + }); }); }); }