feat(forms): add support for validations
This commit is contained in:
		
							parent
							
								
									65ebff056a
								
							
						
					
					
						commit
						ded83e589b
					
				
							
								
								
									
										1
									
								
								modules/angular2/angular2.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								modules/angular2/angular2.js
									
									
									
									
										vendored
									
									
								
							| @ -4,3 +4,4 @@ | ||||
| export * from './change_detection'; | ||||
| export * from './core'; | ||||
| export * from './directives'; | ||||
| export * from './forms'; | ||||
|  | ||||
							
								
								
									
										2
									
								
								modules/angular2/forms.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								modules/angular2/forms.js
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +1,4 @@ | ||||
| export * from './src/forms/model'; | ||||
| export * from './src/forms/directives'; | ||||
| export * from './src/forms/validators'; | ||||
| export * from './src/forms/validator_directives'; | ||||
|  | ||||
| @ -67,6 +67,14 @@ class StringMapWrapper { | ||||
|   static void forEach(Map m, fn(v, k)) { | ||||
|     m.forEach((k, v) => fn(v, k)); | ||||
|   } | ||||
|   static HashMap merge(HashMap a, HashMap b) { | ||||
|     var m = {}; | ||||
| 
 | ||||
|     a.forEach((k, v) => m[k] = v); | ||||
|     b.forEach((k, v) => m[k] = v); | ||||
| 
 | ||||
|     return m; | ||||
|   } | ||||
|   static bool isEmpty(Map m) => m.isEmpty; | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										22
									
								
								modules/angular2/src/forms/directives.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								modules/angular2/src/forms/directives.js
									
									
									
									
										vendored
									
									
								
							| @ -3,6 +3,7 @@ import {DOM} from 'angular2/src/facade/dom'; | ||||
| import {isBlank, isPresent, CONST} from 'angular2/src/facade/lang'; | ||||
| import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; | ||||
| import {ControlGroup, Control} from './model'; | ||||
| import * as validators from './validators'; | ||||
| 
 | ||||
| class ControlGroupDirectiveBase { | ||||
|   addDirective(directive):void {} | ||||
| @ -68,16 +69,25 @@ export class ControlDirectiveBase { | ||||
|   type:string; | ||||
|   valueAccessor:ControlValueAccessor; | ||||
| 
 | ||||
|   validator:Function; | ||||
| 
 | ||||
|   constructor(groupDecorator, el:NgElement)  { | ||||
|     this._groupDecorator = groupDecorator; | ||||
|     this._el = el; | ||||
|     this.validator = validators.nullValidator; | ||||
|   } | ||||
| 
 | ||||
|   _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._groupDecorator.addDirective(this); | ||||
|     this._updateDomValue(); | ||||
|     DOM.on(this._el.domElement, "change", (_) => this._updateControlValue()); | ||||
|   } | ||||
| @ -87,7 +97,7 @@ export class ControlDirectiveBase { | ||||
|   } | ||||
| 
 | ||||
|   _updateControlValue() { | ||||
|     this._control().value = this.valueAccessor.readValue(this._el.domElement); | ||||
|     this._control().updateValue(this.valueAccessor.readValue(this._el.domElement)); | ||||
|   } | ||||
| 
 | ||||
|   _control() { | ||||
| @ -205,6 +215,14 @@ export class NewControlGroupDirective extends ControlGroupDirectiveBase { | ||||
|   get value() { | ||||
|     return this._controlGroup.value; | ||||
|   } | ||||
| 
 | ||||
|   get errors() { | ||||
|     return this._controlGroup.errors; | ||||
|   } | ||||
| 
 | ||||
|   get valid() { | ||||
|     return this._controlGroup.valid; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export var FormDirectives = [ | ||||
|  | ||||
							
								
								
									
										65
									
								
								modules/angular2/src/forms/model.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										65
									
								
								modules/angular2/src/forms/model.js
									
									
									
									
										vendored
									
									
								
							| @ -1,18 +1,56 @@ | ||||
| import {StringMapWrapper, StringMap} from 'angular2/src/facade/collection'; | ||||
| import {isPresent} from 'angular2/src/facade/lang'; | ||||
| import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection'; | ||||
| import {nullValidator, controlGroupValidator} from './validators'; | ||||
| 
 | ||||
| export const VALID = "VALID"; | ||||
| export const INVALID = "INVALID"; | ||||
| 
 | ||||
| export class Control { | ||||
|   value:any; | ||||
|   validator:Function; | ||||
|   status:string; | ||||
|   errors; | ||||
|   _parent:ControlGroup; | ||||
| 
 | ||||
|   constructor(value:any) { | ||||
|   constructor(value:any, validator:Function = nullValidator) { | ||||
|     this.value = value; | ||||
|     this.validator = validator; | ||||
|     this._updateStatus(); | ||||
|   } | ||||
| 
 | ||||
|   updateValue(value:any) { | ||||
|     this.value = value; | ||||
|     this._updateStatus(); | ||||
|     this._updateParent(); | ||||
|   } | ||||
| 
 | ||||
|   get valid() { | ||||
|     return this.status === VALID; | ||||
|   } | ||||
| 
 | ||||
|   _updateStatus() { | ||||
|     this.errors = this.validator(this); | ||||
|     this.status = isPresent(this.errors) ? INVALID : VALID; | ||||
|   } | ||||
| 
 | ||||
|   _updateParent() { | ||||
|     if (isPresent(this._parent)){ | ||||
|       this._parent._controlChanged(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class ControlGroup { | ||||
|   controls: StringMap; | ||||
|   controls; | ||||
|   validator:Function; | ||||
|   status:string; | ||||
|   errors; | ||||
| 
 | ||||
|   constructor(controls:StringMap) { | ||||
|   constructor(controls, validator:Function = controlGroupValidator) { | ||||
|     this.controls = controls; | ||||
|     this.validator = validator; | ||||
|     this._setParentForControls(); | ||||
|     this._updateStatus(); | ||||
|   } | ||||
| 
 | ||||
|   get value() { | ||||
| @ -22,4 +60,23 @@ export class ControlGroup { | ||||
|     }); | ||||
|     return res; | ||||
|   } | ||||
| 
 | ||||
|   get valid() { | ||||
|     return this.status === VALID; | ||||
|   } | ||||
| 
 | ||||
|   _setParentForControls() { | ||||
|     StringMapWrapper.forEach(this.controls, (control, name) => { | ||||
|       control._parent = this; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   _updateStatus() { | ||||
|     this.errors = this.validator(this); | ||||
|     this.status = isPresent(this.errors) ? INVALID : VALID; | ||||
|   } | ||||
| 
 | ||||
|   _controlChanged() { | ||||
|     this._updateStatus(); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										15
									
								
								modules/angular2/src/forms/validator_directives.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								modules/angular2/src/forms/validator_directives.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| import {isBlank, isPresent} from 'angular2/src/facade/lang'; | ||||
| import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; | ||||
| import {Decorator} from 'angular2/core'; | ||||
| 
 | ||||
| import {ControlGroup, Control, ControlDirective} from 'angular2/forms'; | ||||
| import * as validators from 'angular2/forms'; | ||||
| 
 | ||||
| @Decorator({ | ||||
|   selector: '[required]' | ||||
| }) | ||||
| export class RequiredValidatorDirective { | ||||
|   constructor(c:ControlDirective) { | ||||
|     c.validator = validators.compose([c.validator, validators.required]); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										31
									
								
								modules/angular2/src/forms/validators.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								modules/angular2/src/forms/validators.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| import {isBlank, isPresent} from 'angular2/src/facade/lang'; | ||||
| import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; | ||||
| 
 | ||||
| import {ControlGroup, Control} from 'angular2/forms'; | ||||
| 
 | ||||
| export function required(c:Control) { | ||||
|   return isBlank(c.value) || c.value === "" ? {"required" : true} : null; | ||||
| } | ||||
| 
 | ||||
| export function nullValidator(c:Control) { | ||||
|   return null; | ||||
| } | ||||
| 
 | ||||
| export function compose(validators:List<Function>):Function { | ||||
|   return function(c:Control) { | ||||
|     return ListWrapper.reduce(validators, (res, validator) => { | ||||
|       var errors = validator(c); | ||||
|       return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; | ||||
|     }, {}); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function controlGroupValidator(c:ControlGroup) { | ||||
|   var res = {}; | ||||
|   StringMapWrapper.forEach(c.controls, (control, name) => { | ||||
|     if (isPresent(control.errors)) { | ||||
|       res[name] = control.errors; | ||||
|     } | ||||
|   }); | ||||
|   return StringMapWrapper.isEmpty(res) ? null : res; | ||||
| } | ||||
							
								
								
									
										31
									
								
								modules/angular2/test/forms/integration_spec.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								modules/angular2/test/forms/integration_spec.js
									
									
									
									
										vendored
									
									
								
							| @ -17,7 +17,8 @@ import {Injector} from 'angular2/di'; | ||||
| import {Component, Decorator, Template} from 'angular2/core'; | ||||
| import {ControlGroupDirective, ControlNameDirective, | ||||
|   ControlDirective, NewControlGroupDirective, | ||||
|   Control, ControlGroup, ControlValueAccessor} from 'angular2/forms'; | ||||
|   Control, ControlGroup, ControlValueAccessor, | ||||
|   RequiredValidatorDirective} from 'angular2/forms'; | ||||
| 
 | ||||
| export function main() { | ||||
|   function detectChanges(view) { | ||||
| @ -210,11 +211,37 @@ export function main() { | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       it("should support validators",(done) => { | ||||
|         var t = `<div #form [new-control-group]="{'login': 'loginValue'}">
 | ||||
|                   <input type="text" control="login" required> | ||||
|                 </div>`; | ||||
| 
 | ||||
|         compile(MyComp, t, new MyComp(), (view) => { | ||||
|           var form = view.contextWithLocals.get("form"); | ||||
|           expect(form.valid).toEqual(true); | ||||
| 
 | ||||
|           var input = queryView(view, "input"); | ||||
| 
 | ||||
|           input.value = ""; | ||||
|           dispatchEvent(input, "change"); | ||||
| 
 | ||||
|           expect(form.valid).toEqual(false); | ||||
|           done(); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| @Component({selector: "my-comp"}) | ||||
| @Component({ | ||||
|   selector: "my-comp" | ||||
| }) | ||||
| @Template({ | ||||
|   inline: "", | ||||
|   directives: [ControlGroupDirective, ControlNameDirective, | ||||
|     ControlDirective, NewControlGroupDirective, RequiredValidatorDirective, | ||||
|     WrappedValue] | ||||
| }) | ||||
| class MyComp { | ||||
|   form:ControlGroup; | ||||
|   name:string; | ||||
|  | ||||
							
								
								
									
										43
									
								
								modules/angular2/test/forms/model_spec.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								modules/angular2/test/forms/model_spec.js
									
									
									
									
										vendored
									
									
								
							| @ -1,7 +1,28 @@ | ||||
| import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib'; | ||||
| import {ControlGroup, Control} from 'angular2/forms'; | ||||
| import * as validations from 'angular2/forms'; | ||||
| 
 | ||||
| export function main() { | ||||
|   describe("Control", () => { | ||||
|     describe("validator", () => { | ||||
|       it("should run validator with the initial value", () => { | ||||
|         var c = new Control("value", validations.required); | ||||
|         expect(c.valid).toEqual(true); | ||||
|       }); | ||||
| 
 | ||||
|       it("should rerun the validator when the value changes", () => { | ||||
|         var c = new Control("value", validations.required); | ||||
|         c.updateValue(null); | ||||
|         expect(c.valid).toEqual(false); | ||||
|       }); | ||||
| 
 | ||||
|       it("should return errors", () => { | ||||
|         var c = new Control(null, validations.required); | ||||
|         expect(c.errors).toEqual({"required" : true}); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe("ControlGroup", () => { | ||||
|     describe("value", () => { | ||||
|       it("should be the reduced value of the child controls", () => { | ||||
| @ -17,5 +38,27 @@ export function main() { | ||||
|         expect(g.value).toEqual({}) | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe("validator", () => { | ||||
|       it("should run the validator with the initial value", () => { | ||||
|         var g = new ControlGroup({ | ||||
|           "one": new Control(null, validations.required) | ||||
|         }); | ||||
| 
 | ||||
|         expect(g.valid).toEqual(false); | ||||
| 
 | ||||
|         expect(g.errors).toEqual({"one": {"required" : true}}); | ||||
|       }); | ||||
| 
 | ||||
|       it("should run the validator with the value changes", () => { | ||||
|         var c = new Control(null, validations.required); | ||||
|         var g = new ControlGroup({"one": c}); | ||||
| 
 | ||||
|         c.updateValue("some value"); | ||||
| 
 | ||||
|         expect(g.valid).toEqual(true); | ||||
|         expect(g.errors).toEqual(null); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										73
									
								
								modules/angular2/test/forms/validators_spec.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								modules/angular2/test/forms/validators_spec.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | ||||
| import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib'; | ||||
| import {ControlGroup, Control, required, compose, controlGroupValidator} from 'angular2/forms'; | ||||
| 
 | ||||
| export function main() { | ||||
|   function validator(key:string, error:any){ | ||||
|     return function(c:Control) { | ||||
|       var r = {}; | ||||
|       r[key] = error; | ||||
|       return r; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   describe("Validators", () => { | ||||
|     describe("required", () => { | ||||
|       it("should error on an empty string", () => { | ||||
|         expect(required(new Control(""))).toEqual({"required" : true}); | ||||
|       }); | ||||
| 
 | ||||
|       it("should error on null", () => { | ||||
|         expect(required(new Control(null))).toEqual({"required" : true}); | ||||
|       }); | ||||
| 
 | ||||
|       it("should not error on a non-empty string", () => { | ||||
|         expect(required(new Control("not empty"))).toEqual(null); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe("compose", () => { | ||||
|       it("should collect errors from all the validators", () => { | ||||
|         var c = compose([validator("a", true), validator("b", true)]); | ||||
|         expect(c(new Control(""))).toEqual({"a" : true, "b" : true}); | ||||
|       }); | ||||
| 
 | ||||
|       it("should run validators left to right", () => { | ||||
|         var c = compose([validator("a", 1), validator("a", 2)]); | ||||
|         expect(c(new Control(""))).toEqual({"a" : 2}); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe("controlGroupValidator", () => { | ||||
|       it("should collect errors from the child controls", () => { | ||||
|         var g = new ControlGroup({ | ||||
|           "one" : new Control("one", validator("a", true)), | ||||
|           "two" : new Control("two", validator("b", true)) | ||||
|         }); | ||||
| 
 | ||||
|         expect(controlGroupValidator(g)).toEqual({ | ||||
|           "one" : {"a" : true}, | ||||
|           "two" : {"b" : true} | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       it("should not include keys for controls that have no errors", () => { | ||||
|         var g = new ControlGroup({ | ||||
|           "one" : new Control("one", validator("a", true)), | ||||
|           "two" : new Control("one") | ||||
|         }); | ||||
| 
 | ||||
|         expect(controlGroupValidator(g)).toEqual({ | ||||
|           "one" : {"a" : true} | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       it("should return null when no errors", () => { | ||||
|         var g = new ControlGroup({ | ||||
|           "one" : new Control("one") | ||||
|         }); | ||||
| 
 | ||||
|         expect(controlGroupValidator(g)).toEqual(null); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user