2015-06-05 14:28:19 -07:00
|
|
|
import {StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang';
|
2015-04-14 14:34:41 -07:00
|
|
|
import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
2015-03-25 10:51:05 -07:00
|
|
|
import {StringMap, StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
|
2015-03-19 14:21:40 -07:00
|
|
|
import {Validators} from './validators';
|
2015-02-11 11:10:31 -08:00
|
|
|
|
2015-03-31 22:47:11 +00:00
|
|
|
/**
|
2015-04-10 11:15:01 -07:00
|
|
|
* Indicates that a Control is valid, i.e. that no errors exist in the input value.
|
2015-03-31 22:47:11 +00:00
|
|
|
*/
|
2015-02-11 11:10:31 -08:00
|
|
|
export const VALID = "VALID";
|
2015-03-31 22:47:11 +00:00
|
|
|
|
|
|
|
/**
|
2015-04-10 11:15:01 -07:00
|
|
|
* Indicates that a Control is invalid, i.e. that an error exists in the input value.
|
2015-03-31 22:47:11 +00:00
|
|
|
*/
|
2015-02-11 11:10:31 -08:00
|
|
|
export const INVALID = "INVALID";
|
2015-02-03 07:27:09 -08:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
export function isControl(c: Object): boolean {
|
2015-05-11 12:00:56 -07:00
|
|
|
return c instanceof AbstractControl;
|
|
|
|
}
|
|
|
|
|
2015-06-05 14:28:19 -07:00
|
|
|
function _find(c: AbstractControl, path: List<string | number>| string) {
|
|
|
|
if (isBlank(path)) return null;
|
|
|
|
if (!(path instanceof List)) {
|
|
|
|
path = StringWrapper.split(<string>path, new RegExp("/"));
|
|
|
|
}
|
|
|
|
if (ListWrapper.isEmpty(path)) return null;
|
|
|
|
|
|
|
|
return ListWrapper.reduce(<List<string | number>>path, (v, name) => {
|
|
|
|
if (v instanceof ControlGroup) {
|
|
|
|
return isPresent(v.controls[name]) ? v.controls[name] : null;
|
|
|
|
} else if (v instanceof ControlArray) {
|
|
|
|
var index = <number>name;
|
|
|
|
return isPresent(v.at(index)) ? v.at(index) : null;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}, c);
|
|
|
|
}
|
2015-05-11 12:00:56 -07:00
|
|
|
|
2015-03-31 22:47:11 +00:00
|
|
|
/**
|
2015-04-10 11:15:01 -07:00
|
|
|
* Omitting from external API doc as this is really an abstract internal concept.
|
2015-03-31 22:47:11 +00:00
|
|
|
*/
|
2015-05-20 18:10:30 -07:00
|
|
|
export class AbstractControl {
|
|
|
|
_value: any;
|
|
|
|
_status: string;
|
|
|
|
_errors: StringMap<string, any>;
|
|
|
|
_pristine: boolean;
|
2015-06-02 08:41:33 -07:00
|
|
|
_touched: boolean;
|
2015-06-05 14:28:19 -07:00
|
|
|
_parent: ControlGroup | ControlArray;
|
2015-05-20 18:10:30 -07:00
|
|
|
validator: Function;
|
2015-02-03 07:27:09 -08:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
_valueChanges: EventEmitter;
|
2015-03-25 10:51:05 -07:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
constructor(validator: Function) {
|
2015-02-11 11:10:31 -08:00
|
|
|
this.validator = validator;
|
2015-03-19 10:51:49 -07:00
|
|
|
this._pristine = true;
|
2015-06-02 08:41:33 -07:00
|
|
|
this._touched = false;
|
2015-02-11 11:10:31 -08:00
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
get value(): any { return this._value; }
|
2015-02-24 11:59:10 -08:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
get status(): string { return this._status; }
|
2015-02-24 11:59:10 -08:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
get valid(): boolean { return this._status === VALID; }
|
2015-02-24 11:59:10 -08:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
get errors(): StringMap<string, any> { return this._errors; }
|
2015-02-24 11:59:10 -08:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
get pristine(): boolean { return this._pristine; }
|
2015-03-19 10:51:49 -07:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
get dirty(): boolean { return !this.pristine; }
|
2015-03-19 10:51:49 -07:00
|
|
|
|
2015-06-02 08:41:33 -07:00
|
|
|
get touched(): boolean { return this._touched; }
|
|
|
|
|
|
|
|
get untouched(): boolean { return !this._touched; }
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
get valueChanges(): Observable { return this._valueChanges; }
|
2015-04-14 14:34:41 -07:00
|
|
|
|
2015-06-03 11:54:39 -07:00
|
|
|
markAsTouched(): void { this._touched = true; }
|
|
|
|
|
|
|
|
markAsDirty({onlySelf}: {onlySelf?: boolean} = {}): void {
|
|
|
|
onlySelf = isPresent(onlySelf) ? onlySelf : false;
|
|
|
|
|
|
|
|
this._pristine = false;
|
|
|
|
if (isPresent(this._parent) && !onlySelf) {
|
|
|
|
this._parent.markAsDirty({onlySelf: onlySelf});
|
|
|
|
}
|
|
|
|
}
|
2015-06-02 08:41:33 -07:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
setParent(parent) { this._parent = parent; }
|
2015-02-11 11:10:31 -08:00
|
|
|
|
2015-06-01 10:41:50 -07:00
|
|
|
updateValidity({onlySelf}: {onlySelf?: boolean} = {}): void {
|
|
|
|
onlySelf = isPresent(onlySelf) ? onlySelf : false;
|
|
|
|
|
|
|
|
this._errors = this.validator(this);
|
|
|
|
this._status = isPresent(this._errors) ? INVALID : VALID;
|
2015-06-05 14:28:19 -07:00
|
|
|
|
2015-06-01 10:41:50 -07:00
|
|
|
if (isPresent(this._parent) && !onlySelf) {
|
|
|
|
this._parent.updateValidity({onlySelf: onlySelf});
|
2015-02-11 11:10:31 -08:00
|
|
|
}
|
2015-02-03 07:27:09 -08:00
|
|
|
}
|
2015-05-30 11:56:00 -07:00
|
|
|
|
2015-07-10 11:29:41 +02:00
|
|
|
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
|
|
|
void {
|
2015-06-01 10:41:50 -07:00
|
|
|
onlySelf = isPresent(onlySelf) ? onlySelf : false;
|
|
|
|
emitEvent = isPresent(emitEvent) ? emitEvent : true;
|
|
|
|
|
|
|
|
this._updateValue();
|
|
|
|
|
|
|
|
if (emitEvent) {
|
|
|
|
ObservableWrapper.callNext(this._valueChanges, this._value);
|
|
|
|
}
|
|
|
|
|
2015-05-30 11:56:00 -07:00
|
|
|
this._errors = this.validator(this);
|
|
|
|
this._status = isPresent(this._errors) ? INVALID : VALID;
|
2015-06-01 10:41:50 -07:00
|
|
|
if (isPresent(this._parent) && !onlySelf) {
|
|
|
|
this._parent.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
|
2015-05-30 11:56:00 -07:00
|
|
|
}
|
|
|
|
}
|
2015-06-01 10:41:50 -07:00
|
|
|
|
2015-06-05 14:28:19 -07:00
|
|
|
find(path: List<string | number>| string): AbstractControl { return _find(this, path); }
|
|
|
|
|
2015-06-26 11:10:52 -07:00
|
|
|
getError(errorCode: string, path: List<string> = null): any {
|
2015-06-09 16:05:13 -07:00
|
|
|
var c = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this;
|
2015-06-05 14:28:19 -07:00
|
|
|
if (isPresent(c) && isPresent(c._errors)) {
|
|
|
|
return StringMapWrapper.get(c._errors, errorCode);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-26 11:10:52 -07:00
|
|
|
hasError(errorCode: string, path: List<string> = null): boolean {
|
2015-06-05 14:28:19 -07:00
|
|
|
return isPresent(this.getError(errorCode, path));
|
|
|
|
}
|
|
|
|
|
2015-06-01 10:41:50 -07:00
|
|
|
_updateValue(): void {}
|
2015-02-03 07:27:09 -08:00
|
|
|
}
|
|
|
|
|
2015-03-31 22:47:11 +00:00
|
|
|
/**
|
2015-04-10 11:15:01 -07:00
|
|
|
* Defines a part of a form that cannot be divided into other controls.
|
|
|
|
*
|
2015-05-20 18:10:30 -07:00
|
|
|
* `Control` is one of the three fundamental building blocks used to define forms in Angular, along
|
|
|
|
* with
|
2015-04-17 13:01:07 -07:00
|
|
|
* {@link ControlGroup} and {@link ControlArray}.
|
2015-03-31 22:47:11 +00:00
|
|
|
*/
|
2015-02-25 15:10:27 -08:00
|
|
|
export class Control extends AbstractControl {
|
2015-06-01 10:41:50 -07:00
|
|
|
_onChange: Function;
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
constructor(value: any, validator: Function = Validators.nullValidator) {
|
2015-02-25 15:10:27 -08:00
|
|
|
super(validator);
|
2015-06-01 10:41:50 -07:00
|
|
|
this._value = value;
|
|
|
|
this.updateValidity({onlySelf: true});
|
2015-04-14 14:34:41 -07:00
|
|
|
this._valueChanges = new EventEmitter();
|
2015-02-03 07:27:09 -08:00
|
|
|
}
|
|
|
|
|
2015-07-10 11:29:41 +02:00
|
|
|
updateValue(value: any, {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
|
|
|
void {
|
2015-03-24 13:45:47 -07:00
|
|
|
this._value = value;
|
2015-06-01 10:41:50 -07:00
|
|
|
if (isPresent(this._onChange)) this._onChange(this._value);
|
|
|
|
this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
|
2015-02-03 07:27:09 -08:00
|
|
|
}
|
2015-06-01 10:41:50 -07:00
|
|
|
|
|
|
|
registerOnChange(fn: Function): void { this._onChange = fn; }
|
2015-02-25 15:10:27 -08:00
|
|
|
}
|
2015-02-11 11:10:31 -08:00
|
|
|
|
2015-03-31 22:47:11 +00:00
|
|
|
/**
|
2015-04-10 11:15:01 -07:00
|
|
|
* Defines a part of a form, of fixed length, that can contain other controls.
|
|
|
|
*
|
2015-05-20 18:10:30 -07:00
|
|
|
* 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
|
2015-04-17 13:01:07 -07:00
|
|
|
* changes as well.
|
2015-04-10 11:15:01 -07:00
|
|
|
*
|
2015-05-20 18:10:30 -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
|
2015-04-17 13:01:07 -07:00
|
|
|
* length.
|
2015-03-31 22:47:11 +00:00
|
|
|
*/
|
2015-02-25 15:10:27 -08:00
|
|
|
export class ControlGroup extends AbstractControl {
|
2015-05-20 18:10:30 -07:00
|
|
|
controls: StringMap<string, AbstractControl>;
|
|
|
|
_optionals: StringMap<string, boolean>;
|
2015-02-24 11:59:10 -08:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
constructor(controls: StringMap<String, AbstractControl>,
|
|
|
|
optionals: StringMap<String, boolean> = null,
|
|
|
|
validator: Function = Validators.group) {
|
2015-02-25 15:10:27 -08:00
|
|
|
super(validator);
|
|
|
|
this.controls = controls;
|
2015-03-26 19:50:13 -07:00
|
|
|
this._optionals = isPresent(optionals) ? optionals : {};
|
2015-03-24 13:45:47 -07:00
|
|
|
|
2015-04-14 14:34:41 -07:00
|
|
|
this._valueChanges = new EventEmitter();
|
2015-03-24 13:45:47 -07:00
|
|
|
|
2015-02-25 15:10:27 -08:00
|
|
|
this._setParentForControls();
|
2015-06-01 10:41:50 -07:00
|
|
|
this._value = this._reduceValue();
|
|
|
|
this.updateValidity({onlySelf: true});
|
2015-02-11 11:10:31 -08:00
|
|
|
}
|
|
|
|
|
2015-06-05 14:28:19 -07:00
|
|
|
addControl(name: string, c: AbstractControl) {
|
|
|
|
this.controls[name] = c;
|
|
|
|
c.setParent(this);
|
|
|
|
}
|
2015-05-30 11:56:00 -07:00
|
|
|
|
|
|
|
removeControl(name: string) { StringMapWrapper.delete(this.controls, name); }
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
include(controlName: string): void {
|
2015-03-26 19:50:13 -07:00
|
|
|
StringMapWrapper.set(this._optionals, controlName, true);
|
2015-06-01 10:41:50 -07:00
|
|
|
this.updateValueAndValidity();
|
2015-03-10 18:12:30 -07:00
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
exclude(controlName: string): void {
|
2015-03-26 19:50:13 -07:00
|
|
|
StringMapWrapper.set(this._optionals, controlName, false);
|
2015-06-01 10:41:50 -07:00
|
|
|
this.updateValueAndValidity();
|
2015-03-10 18:12:30 -07:00
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
contains(controlName: string): boolean {
|
2015-03-10 18:12:30 -07:00
|
|
|
var c = StringMapWrapper.contains(this.controls, controlName);
|
|
|
|
return c && this._included(controlName);
|
|
|
|
}
|
|
|
|
|
2015-02-11 11:10:31 -08:00
|
|
|
_setParentForControls() {
|
2015-05-20 18:10:30 -07:00
|
|
|
StringMapWrapper.forEach(this.controls, (control, name) => { control.setParent(this); });
|
2015-02-11 11:10:31 -08:00
|
|
|
}
|
|
|
|
|
2015-06-01 10:41:50 -07:00
|
|
|
_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;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
_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) => {
|
2015-03-10 18:12:30 -07:00
|
|
|
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;
|
2015-02-11 11:10:31 -08:00
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
_included(controlName: string): boolean {
|
2015-03-26 19:50:13 -07:00
|
|
|
var isOptional = StringMapWrapper.contains(this._optionals, controlName);
|
|
|
|
return !isOptional || StringMapWrapper.get(this._optionals, controlName);
|
2015-02-24 11:59:10 -08:00
|
|
|
}
|
2015-03-25 10:51:05 -07:00
|
|
|
}
|
|
|
|
|
2015-03-31 22:47:11 +00:00
|
|
|
/**
|
2015-04-10 11:15:01 -07:00
|
|
|
* Defines a part of a form, of variable length, that can contain other controls.
|
|
|
|
*
|
2015-05-20 18:10:30 -07:00
|
|
|
* 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
|
2015-04-17 13:01:07 -07:00
|
|
|
* changes as well.
|
2015-04-10 11:15:01 -07:00
|
|
|
*
|
2015-05-20 18:10:30 -07:00
|
|
|
* `ControlArray` is one of the three fundamental building blocks used to define forms in Angular,
|
2015-07-11 07:21:21 +01:00
|
|
|
* along with {@link Control} and {@link ControlGroup}. {@link ControlGroup} can also contain
|
|
|
|
* other controls, but is of fixed length.
|
2015-03-31 22:47:11 +00:00
|
|
|
*/
|
2015-03-25 10:51:05 -07:00
|
|
|
export class ControlArray extends AbstractControl {
|
2015-05-20 18:10:30 -07:00
|
|
|
controls: List<AbstractControl>;
|
2015-03-25 10:51:05 -07:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
constructor(controls: List<AbstractControl>, validator: Function = Validators.array) {
|
2015-03-25 10:51:05 -07:00
|
|
|
super(validator);
|
|
|
|
this.controls = controls;
|
|
|
|
|
2015-04-14 14:34:41 -07:00
|
|
|
this._valueChanges = new EventEmitter();
|
2015-03-25 10:51:05 -07:00
|
|
|
|
|
|
|
this._setParentForControls();
|
2015-06-01 10:41:50 -07:00
|
|
|
this._updateValue();
|
|
|
|
this.updateValidity({onlySelf: true});
|
2015-03-25 10:51:05 -07:00
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
at(index: number): AbstractControl { return this.controls[index]; }
|
2015-03-25 10:51:05 -07:00
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
push(control: AbstractControl): void {
|
2015-06-17 11:17:21 -07:00
|
|
|
this.controls.push(control);
|
2015-03-25 10:51:05 -07:00
|
|
|
control.setParent(this);
|
2015-06-01 10:41:50 -07:00
|
|
|
this.updateValueAndValidity();
|
2015-03-25 10:51:05 -07:00
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
insert(index: number, control: AbstractControl): void {
|
2015-03-25 10:51:05 -07:00
|
|
|
ListWrapper.insert(this.controls, index, control);
|
|
|
|
control.setParent(this);
|
2015-06-01 10:41:50 -07:00
|
|
|
this.updateValueAndValidity();
|
2015-03-25 10:51:05 -07:00
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
removeAt(index: number): void {
|
2015-03-25 10:51:05 -07:00
|
|
|
ListWrapper.removeAt(this.controls, index);
|
2015-06-01 10:41:50 -07:00
|
|
|
this.updateValueAndValidity();
|
2015-03-25 10:51:05 -07:00
|
|
|
}
|
|
|
|
|
2015-05-20 18:10:30 -07:00
|
|
|
get length(): number { return this.controls.length; }
|
2015-03-25 10:51:05 -07:00
|
|
|
|
2015-06-01 10:41:50 -07:00
|
|
|
_updateValue() { this._value = ListWrapper.map(this.controls, (c) => c.value); }
|
2015-03-25 10:51:05 -07:00
|
|
|
|
|
|
|
_setParentForControls() {
|
2015-05-20 18:10:30 -07:00
|
|
|
ListWrapper.forEach(this.controls, (control) => { control.setParent(this); });
|
2015-03-25 10:51:05 -07:00
|
|
|
}
|
2015-03-24 11:01:26 -04:00
|
|
|
}
|