refactor(form): misc minor refactoring

Closes #3951
This commit is contained in:
Victor Berchet 2015-09-01 20:59:43 -07:00
parent 73351ac00f
commit d6cda15879
16 changed files with 160 additions and 197 deletions

View File

@ -40,7 +40,7 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor {
cd.valueAccessor = this; cd.valueAccessor = this;
} }
writeValue(value: any) { setProperty(this._renderer, this._elementRef, "checked", value); } writeValue(value: any): void { setProperty(this._renderer, this._elementRef, "checked", value); }
get ngClassUntouched(): boolean { get ngClassUntouched(): boolean {
return isPresent(this._cd.control) ? this._cd.control.untouched : false; return isPresent(this._cd.control) ? this._cd.control.untouched : false;

View File

@ -2,7 +2,7 @@ import {Form} from './form_interface';
import {AbstractControlDirective} from './abstract_control_directive'; import {AbstractControlDirective} from './abstract_control_directive';
/** /**
* A directive that contains a group of [NgControl]. * A directive that contains multiple {@link NgControl}.
* *
* Only used by the forms module. * Only used by the forms module.
*/ */

View File

@ -41,9 +41,7 @@ export class DefaultValueAccessor implements ControlValueAccessor {
cd.valueAccessor = this; cd.valueAccessor = this;
} }
writeValue(value: any) { writeValue(value: any): void {
// both this.value and setProperty are required at the moment
// remove when a proper imperative API is provided
var normalizedValue = isBlank(value) ? '' : value; var normalizedValue = isBlank(value) ? '' : value;
setProperty(this._renderer, this._elementRef, 'value', normalizedValue); setProperty(this._renderer, this._elementRef, 'value', normalizedValue);
} }

View File

@ -42,7 +42,8 @@ const controlGroupBinding =
* `}) * `})
* class SignupComp { * class SignupComp {
* onSignUp(value) { * onSignUp(value) {
* // value === {personal: {name: 'some name'}, * // value === {
* // personal: {name: 'some name'},
* // credentials: {login: 'some login', password: 'some password'}} * // credentials: {login: 'some login', password: 'some password'}}
* } * }
* } * }
@ -63,9 +64,9 @@ export class NgControlGroup extends ControlContainer implements OnInit,
this._parent = _parent; this._parent = _parent;
} }
onInit() { this.formDirective.addControlGroup(this); } onInit(): void { this.formDirective.addControlGroup(this); }
onDestroy() { this.formDirective.removeControlGroup(this); } onDestroy(): void { this.formDirective.removeControlGroup(this); }
get control(): ControlGroup { return this.formDirective.getControlGroup(this); } get control(): ControlGroup { return this.formDirective.getControlGroup(this); }

View File

