273 lines
8.2 KiB
TypeScript
Raw Normal View History

import {StringWrapper, isPresent} from 'angular2/src/facade/lang';
import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {StringMap, StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {Validators} from './validators';
/**
2015-04-10 11:15:01 -07:00
* Indicates that a Control is valid, i.e. that no errors exist in the input value.
*
* @exportedAs angular2/forms
*/
export const VALID = "VALID";
/**
2015-04-10 11:15:01 -07:00
* Indicates that a Control is invalid, i.e. that an error exists in the input value.
*
* @exportedAs angular2/forms
*/
export const INVALID = "INVALID";
export function isControl(c: Object): boolean {
return c instanceof AbstractControl;
}
/**
2015-04-10 11:15:01 -07:00
* Omitting from external API doc as this is really an abstract internal concept.
*/
export class AbstractControl {
_value: any;
_status: string;
_errors: StringMap<string, any>;
_pristine: boolean;
_parent: any; /* ControlGroup | ControlArray */
validator: Function;
_valueChanges: EventEmitter;
constructor(validator: Function) {
this.validator = validator;
2015-03-19 10:51:49 -07:00
this._pristine = true;
}
get value(): any { return this._value; }
2015-02-24 11:59:10 -08:00
get status(): string { return this._status; }
2015-02-24 11:59:10 -08:00
get valid(): boolean { return this._status === VALID; }
2015-02-24 11:59:10 -08:00
get errors(): StringMap<string, any> { return this._errors; }
2015-02-24 11:59:10 -08:00
get pristine(): boolean { return this._pristine; }
2015-03-19 10:51:49 -07:00
get dirty(): boolean { return !this.pristine; }
2015-03-19 10:51:49 -07:00
get valueChanges(): Observable { return this._valueChanges; }
setParent(parent) { this._parent = parent; }
updateValidity({onlySelf}: {onlySelf?: boolean} = {}): void {
onlySelf = isPresent(onlySelf) ? onlySelf : false;
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
if (isPresent(this._parent) && !onlySelf) {
this._parent.updateValidity({onlySelf: onlySelf});
}
}
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean,
emitEvent?: boolean} = {}): void {
onlySelf = isPresent(onlySelf) ? onlySelf : false;
emitEvent = isPresent(emitEvent) ? emitEvent : true;
this._updateValue();
this._pristine = false;
if (emitEvent) {
ObservableWrapper.callNext(this._valueChanges, this._value);
}
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
if (isPresent(this._parent) && !onlySelf) {
this._parent.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
}
}
_updateValue(): void {}
}
/**
2015-04-10 11:15:01 -07:00
* 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
* with
* {@link ControlGroup} and {@link ControlArray}.
2015-04-10 11:15:01 -07:00
*
* @exportedAs angular2/forms
*/
export class Control extends AbstractControl {
_onChange: Function;
constructor(value: any, validator: Function = Validators.nullValidator) {
super(validator);
this._value = value;
this.updateValidity({onlySelf: true});
this._valueChanges = new EventEmitter();
}
updateValue(value: any,
{onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._value = value;
if (isPresent(this._onChange)) this._onChange(this._value);
this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
}
registerOnChange(fn: Function): void { this._onChange = fn; }
}
/**
2015-04-10 11:15:01 -07:00
* 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
* one of the controls
* in a group is invalid, the entire group is invalid. Similarly, if a control changes its value,
* the entire group
* changes as well.
2015-04-10 11:15:01 -07:00
*
* `ControlGroup` is one of the three fundamental building blocks used to define forms in Angular,
* along with
* {@link Control} and {@link ControlArray}. {@link ControlArray} can also contain other controls,
* but is of variable
* length.
2015-04-10 11:15:01 -07:00
*
* @exportedAs angular2/forms
*/
export class ControlGroup extends AbstractControl {
controls: StringMap<string, AbstractControl>;
_optionals: StringMap<string, boolean>;
2015-02-24 11:59:10 -08:00
constructor(controls: StringMap<String, AbstractControl>,
optionals: StringMap<String, boolean> = null,
validator: Function = Validators.group) {
super(validator);
this.controls = controls;
this._optionals = isPresent(optionals) ? optionals : {};
this._valueChanges = new EventEmitter();
this._setParentForControls();
this._value = this._reduceValue();
this.updateValidity({onlySelf: true});
}
addControl(name: string, c: AbstractControl) { this.controls[name] = c; }
removeControl(name: string) { StringMapWrapper.delete(this.controls, name); }
include(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, true);
this.updateValueAndValidity();
}
exclude(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, false);
this.updateValueAndValidity();
}
contains(controlName: string): boolean {
var c = StringMapWrapper.contains(this.controls, controlName);
return c && this._included(controlName);
}
find(path: string | List<string>): AbstractControl {
if (!(path instanceof List)) {
path = StringWrapper.split(<string>path, new RegExp("/"));
}
return ListWrapper.reduce(
<List<string>>path, (v, name) => v instanceof ControlGroup && isPresent(v.controls[name]) ?
v.controls[name] :
null,
this);
}
_setParentForControls() {
StringMapWrapper.forEach(this.controls, (control, name) => { control.setParent(this); });
}
_updateValue() { this._value = this._reduceValue(); }
2015-02-24 11:59:10 -08:00
_reduceValue() {
2015-03-19 10:51:49 -07:00
return this._reduceChildren({}, (acc, control, name) => {
acc[name] = control.value;
return acc;
});
}
_reduceChildren(initValue: any, fn: Function) {
2015-03-19 10:51:49 -07:00
var res = initValue;
2015-02-24 11:59:10 -08:00
StringMapWrapper.forEach(this.controls, (control, name) => {
if (this._included(name)) {
2015-03-19 10:51:49 -07:00
res = fn(res, control, name);
2015-02-24 11:59:10 -08:00
}
});
2015-03-19 10:51:49 -07:00
return res;
}
_included(controlName: string): boolean {
var isOptional = StringMapWrapper.contains(this._optionals, controlName);
return !isOptional || StringMapWrapper.get(this._optionals, controlName);
2015-02-24 11:59:10 -08:00
}
}
/**
2015-04-10 11:15:01 -07:00
* 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
* one of the controls
* in a group is invalid, the entire group is invalid. Similarly, if a control changes its value,
* the entire group
* changes as well.
2015-04-10 11:15:01 -07:00
*
* `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 other controls,
* but is of fixed
* length.
2015-04-10 11:15:01 -07:00
*
* @exportedAs angular2/forms
*/
export class ControlArray extends AbstractControl {
controls: List<AbstractControl>;
constructor(controls: List<AbstractControl>, validator: Function = Validators.array) {
super(validator);
this.controls = controls;
this._valueChanges = new EventEmitter();
this._setParentForControls();
this._updateValue();
this.updateValidity({onlySelf: true});
}
at(index: number): AbstractControl { return this.controls[index]; }
push(control: AbstractControl): void {
ListWrapper.push(this.controls, control);
control.setParent(this);
this.updateValueAndValidity();
}
insert(index: number, control: AbstractControl): void {
ListWrapper.insert(this.controls, index, control);
control.setParent(this);
this.updateValueAndValidity();
}
removeAt(index: number): void {
ListWrapper.removeAt(this.controls, index);
this.updateValueAndValidity();
}
get length(): number { return this.controls.length; }
_updateValue() { this._value = ListWrapper.map(this.controls, (c) => c.value); }
_setParentForControls() {
ListWrapper.forEach(this.controls, (control) => { control.setParent(this); });
}
}