refactor(forms): refactored forms to user Query to get html validators

This commit is contained in:
vsavkin 2015-06-17 14:45:40 -07:00
parent 85b8a15374
commit 4d1ed509e3
11 changed files with 104 additions and 37 deletions

View File

@ -38,8 +38,6 @@ export class BaseQueryList<T> {
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); }
}

View File

@ -184,6 +184,8 @@ class ListWrapper {
bool isListLikeIterable(obj) => obj is Iterable;
List<T> iterableToList(Iterable<T> ii) => ii.toList();
void iterateListLike(iter, fn(item)) {
assert(iter is Iterable);
for (var item in iter) {

View File

@ -253,7 +253,13 @@ export function iterateListLike(obj, fn: Function) {
}
}
}
export function iterableToList<T>(ii:Iterable<T>):List<T> {
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.

View File

@ -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';
/**
*

View File

@ -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<string> { return null; }
get control(): Control { return null; }
constructor() { this.validator = Validators.nullValidator; }
viewToModelUpdate(newValue: any): void {}
}

View File

@ -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<NgValidator>;
_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<NgValidator>) {
super();
this._parent = _parent;
this._parent = parent;
this.ngModel = new EventEmitter();
this._added = false;
this.ngValidators = ngValidators;
}
onChange(c: StringMap<string, any>) {
@ -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); }
}

View File

@ -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<NgValidator>;
constructor() {
// Scope the query once https://github.com/angular/angular/issues/2603 is fixed
constructor(@Query(NgValidator) ngValidators: QueryList<NgValidator>) {
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<string> { return []; }
get control(): Control { return this.form; }
get path(): List<string> { return []; }
get validator(): Function { return composeNgValidator(this.ngValidators); }
viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.ngModel, newValue); }
}

View File

@ -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<NgValidator>;
// Scope the query once https://github.com/angular/angular/issues/2603 is fixed
constructor(@Query(NgValidator) ngValidators: QueryList<NgValidator>) {
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<string> { return []; }
get validator(): Function { return composeNgValidator(this.ngValidators); }
viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.ngModel, newValue); }
}

View File

@ -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<NgValidator>): 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}'`);

View File

@ -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; }
}

View File

@ -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<any>());
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<any>());
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<any>());
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);
});
});
});
}