diff --git a/modules/angular2/forms.js b/modules/angular2/forms.js index c6b0c5d325..e943dd2d96 100644 --- a/modules/angular2/forms.js +++ b/modules/angular2/forms.js @@ -1,3 +1,4 @@ export * from './src/forms/model'; export * from './src/forms/directives'; -export * from './src/forms/validators'; \ No newline at end of file +export * from './src/forms/validators'; +export * from './src/forms/validator_directives'; \ No newline at end of file diff --git a/modules/angular2/src/forms/directives.js b/modules/angular2/src/forms/directives.js index 9ca80e38a4..66c4d4ad89 100644 --- a/modules/angular2/src/forms/directives.js +++ b/modules/angular2/src/forms/directives.js @@ -88,14 +88,13 @@ export class ControlDirective { _initialize() { this._groupDecorator.addDirective(this); - if (isPresent(this.validator)) { - var c = this._control(); - c.validator = validators.compose([c.validator, this.validator]); - } + var c = this._control(); + c.validator = validators.compose([c.validator, this.validator]); if (isBlank(this.valueAccessor)) { this.valueAccessor = controlValueAccessorFor(this.type); } + this._updateDomValue(); DOM.on(this._el.domElement, "change", (_) => this._updateControlValue()); } diff --git a/modules/angular2/src/forms/model.js b/modules/angular2/src/forms/model.js index c714f20471..982b21e659 100644 --- a/modules/angular2/src/forms/model.js +++ b/modules/angular2/src/forms/model.js @@ -5,32 +5,69 @@ import {nullValidator, controlGroupValidator} from './validators'; export const VALID = "VALID"; export const INVALID = "INVALID"; +//interface IControl { +// get value():any; +// validator:Function; +// get status():string; +// get errors():Map; +// get active():boolean {} +// updateValue(value:any){} +// setParent(parent){} +//} + export class Control { - value:any; - validator:Function; - status:string; - errors; + _value:any; + _status:string; + _errors; + _updated:boolean; _parent:ControlGroup; + validator:Function; constructor(value:any, validator:Function = nullValidator) { - this.value = value; + this._value = value; this.validator = validator; - this._updateStatus(); + this._updated = true; } updateValue(value:any) { - this.value = value; - this._updateStatus(); + this._value = value; + this._updated = true; this._updateParent(); } - get valid() { - return this.status === VALID; + get active():boolean { + return true; } - _updateStatus() { - this.errors = this.validator(this); - this.status = isPresent(this.errors) ? INVALID : VALID; + get value() { + return this._value; + } + + get status() { + this._updateIfNeeded(); + return this._status; + } + + get valid() { + this._updateIfNeeded(); + return this._status === VALID; + } + + get errors() { + this._updateIfNeeded(); + return this._errors; + } + + setParent(parent){ + this._parent = parent; + } + + _updateIfNeeded() { + if (this._updated) { + this._updated = false; + this._errors = this.validator(this); + this._status = isPresent(this._errors) ? INVALID : VALID; + } } _updateParent() { @@ -41,42 +78,118 @@ export class Control { } export class ControlGroup { - controls; + _value:any; + _status:string; + _errors; + _updated:boolean; validator:Function; - status:string; - errors; + controls; constructor(controls, validator:Function = controlGroupValidator) { this.controls = controls; this.validator = validator; + this._updated = true; this._setParentForControls(); - this._updateStatus(); } get value() { - var res = {}; - StringMapWrapper.forEach(this.controls, (control, name) => { - res[name] = control.value; - }); - return res; + this._updateIfNeeded(); + return this._value; + } + + get status() { + this._updateIfNeeded(); + return this._status; } get valid() { - return this.status === VALID; + this._updateIfNeeded(); + return this._status === VALID; + } + + get errors() { + this._updateIfNeeded(); + return this._errors; } _setParentForControls() { StringMapWrapper.forEach(this.controls, (control, name) => { - control._parent = this; + control.setParent(this); }); } - _updateStatus() { - this.errors = this.validator(this); - this.status = isPresent(this.errors) ? INVALID : VALID; + _updateIfNeeded() { + if (this._updated) { + this._updated = false; + this._value = this._reduceValue(); + this._errors = this.validator(this); + this._status = isPresent(this._errors) ? INVALID : VALID; + } + } + + _reduceValue() { + var newValue = {}; + StringMapWrapper.forEach(this.controls, (control, name) => { + if (control.active) { + newValue[name] = control.value; + } + }); + return newValue; } _controlChanged() { - this._updateStatus(); + this._updated = true; } } + +export class OptionalControl { + _control:Control; + _cond:boolean; + + constructor(control:Control, cond:boolean) { + super(); + this._control = control; + this._cond = cond; + } + + get active():boolean { + return this._cond; + } + + get value() { + return this._control.value; + } + + get status() { + return this._control.status; + } + + get errors() { + return this._control.errors; + } + + set validator(v) { + this._control.validator = v; + } + + get validator() { + return this._control.validator; + } + + set cond(value:boolean){ + this._cond = value; + this._control._updateParent(); + } + + get cond():boolean{ + return this._cond; + } + + updateValue(value:any){ + this._control.updateValue(value); + } + + setParent(parent){ + this._control.setParent(parent); + } +} \ No newline at end of file diff --git a/modules/angular2/src/forms/validators.js b/modules/angular2/src/forms/validators.js index ec43010b0e..f49a75a58d 100644 --- a/modules/angular2/src/forms/validators.js +++ b/modules/angular2/src/forms/validators.js @@ -4,7 +4,7 @@ import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collectio import {ControlGroup, Control} from 'angular2/forms'; export function required(c:Control) { - return isBlank(c.value) || c.value === "" ? {"required" : true} : null; + return isBlank(c.value) || c.value == "" ? {"required" : true} : null; } export function nullValidator(c:Control) { @@ -13,17 +13,18 @@ export function nullValidator(c:Control) { export function compose(validators:List):Function { return function(c:Control) { - return ListWrapper.reduce(validators, (res, validator) => { + var res = ListWrapper.reduce(validators, (res, validator) => { var errors = validator(c); return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; }, {}); + return StringMapWrapper.isEmpty(res) ? null : res; } } export function controlGroupValidator(c:ControlGroup) { var res = {}; StringMapWrapper.forEach(c.controls, (control, name) => { - if (isPresent(control.errors)) { + if (control.active && isPresent(control.errors)) { res[name] = control.errors; } }); diff --git a/modules/angular2/test/forms/integration_spec.js b/modules/angular2/test/forms/integration_spec.js index b77c4ddc62..27eca06218 100644 --- a/modules/angular2/test/forms/integration_spec.js +++ b/modules/angular2/test/forms/integration_spec.js @@ -15,7 +15,7 @@ import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock'; import {Injector} from 'angular2/di'; import {Component, Decorator, Template} from 'angular2/core'; -import {ControlGroupDirective, ControlDirective, Control, ControlGroup, +import {ControlGroupDirective, ControlDirective, Control, ControlGroup, OptionalControl, ControlValueAccessor, RequiredValidatorDirective} from 'angular2/forms'; import * as validators from 'angular2/src/forms/validators'; @@ -42,7 +42,7 @@ export function main() { tplResolver.setTemplate(componentType, new Template({ inline: template, - directives: [ControlGroupDirective, ControlDirective, WrappedValue] + directives: [ControlGroupDirective, ControlDirective, WrappedValue, RequiredValidatorDirective] })); compiler.compile(componentType).then((pv) => { diff --git a/modules/angular2/test/forms/model_spec.js b/modules/angular2/test/forms/model_spec.js index 700b50a23a..8ef2f7bc21 100644 --- a/modules/angular2/test/forms/model_spec.js +++ b/modules/angular2/test/forms/model_spec.js @@ -1,5 +1,5 @@ import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib'; -import {ControlGroup, Control} from 'angular2/forms'; +import {ControlGroup, Control, OptionalControl} from 'angular2/forms'; import * as validations from 'angular2/forms'; export function main() { @@ -40,7 +40,17 @@ export function main() { }); describe("validator", () => { - it("should run the validator with the initial value", () => { + it("should run the validator with the initial value (valid)", () => { + var g = new ControlGroup({ + "one": new Control('value', validations.required) + }); + + expect(g.valid).toEqual(true); + + expect(g.errors).toEqual(null); + }); + + it("should run the validator with the initial value (invalid)", () => { var g = new ControlGroup({ "one": new Control(null, validations.required) }); @@ -61,4 +71,54 @@ export function main() { }); }); }); + + describe("OptionalControl", () => { + it("should read properties from the wrapped component", () => { + var wrapperControl = new Control("value", validations.required); + var c = new OptionalControl(wrapperControl, true); + + expect(c.value).toEqual('value'); + expect(c.status).toEqual('VALID'); + expect(c.validator).toEqual(validations.required); + }); + + it("should update the wrapped component", () => { + var wrappedControl = new Control("value"); + var c = new OptionalControl(wrappedControl, true); + + c.validator = validations.required; + c.updateValue("newValue"); + + + expect(wrappedControl.validator).toEqual(validations.required); + expect(wrappedControl.value).toEqual('newValue'); + }); + + it("should not include an inactive component into the group value", () => { + var group = new ControlGroup({ + "required" : new Control("requiredValue"), + "optional" : new OptionalControl(new Control("optionalValue"), false) + }); + + expect(group.value).toEqual({"required" : "requiredValue"}); + + group.controls["optional"].cond = true; + + expect(group.value).toEqual({"required" : "requiredValue", "optional" : "optionalValue"}); + }); + + it("should not run validations on an inactive component", () => { + var group = new ControlGroup({ + "required" : new Control("requiredValue", validations.required), + "optional" : new OptionalControl(new Control("", validations.required), false) + }); + + expect(group.valid).toEqual(true); + + group.controls["optional"].cond = true; + + expect(group.valid).toEqual(false); + }); + }); + } \ No newline at end of file diff --git a/modules/angular2/test/forms/validators_spec.js b/modules/angular2/test/forms/validators_spec.js index db3e8d7bcd..be3ccbb865 100644 --- a/modules/angular2/test/forms/validators_spec.js +++ b/modules/angular2/test/forms/validators_spec.js @@ -1,5 +1,5 @@ import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib'; -import {ControlGroup, Control, required, compose, controlGroupValidator} from 'angular2/forms'; +import {ControlGroup, Control, required, compose, controlGroupValidator, nullValidator} from 'angular2/forms'; export function main() { function validator(key:string, error:any){ @@ -35,6 +35,11 @@ export function main() { var c = compose([validator("a", 1), validator("a", 2)]); expect(c(new Control(""))).toEqual({"a" : 2}); }); + + it("should return null when no errors", () => { + var c = compose([nullValidator, nullValidator]); + expect(c(new Control(""))).toEqual(null); + }); }); describe("controlGroupValidator", () => {