feat(forms): add optional controls
This commit is contained in:
		
							parent
							
								
									a73c643322
								
							
						
					
					
						commit
						f27e538e2c
					
				
							
								
								
									
										1
									
								
								modules/angular2/forms.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								modules/angular2/forms.js
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,4 @@ | |||||||
| export * from './src/forms/model'; | export * from './src/forms/model'; | ||||||
| export * from './src/forms/directives'; | export * from './src/forms/directives'; | ||||||
| export * from './src/forms/validators'; | export * from './src/forms/validators'; | ||||||
|  | export * from './src/forms/validator_directives'; | ||||||
							
								
								
									
										3
									
								
								modules/angular2/src/forms/directives.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								modules/angular2/src/forms/directives.js
									
									
									
									
										vendored
									
									
								
							| @ -88,14 +88,13 @@ export class ControlDirective { | |||||||
|   _initialize() { |   _initialize() { | ||||||
|     this._groupDecorator.addDirective(this); |     this._groupDecorator.addDirective(this); | ||||||
| 
 | 
 | ||||||
|     if (isPresent(this.validator)) { |  | ||||||
|     var c = this._control(); |     var c = this._control(); | ||||||
|     c.validator = validators.compose([c.validator, this.validator]); |     c.validator = validators.compose([c.validator, this.validator]); | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (isBlank(this.valueAccessor)) { |     if (isBlank(this.valueAccessor)) { | ||||||
|       this.valueAccessor = controlValueAccessorFor(this.type); |       this.valueAccessor = controlValueAccessorFor(this.type); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     this._updateDomValue(); |     this._updateDomValue(); | ||||||
|     DOM.on(this._el.domElement, "change", (_) => this._updateControlValue()); |     DOM.on(this._el.domElement, "change", (_) => this._updateControlValue()); | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										169
									
								
								modules/angular2/src/forms/model.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										169
									
								
								modules/angular2/src/forms/model.js
									
									
									
									
										vendored
									
									
								
							| @ -5,32 +5,69 @@ import {nullValidator, controlGroupValidator} from './validators'; | |||||||
| export const VALID = "VALID"; | export const VALID = "VALID"; | ||||||
| export const INVALID = "INVALID"; | 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 { | export class Control { | ||||||
|   value:any; |   _value:any; | ||||||
|   validator:Function; |   _status:string; | ||||||
|   status:string; |   _errors; | ||||||
|   errors; |   _updated:boolean; | ||||||
|   _parent:ControlGroup; |   _parent:ControlGroup; | ||||||
|  |   validator:Function; | ||||||
| 
 | 
 | ||||||
|   constructor(value:any, validator:Function = nullValidator) { |   constructor(value:any, validator:Function = nullValidator) { | ||||||
|     this.value = value; |     this._value = value; | ||||||
|     this.validator = validator; |     this.validator = validator; | ||||||
|     this._updateStatus(); |     this._updated = true; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   updateValue(value:any) { |   updateValue(value:any) { | ||||||
|     this.value = value; |     this._value = value; | ||||||
|     this._updateStatus(); |     this._updated = true; | ||||||
|     this._updateParent(); |     this._updateParent(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get valid() { |   get active():boolean { | ||||||
|     return this.status === VALID; |     return true; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _updateStatus() { |   get value() { | ||||||
|     this.errors = this.validator(this); |     return this._value; | ||||||
|     this.status = isPresent(this.errors) ? INVALID : VALID; |   } | ||||||
|  | 
 | ||||||
|  |   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() { |   _updateParent() { | ||||||
| @ -41,42 +78,118 @@ export class Control { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class ControlGroup { | export class ControlGroup { | ||||||
|   controls; |   _value:any; | ||||||
|  |   _status:string; | ||||||
|  |   _errors; | ||||||
|  |   _updated:boolean; | ||||||
|   validator:Function; |   validator:Function; | ||||||
|   status:string; |   controls; | ||||||
|   errors; |  | ||||||
| 
 | 
 | ||||||
|   constructor(controls, validator:Function = controlGroupValidator) { |   constructor(controls, validator:Function = controlGroupValidator) { | ||||||
|     this.controls = controls; |     this.controls = controls; | ||||||
|     this.validator = validator; |     this.validator = validator; | ||||||
|  |     this._updated = true; | ||||||
|     this._setParentForControls(); |     this._setParentForControls(); | ||||||
|     this._updateStatus(); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get value() { |   get value() { | ||||||
|     var res = {}; |     this._updateIfNeeded(); | ||||||
|     StringMapWrapper.forEach(this.controls, (control, name) => { |     return this._value; | ||||||
|       res[name] = control.value; |   } | ||||||
|     }); | 
 | ||||||
|     return res; |   get status() { | ||||||
|  |     this._updateIfNeeded(); | ||||||
|  |     return this._status; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get valid() { |   get valid() { | ||||||
|     return this.status === VALID; |     this._updateIfNeeded(); | ||||||
|  |     return this._status === VALID; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get errors() { | ||||||
|  |     this._updateIfNeeded(); | ||||||
|  |     return this._errors; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _setParentForControls() { |   _setParentForControls() { | ||||||
|     StringMapWrapper.forEach(this.controls, (control, name) => { |     StringMapWrapper.forEach(this.controls, (control, name) => { | ||||||
|       control._parent = this; |       control.setParent(this); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _updateStatus() { |   _updateIfNeeded() { | ||||||
|     this.errors = this.validator(this); |     if (this._updated) { | ||||||
|     this.status = isPresent(this.errors) ? INVALID : VALID; |       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() { |   _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); | ||||||
|   } |   } | ||||||
| } | } | ||||||
							
								
								
									
										7
									
								
								modules/angular2/src/forms/validators.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								modules/angular2/src/forms/validators.js
									
									
									
									
										vendored
									
									
								
							| @ -4,7 +4,7 @@ import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collectio | |||||||
| import {ControlGroup, Control} from 'angular2/forms'; | import {ControlGroup, Control} from 'angular2/forms'; | ||||||
| 
 | 
 | ||||||
| export function required(c:Control) { | 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) { | export function nullValidator(c:Control) { | ||||||
| @ -13,17 +13,18 @@ export function nullValidator(c:Control) { | |||||||
| 
 | 
 | ||||||
| export function compose(validators:List<Function>):Function { | export function compose(validators:List<Function>):Function { | ||||||
|   return function(c:Control) { |   return function(c:Control) { | ||||||
|     return ListWrapper.reduce(validators, (res, validator) => { |     var res = ListWrapper.reduce(validators, (res, validator) => { | ||||||
|       var errors = validator(c); |       var errors = validator(c); | ||||||
|       return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; |       return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; | ||||||
|     }, {}); |     }, {}); | ||||||
|  |     return StringMapWrapper.isEmpty(res) ? null : res; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function controlGroupValidator(c:ControlGroup) { | export function controlGroupValidator(c:ControlGroup) { | ||||||
|   var res = {}; |   var res = {}; | ||||||
|   StringMapWrapper.forEach(c.controls, (control, name) => { |   StringMapWrapper.forEach(c.controls, (control, name) => { | ||||||
|     if (isPresent(control.errors)) { |     if (control.active && isPresent(control.errors)) { | ||||||
|       res[name] = control.errors; |       res[name] = control.errors; | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock'; | |||||||
| import {Injector} from 'angular2/di'; | import {Injector} from 'angular2/di'; | ||||||
| 
 | 
 | ||||||
| import {Component, Decorator, Template} from 'angular2/core'; | import {Component, Decorator, Template} from 'angular2/core'; | ||||||
| import {ControlGroupDirective, ControlDirective, Control, ControlGroup, | import {ControlGroupDirective, ControlDirective, Control, ControlGroup, OptionalControl, | ||||||
|   ControlValueAccessor, RequiredValidatorDirective} from 'angular2/forms'; |   ControlValueAccessor, RequiredValidatorDirective} from 'angular2/forms'; | ||||||
| 
 | 
 | ||||||
| import * as validators from 'angular2/src/forms/validators'; | import * as validators from 'angular2/src/forms/validators'; | ||||||
| @ -42,7 +42,7 @@ export function main() { | |||||||
| 
 | 
 | ||||||
|     tplResolver.setTemplate(componentType, new Template({ |     tplResolver.setTemplate(componentType, new Template({ | ||||||
|       inline: template, |       inline: template, | ||||||
|       directives: [ControlGroupDirective, ControlDirective, WrappedValue] |       directives: [ControlGroupDirective, ControlDirective, WrappedValue, RequiredValidatorDirective] | ||||||
|     })); |     })); | ||||||
| 
 | 
 | ||||||
|     compiler.compile(componentType).then((pv) => { |     compiler.compile(componentType).then((pv) => { | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								modules/angular2/test/forms/model_spec.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										64
									
								
								modules/angular2/test/forms/model_spec.js
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,5 @@ | |||||||
| import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib'; | 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'; | import * as validations from 'angular2/forms'; | ||||||
| 
 | 
 | ||||||
| export function main() { | export function main() { | ||||||
| @ -40,7 +40,17 @@ export function main() { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe("validator", () => { |     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({ |         var g = new ControlGroup({ | ||||||
|           "one": new Control(null, validations.required) |           "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); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
| } | } | ||||||
| @ -1,5 +1,5 @@ | |||||||
| import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib'; | 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() { | export function main() { | ||||||
|   function validator(key:string, error:any){ |   function validator(key:string, error:any){ | ||||||
| @ -35,6 +35,11 @@ export function main() { | |||||||
|         var c = compose([validator("a", 1), validator("a", 2)]); |         var c = compose([validator("a", 1), validator("a", 2)]); | ||||||
|         expect(c(new Control(""))).toEqual({"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", () => { |     describe("controlGroupValidator", () => { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user