284 lines
7.2 KiB
JavaScript
Raw Normal View History

import {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";
2015-02-24 11:59:10 -08:00
//interface IControl {
// get value():any;
// validator:Function;
// get status():string;
2015-02-25 12:54:27 -08:00
// get valid():boolean;
2015-02-24 11:59:10 -08:00
// get errors():Map;
2015-03-19 10:51:49 -07:00
// get pristine():boolean;
// get dirty():boolean;
2015-02-24 11:59:10 -08:00
// updateValue(value:any){}
// setParent(parent){}
//}
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.
*/
class AbstractControl {
2015-02-24 11:59:10 -08:00
_value:any;
_status:string;
_errors:StringMap;
2015-03-19 10:51:49 -07:00
_pristine:boolean;
_parent:any; /* ControlGroup | ControlArray */
2015-02-24 11:59:10 -08:00
validator:Function;
_valueChanges:EventEmitter;
constructor(validator:Function) {
this.validator = validator;
2015-03-19 10:51:49 -07:00
this._pristine = true;
}
get value():any {
2015-02-24 11:59:10 -08:00
return this._value;
}
get status():string {
2015-02-24 11:59:10 -08:00
return this._status;
}
get valid():boolean {
2015-02-24 11:59:10 -08:00
return this._status === VALID;
}
get errors():StringMap {
2015-02-24 11:59:10 -08:00
return this._errors;
}
get pristine():boolean {
2015-03-19 10:51:49 -07:00
return this._pristine;
}
get dirty():boolean {
2015-03-19 10:51:49 -07:00
return ! this.pristine;
}
get valueChanges():Observable {
return this._valueChanges;
}
2015-02-24 11:59:10 -08:00
setParent(parent){
this._parent = parent;
}
_updateParent() {
if (isPresent(this._parent)){
this._parent._updateValue();
}
}
}
/**
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 {
constructor(value:any, validator:Function = Validators.nullValidator) {
super(validator);
this._setValueErrorsStatus(value);
this._valueChanges = new EventEmitter();
}
updateValue(value:any):void {
this._setValueErrorsStatus(value);
2015-03-19 10:51:49 -07:00
this._pristine = false;
ObservableWrapper.callNext(this._valueChanges, this._value);
this._updateParent();
2015-02-24 11:59:10 -08:00
}
_setValueErrorsStatus(value) {
this._value = value;
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
}
}
/**
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;
_optionals:StringMap;
2015-02-24 11:59:10 -08:00
constructor(controls:StringMap, optionals:StringMap = null, validator:Function = Validators.group) {
super(validator);
this.controls = controls;
this._optionals = isPresent(optionals) ? optionals : {};
this._valueChanges = new EventEmitter();
this._setParentForControls();
this._setValueErrorsStatus();
}
include(controlName:string):void {
StringMapWrapper.set(this._optionals, controlName, true);
this._updateValue();
}
exclude(controlName:string):void {
StringMapWrapper.set(this._optionals, controlName, false);
this._updateValue();
}
contains(controlName:string):boolean {
var c = StringMapWrapper.contains(this.controls, controlName);
return c && this._included(controlName);
}
_setParentForControls() {
StringMapWrapper.forEach(this.controls, (control, name) => {
2015-02-24 11:59:10 -08:00
control.setParent(this);
});
}
_updateValue() {
this._setValueErrorsStatus();
this._pristine = false;
ObservableWrapper.callNext(this._valueChanges, this._value);
this._updateParent();
}
_setValueErrorsStatus() {
this._value = this._reduceValue();
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
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;
constructor(controls:List<AbstractControl>, validator:Function = Validators.array) {
super(validator);
this.controls = controls;
this._valueChanges = new EventEmitter();
this._setParentForControls();
this._setValueErrorsStatus();
}
at(index:number):AbstractControl {
return this.controls[index];
}
push(control:AbstractControl):void {
ListWrapper.push(this.controls, control);
control.setParent(this);
this._updateValue();
}
insert(index:number, control:AbstractControl):void {
ListWrapper.insert(this.controls, index, control);
control.setParent(this);
this._updateValue();
}
removeAt(index:number):void {
ListWrapper.removeAt(this.controls, index);
this._updateValue();
}
get length():number {
return this.controls.length;
}
_updateValue() {
this._setValueErrorsStatus();
this._pristine = false;
ObservableWrapper.callNext(this._valueChanges, this._value);
this._updateParent();
}
_setParentForControls() {
ListWrapper.forEach(this.controls, (control) => {
control.setParent(this);
});
}
_setValueErrorsStatus() {
this._value = ListWrapper.map(this.controls, (c) => c.value);
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
}
}