X Tutup
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions modules/angular2/src/core/facade/promise.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
library angular2.core.facade.promise;

import 'dart:async';
import 'dart:async' as async;
export 'dart:async' show Future;

class PromiseWrapper {
Expand Down Expand Up @@ -29,6 +30,10 @@ class PromiseWrapper {
return promise.catchError(onError);
}

static void scheduleMicrotask(fn) {
async.scheduleMicrotask(fn);
}

static PromiseCompleter<dynamic> completer() =>
new PromiseCompleter(new Completer());
}
Expand Down
4 changes: 4 additions & 0 deletions modules/angular2/src/core/facade/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export class PromiseWrapper {
});
}

static scheduleMicrotask(computation: () => any): void {
PromiseWrapper.then(PromiseWrapper.resolve(null), computation, (_) => {});
}

static completer(): PromiseCompleter<any> {
var resolve;
var reject;
Expand Down
13 changes: 5 additions & 8 deletions modules/angular2/src/core/forms/directives/ng_form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class NgForm extends ControlContainer implements Form {
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }

addControl(dir: NgControl): void {
this._later(_ => {
PromiseWrapper.scheduleMicrotask(() => {
var container = this._findContainer(dir.path);
var ctrl = new Control();
setUpControl(ctrl, dir);
Expand All @@ -117,7 +117,7 @@ export class NgForm extends ControlContainer implements Form {
getControl(dir: NgControl): Control { return <Control>this.form.find(dir.path); }

removeControl(dir: NgControl): void {
this._later(_ => {
PromiseWrapper.scheduleMicrotask(() => {
var container = this._findContainer(dir.path);
if (isPresent(container)) {
container.removeControl(dir.name);
Expand All @@ -127,7 +127,7 @@ export class NgForm extends ControlContainer implements Form {
}

addControlGroup(dir: NgControlGroup): void {
this._later(_ => {
PromiseWrapper.scheduleMicrotask(() => {
var container = this._findContainer(dir.path);
var group = new ControlGroup({});
setUpControlGroup(group, dir);
Expand All @@ -137,7 +137,7 @@ export class NgForm extends ControlContainer implements Form {
}

removeControlGroup(dir: NgControlGroup): void {
this._later(_ => {
PromiseWrapper.scheduleMicrotask(() => {
var container = this._findContainer(dir.path);
if (isPresent(container)) {
container.removeControl(dir.name);
Expand All @@ -151,7 +151,7 @@ export class NgForm extends ControlContainer implements Form {
}

updateModel(dir: NgControl, value: any): void {
this._later(_ => {
PromiseWrapper.scheduleMicrotask(() => {
var ctrl = <Control>this.form.find(dir.path);
ctrl.updateValue(value);
});
Expand All @@ -167,7 +167,4 @@ export class NgForm extends ControlContainer implements Form {
path.pop();
return ListWrapper.isEmpty(path) ? this.form : <ControlGroup>this.form.find(path);
}

/** @internal */
_later(fn): void { PromiseWrapper.then(PromiseWrapper.resolve(null), fn, (_) => {}); }
}
69 changes: 58 additions & 11 deletions modules/angular2/src/core/forms/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {StringWrapper, isPresent, isBlank, normalizeBool} from 'angular2/src/core/facade/lang';
import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/core/facade/async';
import {StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
import {Validators} from './validators';

/**
* Indicates that a Control is valid, i.e. that no errors exist in the input value.
Expand Down Expand Up @@ -59,8 +58,9 @@ export abstract class AbstractControl {
private _pristine: boolean = true;
private _touched: boolean = false;
private _parent: ControlGroup | ControlArray;
private _asyncValidationSubscription;

constructor(public validator: Function) {}
constructor(public validator: Function, public asyncValidator: Function) {}

get value(): any { return this._value; }

Expand Down Expand Up @@ -119,10 +119,14 @@ export abstract class AbstractControl {

this._updateValue();

this._errors = this.validator(this);
this._errors = this._runValidator();
this._controlsErrors = this._calculateControlsErrors();
this._status = this._calculateStatus();

if (this._status == VALID || this._status == PENDING) {
this._runAsyncValidator();
}

if (emitEvent) {
ObservableWrapper.callNext(this._valueChanges, this._value);
}
Expand All @@ -132,6 +136,23 @@ export abstract class AbstractControl {
}
}

private _runValidator() { return isPresent(this.validator) ? this.validator(this) : null; }

private _runAsyncValidator() {
if (isPresent(this.asyncValidator)) {
this._status = PENDING;
this._cancelExistingSubscription();
this._asyncValidationSubscription =
ObservableWrapper.subscribe(this.asyncValidator(this), res => this.setErrors(res));
}
}

private _cancelExistingSubscription(): void {
if (isPresent(this._asyncValidationSubscription)) {
ObservableWrapper.dispose(this._asyncValidationSubscription);
}
}

/**
* Sets errors on a control.
*
Expand Down Expand Up @@ -190,13 +211,18 @@ export abstract class AbstractControl {
}

private _calculateStatus(): string {
return isPresent(this._errors) || isPresent(this._controlsErrors) ? INVALID : VALID;
if (isPresent(this._errors)) return INVALID;
if (this._anyControlsHaveStatus(PENDING)) return PENDING;
if (this._anyControlsHaveStatus(INVALID)) return INVALID;
return VALID;
}

/** @internal */
abstract _updateValue(): void;
/** @internal */
abstract _calculateControlsErrors(): any;
/** @internal */
abstract _anyControlsHaveStatus(status: string): boolean;
}

/**
Expand All @@ -219,8 +245,8 @@ export class Control extends AbstractControl {
/** @internal */
_onChange: Function;

constructor(value: any = null, validator: Function = Validators.nullValidator) {
super(validator);
constructor(value: any = null, validator: Function = null, asyncValidator: Function = null) {
super(validator, asyncValidator);
this._value = value;
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
this._valueChanges = new EventEmitter();
Expand Down Expand Up @@ -259,6 +285,11 @@ export class Control extends AbstractControl {
*/
_calculateControlsErrors() { return null; }

/**
* @internal
*/
_anyControlsHaveStatus(status: string): boolean { return false; }

/**
* Register a listener for change events.
*/
Expand All @@ -282,9 +313,9 @@ export class ControlGroup extends AbstractControl {
private _optionals: {[key: string]: boolean};

constructor(public controls: {[key: string]: AbstractControl},
optionals: {[key: string]: boolean} = null,
validator: Function = Validators.nullValidator) {
super(validator);
optionals: {[key: string]: boolean} = null, validator: Function = null,
asyncValidator: Function = null) {
super(validator, asyncValidator);
this._optionals = isPresent(optionals) ? optionals : {};
this._valueChanges = new EventEmitter();

Expand Down Expand Up @@ -348,6 +379,15 @@ export class ControlGroup extends AbstractControl {
return StringMapWrapper.isEmpty(res) ? null : res;
}

/** @internal */
_anyControlsHaveStatus(status: string): boolean {
var res = false;
StringMapWrapper.forEach(this.controls, (control, name) => {
res = res || (this.contains(name) && control.status == status);
});
return res;
}

/** @internal */
_reduceValue() {
return this._reduceChildren({}, (acc, control, name) => {
Expand Down Expand Up @@ -396,8 +436,9 @@ export class ControlGroup extends AbstractControl {
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
*/
export class ControlArray extends AbstractControl {
constructor(public controls: AbstractControl[], validator: Function = Validators.nullValidator) {
super(validator);
constructor(public controls: AbstractControl[], validator: Function = null,
asyncValidator: Function = null) {
super(validator, asyncValidator);

this._valueChanges = new EventEmitter();

Expand Down Expand Up @@ -457,6 +498,12 @@ export class ControlArray extends AbstractControl {
return anyErrors ? res : null;
}

/** @internal */
_anyControlsHaveStatus(status: string): boolean {
return ListWrapper.any(this.controls, c => c.status == status);
}


/** @internal */
_setParentForControls(): void {
this.controls.forEach((control) => { control.setParent(this); });
Expand Down
5 changes: 2 additions & 3 deletions modules/angular2/src/core/forms/validators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {isBlank, isPresent} from 'angular2/src/core/facade/lang';
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
import {isBlank, isPresent, CONST_EXPR} from 'angular2/src/core/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
import {OpaqueToken} from 'angular2/src/core/di';

Expand Down Expand Up @@ -81,7 +80,7 @@ export class Validators {

return function(control: modelModule.AbstractControl) {
var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(control);
var errors = isPresent(validator) ? validator(control) : null;
return isPresent(errors) ? StringMapWrapper.merge(<any>res, <any>errors) : res;
}, {});
return StringMapWrapper.isEmpty(res) ? null : res;
Expand Down
6 changes: 0 additions & 6 deletions modules/angular2/test/core/forms/form_builder_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,6 @@ export function main() {
expect(g.validator).toBe(Validators.nullValidator);
});

it("should use default validators when no validators are provided", () => {
var g = b.group({"login": "some value"});
expect(g.controls["login"].validator).toBe(Validators.nullValidator);
expect(g.validator).toBe(Validators.nullValidator);
});

it("should create control arrays", () => {
var c = b.control("three");
var a = b.array(["one", ["two", Validators.required], c, b.array(['four'])]);
Expand Down
47 changes: 46 additions & 1 deletion modules/angular2/test/core/forms/integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {By} from 'angular2/src/core/debug';
import {ListWrapper} from 'angular2/src/core/facade/collection';
import {ObservableWrapper} from 'angular2/src/core/facade/async';
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
import {PromiseWrapper} from "angular2/src/core/facade/promise";

export function main() {
describe("integration tests", () => {
Expand Down Expand Up @@ -445,7 +446,7 @@ export function main() {
});
}));

it("should use validators defined in the model",
it("should use sync validators defined in the model",
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var form = new ControlGroup({"login": new Control("aa", Validators.required)});

Expand All @@ -467,6 +468,41 @@ export function main() {
async.done();
});
}));

it("should use async validators defined in the model",
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
var control =
new Control("", Validators.required, uniqLoginAsyncValidator("expected"));
var form = new ControlGroup({"login": control});

var t = `<div [ng-form-model]="form">
<input type="text" ng-control="login">
</div>`;

var rootTC;
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((root) => rootTC = root);
tick();

rootTC.debugElement.componentInstance.form = form;
rootTC.detectChanges();

expect(form.hasError("required", ["login"])).toEqual(true);

var input = rootTC.debugElement.query(By.css("input"));
input.nativeElement.value = "wrong value";
dispatchEvent(input.nativeElement, "change");

expect(form.pending).toEqual(true);
tick();

expect(form.hasError("uniqLogin", ["login"])).toEqual(true);

input.nativeElement.value = "expected";
dispatchEvent(input.nativeElement, "change");
tick();

expect(form.valid).toEqual(true);
})));
});

describe("nested forms", () => {
Expand Down Expand Up @@ -923,6 +959,15 @@ class MyInput implements ControlValueAccessor {
}
}

function uniqLoginAsyncValidator(expectedValue: string) {
return (c) => {
var e = new EventEmitter();
var res = (c.value == expectedValue) ? null : {"uniqLogin": true};
PromiseWrapper.scheduleMicrotask(() => ObservableWrapper.callNext(e, res));
return e;
};
}

function loginIsEmptyGroupValidator(c: ControlGroup) {
return c.controls["login"].value == "" ? {"loginIsEmpty": true} : null;
}
Expand Down
Loading
X Tutup