@ -24,24 +24,23 @@ const controlNameBinding =
* *
* In this example, we create the login and password controls. * In this example, we create the login and password controls.
* We can work with each control separately: check its validity, get its value, listen to its * We can work with each control separately: check its validity, get its value, listen to its
changes. * changes.
* *
* ``` * ```
* @Component({selector: "login-comp"}) * @Component({selector: "login-comp"})
* @View({ * @View({
* directives: [FORM_DIRECTIVES], * directives: [FORM_DIRECTIVES],
* template: ` * template: `
* <form #f="form" (submit)='onLogIn(f.value)'> * <form #f="form" (submit)='onLogIn(f.value)'>
* Login <input type='text' ng-control='login' #l="form"> * Login <input type='text' ng-control='login' #l="form">
* <div *ng-if="!l.valid">Login is invalid</div> * <div *ng-if="!l.valid">Login is invalid</div>
* *
* Password <input type='password' ng-control='password'> * Password <input type='password' ng-control='password'>
* <button type='submit'>Log in!</button>
* <button type='submit'>Log in!</button> * </form>
* </form>
* `}) * `})
* class LoginComp { * class LoginComp {
* onLogIn(value) { * onLogIn(value): void {
* // value === {login: 'some login', password: 'some password'} * // value === {login: 'some login', password: 'some password'}
* } * }
* } * }
@ -54,17 +53,17 @@ const controlNameBinding =
* @View({ * @View({
* directives: [FORM_DIRECTIVES], * directives: [FORM_DIRECTIVES],
* template: ` * template: `
* <form (submit)='onLogIn()'> * <form (submit)='onLogIn()'>
* Login <input type='text' ng-control='login' [(ng-model)]="credentials.login"> * Login <input type='text' ng-control='login' [(ng-model)]="credentials.login">
* Password <input type='password' ng-control='password' * Password <input type='password' ng-control='password'
[(ng-model)]="credentials.password"> * [(ng-model)]="credentials.password">
* <button type='submit'>Log in!</button> * <button type='submit'>Log in!</button>
* </form> * </form>
* `}) * `})
* class LoginComp { * class LoginComp {
* credentials: {login:string, password:string}; * credentials: {login:string, password:string};
* *
* onLogIn() { * onLogIn(): void {
* // this.credentials.login === "some login" * // this.credentials.login === "some login"
* // this.credentials.password === "some password" * // this.credentials.password === "some password"
* } * }
@ -94,18 +93,18 @@ export class NgControlName extends NgControl implements OnChanges,
this.validators = validators; this.validators = validators;
} }
onChanges(c: StringMap<string, any>) { onChanges(changes: StringMap<string, any>) {
if (!this._added) { if (!this._added) {
this.formDirective.addControl(this); this.formDirective.addControl(this);
this._added = true; this._added = true;
} }
if (isPropertyUpdated(c, this.viewModel)) { if (isPropertyUpdated(changes, this.viewModel)) {
this.viewModel = this.model; this.viewModel = this.model;
this.formDirective.updateModel(this, this.model); this.formDirective.updateModel(this, this.model);
} }
} }
onDestroy() { this.formDirective.removeControl(this); } onDestroy(): void { this.formDirective.removeControl(this); }
viewToModelUpdate(newValue: any): void { viewToModelUpdate(newValue: any): void {
this.viewModel = newValue; this.viewModel = newValue;

View File

@ -42,8 +42,9 @@ const formDirectiveBinding =
* </form> * </form>
* `}) * `})
* class SignupComp { * class SignupComp {
* onSignUp(value) { * onSignUp(value): void {
* // value === {personal: {name: 'some name'}, * // value === {
* // personal: {name: 'some name'},
* // credentials: {login: 'some login', password: 'some password'}} * // credentials: {login: 'some login', password: 'some password'}}
* } * }
* } * }
@ -60,14 +61,9 @@ const formDirectiveBinding =
exportAs: 'form' exportAs: 'form'
}) })
export class NgForm extends ControlContainer implements Form { export class NgForm extends ControlContainer implements Form {
form: ControlGroup; form: ControlGroup = new ControlGroup({});
ngSubmit = new EventEmitter(); ngSubmit = new EventEmitter();
constructor() {
super();
this.form = new ControlGroup({});
}
get formDirective(): Form { return this; } get formDirective(): Form { return this; }
get control(): ControlGroup { return this.form; } get control(): ControlGroup { return this.form; }
@ -79,10 +75,10 @@ export class NgForm extends ControlContainer implements Form {
addControl(dir: NgControl): void { addControl(dir: NgControl): void {
this._later(_ => { this._later(_ => {
var container = this._findContainer(dir.path); var container = this._findContainer(dir.path);
var c = new Control(); var ctrl = new Control();
setUpControl(c, dir); setUpControl(ctrl, dir);
container.addControl(dir.name, c); container.addControl(dir.name, ctrl);
c.updateValidity(); ctrl.updateValidity();
}); });
} }
@ -101,9 +97,9 @@ export class NgForm extends ControlContainer implements Form {
addControlGroup(dir: NgControlGroup): void { addControlGroup(dir: NgControlGroup): void {
this._later(_ => { this._later(_ => {
var container = this._findContainer(dir.path); var container = this._findContainer(dir.path);
var c = new ControlGroup({}); var group = new ControlGroup({});
container.addControl(dir.name, c); container.addControl(dir.name, group);
c.updateValidity(); group.updateValidity();
}); });
} }
@ -123,8 +119,8 @@ export class NgForm extends ControlContainer implements Form {
updateModel(dir: NgControl, value: any): void { updateModel(dir: NgControl, value: any): void {
this._later(_ => { this._later(_ => {
var c = <Control>this.form.find(dir.path); var ctrl = <Control>this.form.find(dir.path);
c.updateValue(value); ctrl.updateValue(value);
}); });
} }
@ -138,9 +134,5 @@ export class NgForm extends ControlContainer implements Form {
return ListWrapper.isEmpty(path) ? this.form : <ControlGroup>this.form.find(path); return ListWrapper.isEmpty(path) ? this.form : <ControlGroup>this.form.find(path);
} }
_later(fn) { _later(fn): void { PromiseWrapper.then(PromiseWrapper.resolve(null), fn, (_) => {}); }
var c: PromiseCompleter<any> = PromiseWrapper.completer();
PromiseWrapper.then(c.promise, fn, (_) => {});
c.resolve(null);
}
} }

View File

@ -17,10 +17,8 @@ const formControlBinding =
* # Example * # Example
* *
* In this example, we bind the control to an input element. When the value of the input element * In this example, we bind the control to an input element. When the value of the input element
* changes, the value of * changes, the value of the control will reflect that change. Likewise, if the value of the
* the control will reflect that change. Likewise, if the value of the control changes, the input * control changes, the input element reflects that change.
* element reflects that
* change.
* *
* ``` * ```
* @Component({selector: "login-comp"}) * @Component({selector: "login-comp"})
@ -29,16 +27,12 @@ const formControlBinding =
* template: "<input type='text' [ng-form-control]='loginControl'>" * template: "<input type='text' [ng-form-control]='loginControl'>"
* }) * })
* class LoginComp { * class LoginComp {
* loginControl:Control; * loginControl: Control = new Control('');;
*
* constructor() {
* this.loginControl = new Control('');
* }
* } * }
* *
* ``` * ```
* *
* We can also use ng-model to bind a domain model to the form. * We can also use `ng-model` to bind a domain model to the form.
* *
* ``` * ```
* @Component({selector: "login-comp"}) * @Component({selector: "login-comp"})
@ -47,12 +41,8 @@ const formControlBinding =
* template: "<input type='text' [ng-form-control]='loginControl' [(ng-model)]='login'>" * template: "<input type='text' [ng-form-control]='loginControl' [(ng-model)]='login'>"
* }) * })
* class LoginComp { * class LoginComp {
* loginControl:Control; * loginControl: Control = new Control('');
* login:string; * login:string;
*
* constructor() {
* this.loginControl = new Control('');
* }
* } * }
* ``` * ```
*/ */
@ -76,13 +66,13 @@ export class NgFormControl extends NgControl implements OnChanges {
this.validators = validators; this.validators = validators;
} }
onChanges(c: StringMap<string, any>) { onChanges(changes: StringMap<string, any>): void {
if (!this._added) { if (!this._added) {
setUpControl(this.form, this); setUpControl(this.form, this);
this.form.updateValidity(); this.form.updateValidity();
this._added = true; this._added = true;
} }
if (isPropertyUpdated(c, this.viewModel)) { if (isPropertyUpdated(changes, this.viewModel)) {
this.form.updateValue(this.model); this.form.updateValue(this.model);
this.viewModel = this.model; this.viewModel = this.model;
} }

View File

@ -21,21 +21,21 @@ const formDirectiveBinding =
* # Example * # Example
* *
* In this example, we bind the control group to the form element, and we bind the login and * In this example, we bind the control group to the form element, and we bind the login and
* password controls to the * password controls to the login and password elements.
* login and password elements.
* *
* ``` * ```
* @Component({selector: "login-comp"}) * @Component({selector: "login-comp"})
* @View({ * @View({
* directives: [FORM_DIRECTIVES], * directives: [FORM_DIRECTIVES],
* template: "<form [ng-form-model]='loginForm'>" + * template: `
* "Login <input type='text' ng-control='login'>" + * <form [ng-form-model]='loginForm'>
* "Password <input type='password' ng-control='password'>" + * Login <input type='text' ng-control='login'>
* "<button (click)="onLogin()">Login</button>" + * Password <input type='password' ng-control='password'>
* "</form>" * <button (click)="onLogin()">Login</button>
* </form>`
* }) * })
* class LoginComp { * class LoginComp {
* loginForm:ControlGroup; * loginForm: ControlGroup;
* *
* constructor() { * constructor() {
* this.loginForm = new ControlGroup({ * this.loginForm = new ControlGroup({
@ -44,7 +44,7 @@ const formDirectiveBinding =
* }); * });
* } * }
* *
* onLogin() { * onLogin(): void {
* // this.loginForm.value * // this.loginForm.value
* } * }
* } * }
@ -57,15 +57,17 @@ const formDirectiveBinding =
* @Component({selector: "login-comp"}) * @Component({selector: "login-comp"})
* @View({ * @View({
* directives: [FORM_DIRECTIVES], * directives: [FORM_DIRECTIVES],
* template: "<form [ng-form-model]='loginForm'>" + * template: `
* "Login <input type='text' ng-control='login' [(ng-model)]='login'>" + * <form [ng-form-model]='loginForm'>
* "Password <input type='password' ng-control='password' [(ng-model)]='password'>" + * Login <input type='text' ng-control='login' [(ng-model)]='credentials.login'>
* "<button (click)="onLogin()">Login</button>" + * Password <input type='password' ng-control='password'
* "</form>" * [(ng-model)]='credentials.password'>
* <button (click)="onLogin()">Login</button>
* </form>`
* }) * })
* class LoginComp { * class LoginComp {
* credentials:{login:string, password:string} * credentials: {login: string, password: string};
* loginForm:ControlGroup; * loginForm: ControlGroup;
* *
* constructor() { * constructor() {
* this.loginForm = new ControlGroup({ * this.loginForm = new ControlGroup({
@ -74,7 +76,7 @@ const formDirectiveBinding =
* }); * });
* } * }
* *
* onLogin() { * onLogin(): void {
* // this.credentials.login === 'some login' * // this.credentials.login === 'some login'
* // this.credentials.password === 'some password' * // this.credentials.password === 'some password'
* } * }
@ -85,9 +87,7 @@ const formDirectiveBinding =
selector: '[ng-form-model]', selector: '[ng-form-model]',
bindings: [formDirectiveBinding], bindings: [formDirectiveBinding],
properties: ['form: ng-form-model'], properties: ['form: ng-form-model'],
host: { host: {'(submit)': 'onSubmit()'},
'(submit)': 'onSubmit()',
},
events: ['ngSubmit'], events: ['ngSubmit'],
exportAs: 'form' exportAs: 'form'
}) })
@ -97,7 +97,7 @@ export class NgFormModel extends ControlContainer implements Form,
directives: NgControl[] = []; directives: NgControl[] = [];
ngSubmit = new EventEmitter(); ngSubmit = new EventEmitter();
onChanges(_) { this._updateDomValue(); } onChanges(_): void { this._updateDomValue(); }
get formDirective(): Form { return this; } get formDirective(): Form { return this; }
@ -106,9 +106,9 @@ export class NgFormModel extends ControlContainer implements Form,
get path(): string[] { return []; } get path(): string[] { return []; }
addControl(dir: NgControl): void { addControl(dir: NgControl): void {
var c: any = this.form.find(dir.path); var ctrl: any = this.form.find(dir.path);
setUpControl(c, dir); setUpControl(ctrl, dir);
c.updateValidity(); ctrl.updateValidity();
this.directives.push(dir); this.directives.push(dir);
} }
@ -125,8 +125,8 @@ export class NgFormModel extends ControlContainer implements Form,
} }
updateModel(dir: NgControl, value: any): void { updateModel(dir: NgControl, value: any): void {
var c  = <Control>this.form.find(dir.path); var ctrl  = <Control>this.form.find(dir.path);
c.updateValue(value); ctrl.updateValue(value);
} }
onSubmit(): boolean { onSubmit(): boolean {
@ -136,8 +136,8 @@ export class NgFormModel extends ControlContainer implements Form,
_updateDomValue() { _updateDomValue() {
ListWrapper.forEach(this.directives, dir => { ListWrapper.forEach(this.directives, dir => {
var c: any = this.form.find(dir.path); var ctrl: any = this.form.find(dir.path);
dir.valueAccessor.writeValue(c.value); dir.valueAccessor.writeValue(ctrl.value);
}); });
} }
} }

