From 4d1ed509e3d4ffb61ce57435198c489b2c035a8c Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 17 Jun 2015 14:45:40 -0700 Subject: [PATCH] refactor(forms): refactored forms to user Query to get html validators --- .../src/core/compiler/base_query_list.ts | 2 - modules/angular2/src/facade/collection.dart | 2 + modules/angular2/src/facade/collection.ts | 8 +++- modules/angular2/src/forms/directives.ts | 2 +- .../src/forms/directives/ng_control.ts | 4 +- .../src/forms/directives/ng_control_name.ts | 17 +++++--- .../src/forms/directives/ng_form_control.ts | 14 +++++-- .../angular2/src/forms/directives/ng_model.ts | 18 ++++++-- .../angular2/src/forms/directives/shared.ts | 12 ++++-- .../src/forms/directives/validators.ts | 21 +++++++--- .../angular2/test/forms/directives_spec.ts | 41 +++++++++++++++---- 11 files changed, 104 insertions(+), 37 deletions(-) diff --git a/modules/angular2/src/core/compiler/base_query_list.ts b/modules/angular2/src/core/compiler/base_query_list.ts index 9184370ee8..b23ea565ab 100644 --- a/modules/angular2/src/core/compiler/base_query_list.ts +++ b/modules/angular2/src/core/compiler/base_query_list.ts @@ -38,8 +38,6 @@ export class BaseQueryList { removeCallback(callback) { ListWrapper.remove(this._callbacks, callback); } get length() { return this._results.length; } - get first() { return ListWrapper.first(this._results); } - get last() { return ListWrapper.last(this._results); } } diff --git a/modules/angular2/src/facade/collection.dart b/modules/angular2/src/facade/collection.dart index 04c9c28404..a7a97b3e2f 100644 --- a/modules/angular2/src/facade/collection.dart +++ b/modules/angular2/src/facade/collection.dart @@ -184,6 +184,8 @@ class ListWrapper { bool isListLikeIterable(obj) => obj is Iterable; +List iterableToList(Iterable ii) => ii.toList(); + void iterateListLike(iter, fn(item)) { assert(iter is Iterable); for (var item in iter) { diff --git a/modules/angular2/src/facade/collection.ts b/modules/angular2/src/facade/collection.ts index 1243da63ac..3941cf6ce8 100644 --- a/modules/angular2/src/facade/collection.ts +++ b/modules/angular2/src/facade/collection.ts @@ -253,7 +253,13 @@ export function iterateListLike(obj, fn: Function) { } } } - +export function iterableToList(ii:Iterable):List { + var res = []; + for (var i of ii) { + res.push(i); + } + return res; +} // Safari and Internet Explorer do not support the iterable parameter to the // Set constructor. We work around that by manually adding the items. diff --git a/modules/angular2/src/forms/directives.ts b/modules/angular2/src/forms/directives.ts index 5bd1ad0499..b32bd9da69 100644 --- a/modules/angular2/src/forms/directives.ts +++ b/modules/angular2/src/forms/directives.ts @@ -24,7 +24,7 @@ export {ControlValueAccessor} from './directives/control_value_accessor'; export {DefaultValueAccessor} from './directives/default_value_accessor'; export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor'; export {SelectControlValueAccessor} from './directives/select_control_value_accessor'; -export {NgRequiredValidator} from './directives/validators'; +export {NgValidator, NgRequiredValidator} from './directives/validators'; /** * diff --git a/modules/angular2/src/forms/directives/ng_control.ts b/modules/angular2/src/forms/directives/ng_control.ts index 0b7fb2cfac..9e6f405a75 100644 --- a/modules/angular2/src/forms/directives/ng_control.ts +++ b/modules/angular2/src/forms/directives/ng_control.ts @@ -1,5 +1,4 @@ import {ControlValueAccessor} from './control_value_accessor'; -import {Validators} from '../validators'; import {Control} from '../model'; /** @@ -12,11 +11,10 @@ import {Control} from '../model'; export class NgControl { name: string = null; valueAccessor: ControlValueAccessor = null; - validator: Function; + get validator(): Function { return null; } get path(): List { return null; } get control(): Control { return null; } - constructor() { this.validator = Validators.nullValidator; } viewToModelUpdate(newValue: any): void {} } diff --git a/modules/angular2/src/forms/directives/ng_control_name.ts b/modules/angular2/src/forms/directives/ng_control_name.ts index 19c384a110..62eb55672b 100644 --- a/modules/angular2/src/forms/directives/ng_control_name.ts +++ b/modules/angular2/src/forms/directives/ng_control_name.ts @@ -1,12 +1,14 @@ import {CONST_EXPR} from 'angular2/src/facade/lang'; import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; import {List, StringMapWrapper, StringMap} from 'angular2/src/facade/collection'; -import {Directive, Ancestor, onDestroy, onChange} from 'angular2/angular2'; + +import {Directive, Ancestor, onDestroy, onChange, Query, QueryList} from 'angular2/angular2'; import {forwardRef, Binding, Inject} from 'angular2/di'; import {ControlContainer} from './control_container'; import {NgControl} from './ng_control'; -import {controlPath} from './shared'; +import {NgValidator} from './validators'; +import {controlPath, composeNgValidator} from './shared'; import {Control} from '../model'; const controlNameBinding = @@ -82,13 +84,17 @@ export class NgControlName extends NgControl { _parent: ControlContainer; ngModel: EventEmitter; model: any; + ngValidators: QueryList; _added: boolean; - constructor(@Ancestor() _parent: ControlContainer) { + // Scope the query once https://github.com/angular/angular/issues/2603 is fixed + constructor(@Ancestor() parent: ControlContainer, + @Query(NgValidator) ngValidators: QueryList) { super(); - this._parent = _parent; + this._parent = parent; this.ngModel = new EventEmitter(); this._added = false; + this.ngValidators = ngValidators; } onChange(c: StringMap) { @@ -101,7 +107,6 @@ export class NgControlName extends NgControl { } } - onDestroy() { this.formDirective.removeControl(this); } viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.ngModel, newValue); } @@ -111,4 +116,6 @@ export class NgControlName extends NgControl { get formDirective(): any { return this._parent.formDirective; } get control(): Control { return this.formDirective.getControl(this); } + + get validator(): Function { return composeNgValidator(this.ngValidators); } } \ No newline at end of file diff --git a/modules/angular2/src/forms/directives/ng_form_control.ts b/modules/angular2/src/forms/directives/ng_form_control.ts index a80749fbf7..afc8b76171 100644 --- a/modules/angular2/src/forms/directives/ng_form_control.ts +++ b/modules/angular2/src/forms/directives/ng_form_control.ts @@ -2,12 +2,13 @@ import {CONST_EXPR} from 'angular2/src/facade/lang'; import {StringMapWrapper} from 'angular2/src/facade/collection'; import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; -import {Directive, Ancestor, onChange} from 'angular2/angular2'; +import {Directive, Ancestor, onChange, Query, QueryList} from 'angular2/angular2'; import {forwardRef, Binding} from 'angular2/di'; import {NgControl} from './ng_control'; import {Control} from '../model'; -import {setUpControl} from './shared'; +import {NgValidator} from './validators'; +import {setUpControl, composeNgValidator} from './shared'; const formControlBinding = CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgFormControl)})); @@ -72,11 +73,14 @@ export class NgFormControl extends NgControl { ngModel: EventEmitter; _added: boolean; model: any; + ngValidators: QueryList; - constructor() { + // Scope the query once https://github.com/angular/angular/issues/2603 is fixed + constructor(@Query(NgValidator) ngValidators: QueryList) { super(); this.ngModel = new EventEmitter(); this._added = false; + this.ngValidators = ngValidators; } onChange(c) { @@ -90,9 +94,11 @@ export class NgFormControl extends NgControl { } } + get path(): List { return []; } + get control(): Control { return this.form; } - get path(): List { return []; } + get validator(): Function { return composeNgValidator(this.ngValidators); } viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.ngModel, newValue); } } diff --git a/modules/angular2/src/forms/directives/ng_model.ts b/modules/angular2/src/forms/directives/ng_model.ts index d000c80e42..75dbf02650 100644 --- a/modules/angular2/src/forms/directives/ng_model.ts +++ b/modules/angular2/src/forms/directives/ng_model.ts @@ -2,12 +2,13 @@ import {CONST_EXPR} from 'angular2/src/facade/lang'; import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; import {StringMapWrapper} from 'angular2/src/facade/collection'; -import {Directive, Ancestor, onChange} from 'angular2/angular2'; +import {Directive, Ancestor, onChange, QueryList, Query} from 'angular2/angular2'; import {forwardRef, Binding} from 'angular2/di'; import {NgControl} from './ng_control'; import {Control} from '../model'; -import {setUpControl} from './shared'; +import {NgValidator} from './validators'; +import {setUpControl, composeNgValidator} from './shared'; const formControlBinding = CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgModel)})); @@ -42,13 +43,20 @@ export class NgModel extends NgControl { _added = false; ngModel = new EventEmitter(); model: any; + ngValidators: QueryList; + + // Scope the query once https://github.com/angular/angular/issues/2603 is fixed + constructor(@Query(NgValidator) ngValidators: QueryList) { + super(); + this.ngValidators = ngValidators; + } onChange(c) { if (!this._added) { setUpControl(this._control, this); - this.control.updateValidity(); + this._control.updateValidity(); this._added = true; - }; + } if (StringMapWrapper.contains(c, "model")) { this._control.updateValue(this.model); @@ -59,5 +67,7 @@ export class NgModel extends NgControl { get path(): List { return []; } + get validator(): Function { return composeNgValidator(this.ngValidators); } + viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.ngModel, newValue); } } diff --git a/modules/angular2/src/forms/directives/shared.ts b/modules/angular2/src/forms/directives/shared.ts index 0decce05a1..c15c0cd3b7 100644 --- a/modules/angular2/src/forms/directives/shared.ts +++ b/modules/angular2/src/forms/directives/shared.ts @@ -1,11 +1,12 @@ -import {ListWrapper} from 'angular2/src/facade/collection'; +import {ListWrapper, iterableToList} from 'angular2/src/facade/collection'; import {isBlank, BaseException} from 'angular2/src/facade/lang'; import {ControlContainer} from './control_container'; import {NgControl} from './ng_control'; +import {NgValidator} from './validators'; import {Control} from '../model'; import {Validators} from '../validators'; -import {Renderer, ElementRef} from 'angular2/angular2'; +import {Renderer, ElementRef, QueryList} from 'angular2/angular2'; export function controlPath(name, parent: ControlContainer) { @@ -35,6 +36,11 @@ export function setUpControl(c: Control, dir: NgControl) { dir.valueAccessor.registerOnTouched(() => c.markAsTouched()); } +export function composeNgValidator(ngValidators: QueryList): Function { + if (isBlank(ngValidators)) return Validators.nullValidator; + return Validators.compose(iterableToList(ngValidators).map(v => v.validator)); +} + function _throwError(dir: NgControl, message: string): void { var path = ListWrapper.join(dir.path, " -> "); throw new BaseException(`${message} '${path}'`); @@ -43,5 +49,5 @@ function _throwError(dir: NgControl, message: string): void { export function setProperty(renderer: Renderer, elementRef: ElementRef, propName: string, propValue: any) { renderer.setElementProperty(elementRef.parentView.render, elementRef.boundElementIndex, propName, - propValue); + propValue); } \ No newline at end of file diff --git a/modules/angular2/src/forms/directives/validators.ts b/modules/angular2/src/forms/directives/validators.ts index 88d0b38c62..6a96156f6a 100644 --- a/modules/angular2/src/forms/directives/validators.ts +++ b/modules/angular2/src/forms/directives/validators.ts @@ -1,10 +1,19 @@ +import {forwardRef, Binding} from 'angular2/di'; +import {CONST_EXPR} from 'angular2/src/facade/lang'; import {Directive} from '../../../angular2'; import {Validators} from '../validators'; -import {NgControl} from '../directives'; -@Directive({selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]'}) -export class NgRequiredValidator { - constructor(c: NgControl) { - c.validator = Validators.compose([c.validator, Validators.required]); - } +export class NgValidator { + get validator(): Function { throw "Is not implemented"; } +} + +const requiredValidatorBinding = + CONST_EXPR(new Binding(NgValidator, {toAlias: forwardRef(() => NgRequiredValidator)})); + +@Directive({ + selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]', + hostInjector: [requiredValidatorBinding] +}) +export class NgRequiredValidator extends NgValidator { + get validator(): Function { return Validators.required; } } diff --git a/modules/angular2/test/forms/directives_spec.ts b/modules/angular2/test/forms/directives_spec.ts index a812d70363..60338a5a3a 100644 --- a/modules/angular2/test/forms/directives_spec.ts +++ b/modules/angular2/test/forms/directives_spec.ts @@ -13,6 +13,9 @@ import { AsyncTestCompleter, inject } from 'angular2/test_lib'; + +import {QueryList} from 'angular2/angular2'; + import { ControlGroup, Control, @@ -22,7 +25,9 @@ import { ControlValueAccessor, Validators, NgForm, - NgFormControl + NgModel, + NgFormControl, + NgRequiredValidator } from 'angular2/forms'; class DummyControlValueAccessor implements ControlValueAccessor { @@ -46,14 +51,14 @@ export function main() { formModel = new ControlGroup({"login": new Control(null)}); form.form = formModel; - loginControlDir = new NgControlName(form); + loginControlDir = new NgControlName(form, new QueryList()); loginControlDir.name = "login"; loginControlDir.valueAccessor = new DummyControlValueAccessor(); }); describe("addControl", () => { it("should throw when no control found", () => { - var dir = new NgControlName(form); + var dir = new NgControlName(form, null); dir.name = "invalidName"; expect(() => form.addControl(dir)) @@ -61,7 +66,7 @@ export function main() { }); it("should throw when no value accessor", () => { - var dir = new NgControlName(form); + var dir = new NgControlName(form, null); dir.name = "login"; expect(() => form.addControl(dir)) @@ -69,7 +74,7 @@ export function main() { }); it("should set up validator", () => { - loginControlDir.validator = Validators.required; + loginControlDir.ngValidators.reset([new NgRequiredValidator()]); expect(formModel.find(["login"]).valid).toBe(true); @@ -127,7 +132,7 @@ export function main() { personControlGroupDir = new NgControlGroup(form); personControlGroupDir.name = "person"; - loginControlDir = new NgControlName(personControlGroupDir); + loginControlDir = new NgControlName(personControlGroupDir, null); loginControlDir.name = "login"; loginControlDir.valueAccessor = new DummyControlValueAccessor(); }); @@ -168,7 +173,7 @@ export function main() { var control; beforeEach(() => { - controlDir = new NgFormControl(); + controlDir = new NgFormControl(new QueryList()); controlDir.valueAccessor = new DummyControlValueAccessor(); control = new Control(null); @@ -176,7 +181,7 @@ export function main() { }); it("should set up validator", () => { - controlDir.validator = Validators.required; + controlDir.ngValidators.reset([new NgRequiredValidator()]); expect(control.valid).toBe(true); @@ -186,5 +191,25 @@ export function main() { expect(control.valid).toBe(false); }); }); + + describe("NgModel", () => { + var ngModel; + + beforeEach(() => { + ngModel = new NgModel(new QueryList()); + ngModel.valueAccessor = new DummyControlValueAccessor(); + }); + + it("should set up validator", () => { + ngModel.ngValidators.reset([new NgRequiredValidator()]); + + expect(ngModel.control.valid).toBe(true); + + // this will add the required validator and recalculate the validity + ngModel.onChange({}); + + expect(ngModel.control.valid).toBe(false); + }); + }); }); }