305 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			305 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {StringWrapper, isPresent, isBlank} from 'angular2/src/core/facade/lang';
 | 
						|
import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/core/facade/async';
 | 
						|
import {StringMap, 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.
 | 
						|
 */
 | 
						|
export const VALID = "VALID";
 | 
						|
 | 
						|
/**
 | 
						|
 * Indicates that a Control is invalid, i.e. that an error exists in the input value.
 | 
						|
 */
 | 
						|
export const INVALID = "INVALID";
 | 
						|
 | 
						|
export function isControl(c: Object): boolean {
 | 
						|
  return c instanceof AbstractControl;
 | 
						|
}
 | 
						|
 | 
						|
function _find(c: AbstractControl, path: Array<string | number>| string) {
 | 
						|
  if (isBlank(path)) return null;
 | 
						|
  if (!(path instanceof Array)) {
 | 
						|
    path = StringWrapper.split(<string>path, new RegExp("/"));
 | 
						|
  }
 | 
						|
  if (path instanceof Array && ListWrapper.isEmpty(path)) return null;
 | 
						|
 | 
						|
  return ListWrapper.reduce(<Array<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);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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;
 | 
						|
  _touched: boolean;
 | 
						|
  _parent: ControlGroup | ControlArray;
 | 
						|
  validator: Function;
 | 
						|
 | 
						|
  _valueChanges: EventEmitter;
 | 
						|
 | 
						|
  constructor(validator: Function) {
 | 
						|
    this.validator = validator;
 | 
						|
    this._pristine = true;
 | 
						|
    this._touched = false;
 | 
						|
  }
 | 
						|
 | 
						|
  get value(): any { return this._value; }
 | 
						|
 | 
						|
  get status(): string { return this._status; }
 | 
						|
 | 
						|
  get valid(): boolean { return this._status === VALID; }
 | 
						|
 | 
						|
  get errors(): StringMap<string, any> { return this._errors; }
 | 
						|
 | 
						|
  get pristine(): boolean { return this._pristine; }
 | 
						|
 | 
						|
  get dirty(): boolean { return !this.pristine; }
 | 
						|
 | 
						|
  get touched(): boolean { return this._touched; }
 | 
						|
 | 
						|
  get untouched(): boolean { return !this._touched; }
 | 
						|
 | 
						|
  get valueChanges(): Observable { return this._valueChanges; }
 | 
						|
 | 
						|
  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});
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  setParent(parent: ControlGroup | ControlArray) { 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();
 | 
						|
 | 
						|
    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});
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  find(path: Array<string | number>| string): AbstractControl { return _find(this, path); }
 | 
						|
 | 
						|
  getError(errorCode: string, path: string[] = null): any {
 | 
						|
    var c = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this;
 | 
						|
    if (isPresent(c) && isPresent(c._errors)) {
 | 
						|
      return StringMapWrapper.get(c._errors, errorCode);
 | 
						|
    } else {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  hasError(errorCode: string, path: string[] = null): boolean {
 | 
						|
    return isPresent(this.getError(errorCode, path));
 | 
						|
  }
 | 
						|
 | 
						|
  _updateValue(): void {}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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}.
 | 
						|
 */
 | 
						|
export class Control extends AbstractControl {
 | 
						|
  _onChange: Function;
 | 
						|
 | 
						|
  constructor(value: any = null, validator: Function = Validators.nullValidator) {
 | 
						|
    super(validator);
 | 
						|
    this._value = value;
 | 
						|
    this.updateValidity({onlySelf: true});
 | 
						|
    this._valueChanges = new EventEmitter();
 | 
						|
  }
 | 
						|
 | 
						|
  updateValue(value: any,
 | 
						|
              {onlySelf, emitEvent, emitModelToViewChange}:
 | 
						|
                  {onlySelf?: boolean, emitEvent?: boolean, emitModelToViewChange?: boolean} = {}):
 | 
						|
      void {
 | 
						|
    emitModelToViewChange = isPresent(emitModelToViewChange) ? emitModelToViewChange : true;
 | 
						|
    this._value = value;
 | 
						|
    if (isPresent(this._onChange) && emitModelToViewChange) this._onChange(this._value);
 | 
						|
    this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
 | 
						|
  }
 | 
						|
 | 
						|
  registerOnChange(fn: Function): void { this._onChange = fn; }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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.
 | 
						|
 *
 | 
						|
 * `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.
 | 
						|
 */
 | 
						|
export class ControlGroup extends AbstractControl {
 | 
						|
  controls: StringMap<string, AbstractControl>;
 | 
						|
  _optionals: StringMap<string, boolean>;
 | 
						|
 | 
						|
  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;
 | 
						|
    c.setParent(this);
 | 
						|
  }
 | 
						|
 | 
						|
  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);
 | 
						|
  }
 | 
						|
 | 
						|
  _setParentForControls() {
 | 
						|
    StringMapWrapper.forEach(this.controls, (control, name) => { control.setParent(this); });
 | 
						|
  }
 | 
						|
 | 
						|
  _updateValue() { this._value = this._reduceValue(); }
 | 
						|
 | 
						|
  _reduceValue() {
 | 
						|
    return this._reduceChildren({}, (acc, control, name) => {
 | 
						|
      acc[name] = control.value;
 | 
						|
      return acc;
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  _reduceChildren(initValue: any, fn: Function) {
 | 
						|
    var res = initValue;
 | 
						|
    StringMapWrapper.forEach(this.controls, (control, name) => {
 | 
						|
      if (this._included(name)) {
 | 
						|
        res = fn(res, control, name);
 | 
						|
      }
 | 
						|
    });
 | 
						|
    return res;
 | 
						|
  }
 | 
						|
 | 
						|
  _included(controlName: string): boolean {
 | 
						|
    var isOptional = StringMapWrapper.contains(this._optionals, controlName);
 | 
						|
    return !isOptional || StringMapWrapper.get(this._optionals, controlName);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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.
 | 
						|
 *
 | 
						|
 * `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.
 | 
						|
 */
 | 
						|
export class ControlArray extends AbstractControl {
 | 
						|
  controls: AbstractControl[];
 | 
						|
 | 
						|
  constructor(controls: 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 {
 | 
						|
    this.controls.push(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); });
 | 
						|
  }
 | 
						|
}
 |