View File

@ -20,9 +20,8 @@ const formControlBinding = CONST_EXPR(new Binding(NgControl, {toAlias: forwardRe
* @Component({selector: "search-comp"}) * @Component({selector: "search-comp"})
* @View({ * @View({
* directives: [FORM_DIRECTIVES], * directives: [FORM_DIRECTIVES],
* template: ` * template: `<input type='text' [(ng-model)]="searchQuery">`
<input type='text' [(ng-model)]="searchQuery"> * })
* `})
* class SearchComp { * class SearchComp {
* searchQuery: string; * searchQuery: string;
* } * }
@ -48,14 +47,14 @@ export class NgModel extends NgControl implements OnChanges {
this.validators = validators; this.validators = validators;
} }
onChanges(c: StringMap<string, any>) { onChanges(changes: StringMap<string, any>) {
if (!this._added) { if (!this._added) {
setUpControl(this._control, this); setUpControl(this._control, this);
this._control.updateValidity(); this._control.updateValidity();
this._added = true; this._added = true;
} }
if (isPropertyUpdated(c, this.viewModel)) { if (isPropertyUpdated(changes, this.viewModel)) {
this._control.updateValue(this.model); this._control.updateValue(this.model);
this.viewModel = this.model; this.viewModel = this.model;
} }

View File

@ -9,7 +9,7 @@ import {isPresent} from 'angular2/src/core/facade/lang';
import {setProperty} from './shared'; import {setProperty} from './shared';
/** /**
* Marks <option> as dynamic, so Angular can be notified when options change. * Marks `<option>` as dynamic, so Angular can be notified when options change.
* *
* #Example: * #Example:
* *
@ -53,7 +53,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
this._updateValueWhenListOfOptionsChanges(query); this._updateValueWhenListOfOptionsChanges(query);
} }
writeValue(value: any) { writeValue(value: any): void {
this.value = value; this.value = value;
setProperty(this._renderer, this._elementRef, "value", value); setProperty(this._renderer, this._elementRef, "value", value);
} }

View File

@ -16,25 +16,25 @@ export function controlPath(name: string, parent: ControlContainer): string[] {
return p; return p;
} }
export function setUpControl(c: Control, dir: NgControl) { export function setUpControl(control: Control, dir: NgControl): void {
if (isBlank(c)) _throwError(dir, "Cannot find control"); if (isBlank(control)) _throwError(dir, "Cannot find control");
if (isBlank(dir.valueAccessor)) _throwError(dir, "No value accessor for"); if (isBlank(dir.valueAccessor)) _throwError(dir, "No value accessor for");
c.validator = Validators.compose([c.validator, dir.validator]); control.validator = Validators.compose([control.validator, dir.validator]);
dir.valueAccessor.writeValue(c.value); dir.valueAccessor.writeValue(control.value);
// view -> model // view -> model
dir.valueAccessor.registerOnChange(newValue => { dir.valueAccessor.registerOnChange(newValue => {
dir.viewToModelUpdate(newValue); dir.viewToModelUpdate(newValue);
c.updateValue(newValue, {emitModelToViewChange: false}); control.updateValue(newValue, {emitModelToViewChange: false});
c.markAsDirty(); control.markAsDirty();
}); });
// model -> view // model -> view
c.registerOnChange(newValue => dir.valueAccessor.writeValue(newValue)); control.registerOnChange(newValue => dir.valueAccessor.writeValue(newValue));
// touched // touched
dir.valueAccessor.registerOnTouched(() => c.markAsTouched()); dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
} }
function _throwError(dir: NgControl, message: string): void { function _throwError(dir: NgControl, message: string): void {
@ -53,4 +53,4 @@ export function isPropertyUpdated(changes: StringMap<string, any>, viewModel: an
if (change.isFirstChange()) return true; if (change.isFirstChange()) return true;
return !looseIdentical(viewModel, change.currentValue); return !looseIdentical(viewModel, change.currentValue);
} }

View File

@ -15,9 +15,7 @@ import * as modelModule from './model';
* *
* @Component({ * @Component({
* selector: 'login-comp', * selector: 'login-comp',
* viewBindings: [ * viewBindings: [FormBuilder]
* FormBuilder
* ]
* }) * })
* @View({ * @View({
* template: ` * template: `
@ -30,9 +28,7 @@ import * as modelModule from './model';
* </div> * </div>
* </form> * </form>
* `, * `,
* directives: [ * directives: [FORM_DIRECTIVES]
* FORM_DIRECTIVES
* ]
* }) * })
* class LoginComp { * class LoginComp {
* loginForm: ControlGroup; * loginForm: ControlGroup;
@ -49,12 +45,12 @@ import * as modelModule from './model';
* } * }
* } * }
* *
* bootstrap(LoginComp) * bootstrap(LoginComp);
* ``` * ```
* *
* This example creates a {@link ControlGroup} that consists of a `login` {@link Control}, and a * This example creates a {@link ControlGroup} that consists of a `login` {@link Control}, and a
* nested * nested {@link ControlGroup} that defines a `password` and a `passwordConfirmation`
* {@link ControlGroup} that defines a `password` and a `passwordConfirmation` {@link Control}: * {@link Control}:
* *
* ``` * ```
* var loginForm = builder.group({ * var loginForm = builder.group({

View File

@ -1,4 +1,4 @@
import {StringWrapper, isPresent, isBlank} from 'angular2/src/core/facade/lang'; import {StringWrapper, isPresent, isBlank, normalizeBool} from 'angular2/src/core/facade/lang';
import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/core/facade/async'; import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/core/facade/async';
import {StringMap, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; import {StringMap, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
import {Validators} from './validators'; import {Validators} from './validators';
@ -13,14 +13,15 @@ export const VALID = "VALID";
*/ */
export const INVALID = "INVALID"; export const INVALID = "INVALID";
export function isControl(c: Object): boolean { export function isControl(control: Object): boolean {
return c instanceof AbstractControl; return control instanceof AbstractControl;
} }
function _find(c: AbstractControl, path: Array<string | number>| string) { function _find(control: AbstractControl, path: Array<string | number>| string) {
if (isBlank(path)) return null; if (isBlank(path)) return null;
if (!(path instanceof Array)) { if (!(path instanceof Array)) {
path = StringWrapper.split(<string>path, new RegExp("/")); path = (<string>path).split("/");
} }
if (path instanceof Array && ListWrapper.isEmpty(path)) return null; if (path instanceof Array && ListWrapper.isEmpty(path)) return null;
@ -33,7 +34,7 @@ function _find(c: AbstractControl, path: Array<string | number>| string) {
} else { } else {
return null; return null;
} }
}, c); }, control);
} }
/** /**
@ -43,18 +44,13 @@ export class AbstractControl {
_value: any; _value: any;
_status: string; _status: string;
_errors: StringMap<string, any>; _errors: StringMap<string, any>;
_pristine: boolean; _pristine: boolean = true;
_touched: boolean; _touched: boolean = false;
_parent: ControlGroup | ControlArray; _parent: ControlGroup | ControlArray;
validator: Function;
_valueChanges: EventEmitter; _valueChanges: EventEmitter;
constructor(validator: Function) { constructor(public validator: Function) {}
this.validator = validator;
this._pristine = true;
this._touched = false;
}
get value(): any { return this._value; } get value(): any { return this._value; }
@ -77,18 +73,18 @@ export class AbstractControl {
markAsTouched(): void { this._touched = true; } markAsTouched(): void { this._touched = true; }
markAsDirty({onlySelf}: {onlySelf?: boolean} = {}): void { markAsDirty({onlySelf}: {onlySelf?: boolean} = {}): void {
onlySelf = isPresent(onlySelf) ? onlySelf : false; onlySelf = normalizeBool(onlySelf);
this._pristine = false; this._pristine = false;
if (isPresent(this._parent) && !onlySelf) { if (isPresent(this._parent) && !onlySelf) {
this._parent.markAsDirty({onlySelf: onlySelf}); this._parent.markAsDirty({onlySelf: onlySelf});
} }
} }
setParent(parent: ControlGroup | ControlArray) { this._parent = parent; } setParent(parent: ControlGroup | ControlArray): void { this._parent = parent; }
updateValidity({onlySelf}: {onlySelf?: boolean} = {}): void { updateValidity({onlySelf}: {onlySelf?: boolean} = {}): void {
onlySelf = isPresent(onlySelf) ? onlySelf : false; onlySelf = normalizeBool(onlySelf);
this._errors = this.validator(this); this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID; this._status = isPresent(this._errors) ? INVALID : VALID;
@ -100,7 +96,7 @@ export class AbstractControl {
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void { void {
onlySelf = isPresent(onlySelf) ? onlySelf : false; onlySelf = normalizeBool(onlySelf);
emitEvent = isPresent(emitEvent) ? emitEvent : true; emitEvent = isPresent(emitEvent) ? emitEvent : true;
this._updateValue(); this._updateValue();
@ -111,6 +107,7 @@ export class AbstractControl {
this._errors = this.validator(this); this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID; this._status = isPresent(this._errors) ? INVALID : VALID;
if (isPresent(this._parent) && !onlySelf) { if (isPresent(this._parent) && !onlySelf) {
this._parent.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent}); this._parent.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
} }
@ -119,9 +116,9 @@ export class AbstractControl {
find(path: Array<string | number>| string): AbstractControl { return _find(this, path); } find(path: Array<string | number>| string): AbstractControl { return _find(this, path); }
getError(errorCode: string, path: string[] = null): any { getError(errorCode: string, path: string[] = null): any {
var c = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this; var control = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this;
if (isPresent(c) && isPresent(c._errors)) { if (isPresent(control) && isPresent(control._errors)) {
return StringMapWrapper.get(c._errors, errorCode); return StringMapWrapper.get(control._errors, errorCode);
} else { } else {
return null; return null;
} }
@ -138,8 +135,7 @@ export class AbstractControl {
* Defines a part of a form that cannot be divided into other controls. * Defines a part of a form that cannot be divided into other controls.
* *
* `Control` is one of the three fundamental building blocks used to define forms in Angular, along * `Control` is one of the three fundamental building blocks used to define forms in Angular, along
* with * with {@link ControlGroup} and {@link ControlArray}.
* {@link ControlGroup} and {@link ControlArray}.
*/ */
export class Control extends AbstractControl { export class Control extends AbstractControl {
_onChange: Function; _onChange: Function;
@ -167,29 +163,22 @@ export class Control extends AbstractControl {
/** /**
* Defines a part of a form, of fixed length, that can contain other controls. * Defines a part of a form, of fixed length, that can contain other controls.
* *
* A ControlGroup aggregates the values and errors of each {@link Control} in the group. Thus, if * A `ControlGroup` aggregates the values and errors of each {@link Control} in the group. Thus, if
* one of the controls * one of the controls in a group is invalid, the entire group is invalid. Similarly, if a control
* in a group is invalid, the entire group is invalid. Similarly, if a control changes its value, * changes its value, the entire group changes as well.
* the entire group
* changes as well.
* *
* `ControlGroup` is one of the three fundamental building blocks used to define forms in Angular, * `ControlGroup` is one of the three fundamental building blocks used to define forms in Angular,
* along with * along with {@link Control} and {@link ControlArray}. {@link ControlArray} can also contain other
* {@link Control} and {@link ControlArray}. {@link ControlArray} can also contain other controls, * controls, but is of variable length.
* but is of variable
* length.
*/ */
export class ControlGroup extends AbstractControl { export class ControlGroup extends AbstractControl {
controls: StringMap<string, AbstractControl>; private _optionals: StringMap<string, boolean>;
_optionals: StringMap<string, boolean>;
constructor(controls: StringMap<string, AbstractControl>, constructor(public controls: StringMap<string, AbstractControl>,
optionals: StringMap<string, boolean> = null, optionals: StringMap<string, boolean> = null,
validator: Function = Validators.group) { validator: Function = Validators.group) {
super(validator); super(validator);
this.controls = controls;
this._optionals = isPresent(optionals) ? optionals : {}; this._optionals = isPresent(optionals) ? optionals : {};
this._valueChanges = new EventEmitter(); this._valueChanges = new EventEmitter();
this._setParentForControls(); this._setParentForControls();
@ -197,12 +186,12 @@ export class ControlGroup extends AbstractControl {
this.updateValidity({onlySelf: true}); this.updateValidity({onlySelf: true});
} }
addControl(name: string, c: AbstractControl) { addControl(name: string, control: AbstractControl): void {
this.controls[name] = c; this.controls[name] = control;
c.setParent(this); control.setParent(this);
} }
removeControl(name: string) { StringMapWrapper.delete(this.controls, name); } removeControl(name: string): void { StringMapWrapper.delete(this.controls, name); }
include(controlName: string): void { include(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, true); StringMapWrapper.set(this._optionals, controlName, true);
@ -252,21 +241,16 @@ export class ControlGroup extends AbstractControl {
* Defines a part of a form, of variable length, that can contain other controls. * Defines a part of a form, of variable length, that can contain other controls.
* *
* A `ControlArray` aggregates the values and errors of each {@link Control} in the group. Thus, if * A `ControlArray` aggregates the values and errors of each {@link Control} in the group. Thus, if
* one of the controls * one of the controls in a group is invalid, the entire group is invalid. Similarly, if a control
* in a group is invalid, the entire group is invalid. Similarly, if a control changes its value, * changes its value, the entire group changes as well.
* the entire group
* changes as well.
* *
* `ControlArray` is one of the three fundamental building blocks used to define forms in Angular, * `ControlArray` is one of the three fundamental building blocks used to define forms in Angular,
* along with {@link Control} and {@link ControlGroup}. {@link ControlGroup} can also contain * along with {@link Control} and {@link ControlGroup}. {@link ControlGroup} can also contain
* other controls, but is of fixed length. * other controls, but is of fixed length.
*/ */
export class ControlArray extends AbstractControl { export class ControlArray extends AbstractControl {
controls: AbstractControl[]; constructor(public controls: AbstractControl[], validator: Function = Validators.array) {
constructor(controls: AbstractControl[], validator: Function = Validators.array) {
super(validator); super(validator);
this.controls = controls;
this._valueChanges = new EventEmitter(); this._valueChanges = new EventEmitter();
@ -296,9 +280,9 @@ export class ControlArray extends AbstractControl {
get length(): number { return this.controls.length; } get length(): number { return this.controls.length; }
_updateValue() { this._value = ListWrapper.map(this.controls, (c) => c.value); } _updateValue(): void { this._value = this.controls.map((control) => control.value); }
_setParentForControls() { _setParentForControls(): void {
ListWrapper.forEach(this.controls, (control) => { control.setParent(this); }); this.controls.forEach((control) => { control.setParent(this); });
} }
} }

View File

@ -17,8 +17,8 @@ export const NG_VALIDATORS: OpaqueToken = CONST_EXPR(new OpaqueToken("NgValidato
* ``` * ```
*/ */
export class Validators { export class Validators {
static required(c: modelModule.Control): StringMap<string, boolean> { static required(control: modelModule.Control): StringMap<string, boolean> {
return isBlank(c.value) || c.value == "" ? {"required": true} : null; return isBlank(control.value) || control.value == "" ? {"required": true} : null;
} }
static nullValidator(c: any): StringMap<string, boolean> { return null; } static nullValidator(c: any): StringMap<string, boolean> { return null; }
@ -26,28 +26,28 @@ export class Validators {
static compose(validators: Function[]): Function { static compose(validators: Function[]): Function {
if (isBlank(validators)) return Validators.nullValidator; if (isBlank(validators)) return Validators.nullValidator;
return function(c: modelModule.Control) { return function(control: modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => { var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c); var errors = validator(control);
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
}, {}); }, {});
return StringMapWrapper.isEmpty(res) ? null : res; return StringMapWrapper.isEmpty(res) ? null : res;
}; };
} }
static group(c: modelModule.ControlGroup): StringMap<string, boolean> { static group(group: modelModule.ControlGroup): StringMap<string, boolean> {
var res = {}; var res = {};
StringMapWrapper.forEach(c.controls, (control, name) => { StringMapWrapper.forEach(group.controls, (control, name) => {
if (c.contains(name) && isPresent(control.errors)) { if (group.contains(name) && isPresent(control.errors)) {
Validators._mergeErrors(control, res); Validators._mergeErrors(control, res);
} }
}); });
return StringMapWrapper.isEmpty(res) ? null : res; return StringMapWrapper.isEmpty(res) ? null : res;
} }
static array(c: modelModule.ControlArray): StringMap<string, boolean> { static array(array: modelModule.ControlArray): StringMap<string, boolean> {
var res = {}; var res = {};
ListWrapper.forEach(c.controls, (control) => { array.controls.forEach((control) => {
if (isPresent(control.errors)) { if (isPresent(control.errors)) {
Validators._mergeErrors(control, res); Validators._mergeErrors(control, res);
} }

View File

@ -19,7 +19,7 @@ import {RegExpWrapper, print, isPresent} from 'angular2/src/core/facade/lang';
* Custom validator. * Custom validator.
*/ */
function creditCardValidator(c): StringMap<string, boolean> { function creditCardValidator(c): StringMap<string, boolean> {
if (isPresent(c.value) && RegExpWrapper.test(new RegExp("^\\d{16}$"), c.value)) { if (isPresent(c.value) && RegExpWrapper.test(/^\d{16}$/g, c.value)) {
return null; return null;
} else { } else {
return {"invalidCreditCard": true}; return {"invalidCreditCard": true};
@ -55,17 +55,19 @@ class ShowError {
constructor(@Host() formDir: NgFormModel) { this.formDir = formDir; } constructor(@Host() formDir: NgFormModel) { this.formDir = formDir; }
get errorMessage() { get errorMessage(): string {
var c = this.formDir.form.find(this.controlPath); var control = this.formDir.form.find(this.controlPath);
for (var i = 0; i < this.errorTypes.length; ++i) { if (isPresent(control) && control.touched) {
if (isPresent(c) && c.touched && c.hasError(this.errorTypes[i])) { for (var i = 0; i < this.errorTypes.length; ++i) {
return this._errorMessage(this.errorTypes[i]); if (control.hasError(this.errorTypes[i])) {
return this._errorMessage(this.errorTypes[i]);
}
} }
} }
return null; return null;
} }
_errorMessage(code) { _errorMessage(code: string): string {
var config = {'required': 'is required', 'invalidCreditCard': 'is invalid credit card number'}; var config = {'required': 'is required', 'invalidCreditCard': 'is invalid credit card number'};
return config[code]; return config[code];
} }
@ -148,7 +150,7 @@ class ModelDrivenForms {
}); });
} }
onSubmit() { onSubmit(): void {
print("Submitting:"); print("Submitting:");
print(this.form.value); print(this.form.value);
} }

View File

@ -36,7 +36,7 @@ class CheckoutModel {
* Custom validator. * Custom validator.
*/ */
function creditCardValidator(c): StringMap<string, boolean> { function creditCardValidator(c): StringMap<string, boolean> {
if (isPresent(c.value) && RegExpWrapper.test(new RegExp("^\\d{16}$"), c.value)) { if (isPresent(c.value) && RegExpWrapper.test(/^\d{16}$/g, c.value)) {
return null; return null;
} else { } else {
return {"invalidCreditCard": true}; return {"invalidCreditCard": true};
@ -79,17 +79,19 @@ class ShowError {
constructor(@Host() formDir: NgForm) { this.formDir = formDir; } constructor(@Host() formDir: NgForm) { this.formDir = formDir; }
get errorMessage() { get errorMessage(): string {
var c = this.formDir.form.find(this.controlPath); var control = this.formDir.form.find(this.controlPath);
for (var i = 0; i < this.errorTypes.length; ++i) { if (isPresent(control) && control.touched) {
if (isPresent(c) && c.touched && c.hasError(this.errorTypes[i])) { for (var i = 0; i < this.errorTypes.length; ++i) {
return this._errorMessage(this.errorTypes[i]); if (control.hasError(this.errorTypes[i])) {
return this._errorMessage(this.errorTypes[i]);
}
} }
} }
return null; return null;
} }
_errorMessage(code) { _errorMessage(code: string): string {
var config = {'required': 'is required', 'invalidCreditCard': 'is invalid credit card number'}; var config = {'required': 'is required', 'invalidCreditCard': 'is invalid credit card number'};
return config[code]; return config[code];
} }
@ -159,7 +161,7 @@ class TemplateDrivenForms {
model = new CheckoutModel(); model = new CheckoutModel();
countries = ['US', 'Canada']; countries = ['US', 'Canada'];
onSubmit() { onSubmit(): void {
print("Submitting:"); print("Submitting:");
print(this.model); print(this.model);
} }