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/directives'; | ||||
| 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() { | ||||
|     this._groupDecorator.addDirective(this); | ||||
| 
 | ||||
|     if (isPresent(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()); | ||||
|   } | ||||
|  | ||||
							
								
								
									
										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 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); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										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'; | ||||
| 
 | ||||
| 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>):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; | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| @ -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) => { | ||||
|  | ||||
							
								
								
									
										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 {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); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
| } | ||||
| @ -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", () => { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user