feat(forms): migrated forms to typescript

This commit is contained in:
vsavkin 2015-05-20 18:10:30 -07:00
parent fed86fc8ac
commit 00c3693daa
12 changed files with 254 additions and 287 deletions

View File

@ -2,11 +2,14 @@
* @module * @module
* @public * @public
* @description * @description
* This module is used for handling user input, by defining and building a {@link ControlGroup} that consists of * This module is used for handling user input, by defining and building a {@link ControlGroup} that
* {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used to read information * consists of
* {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used
* to read information
* from the form DOM elements. * from the form DOM elements.
* *
* This module is not included in the `angular2` module; you must import the forms module explicitly. * This module is not included in the `angular2` module; you must import the forms module
* explicitly.
* *
*/ */

View File

@ -1,3 +1,6 @@
library angular2.core.decorators; library angular2.core.decorators;
export '../annotations_impl/annotations.dart';
export '../annotations_impl/visibility.dart';
/* This file is empty because, Dart does not have decorators. */ /* This file is empty because, Dart does not have decorators. */

View File

@ -1,6 +1,11 @@
import {ComponentAnnotation, DirectiveAnnotation} from './annotations'; import {ComponentAnnotation, DirectiveAnnotation} from './annotations';
import {ViewAnnotation} from './view'; import {ViewAnnotation} from './view';
import {AncestorAnnotation, ParentAnnotation} from './visibility'; import {
SelfAnnotation,
ParentAnnotation,
AncestorAnnotation,
UnboundedAnnotation
} from './visibility';
import {AttributeAnnotation, QueryAnnotation} from './di'; import {AttributeAnnotation, QueryAnnotation} from './di';
import {makeDecorator, makeParamDecorator} from '../../util/decorators'; import {makeDecorator, makeParamDecorator} from '../../util/decorators';
@ -12,8 +17,10 @@ export var Directive = makeDecorator(DirectiveAnnotation);
export var View = makeDecorator(ViewAnnotation); export var View = makeDecorator(ViewAnnotation);
/* from visibility */ /* from visibility */
export var Ancestor = makeParamDecorator(AncestorAnnotation); export var Self = makeParamDecorator(SelfAnnotation);
export var Parent = makeParamDecorator(ParentAnnotation); export var Parent = makeParamDecorator(ParentAnnotation);
export var Ancestor = makeParamDecorator(AncestorAnnotation);
export var Unbounded = makeParamDecorator(UnboundedAnnotation);
/* from di */ /* from di */
export var Attribute = makeParamDecorator(AttributeAnnotation); export var Attribute = makeParamDecorator(AttributeAnnotation);

View File

@ -1,4 +1,6 @@
export { export {
Self as SelfAnnotation,
Ancestor as AncestorAnnotation, Ancestor as AncestorAnnotation,
Parent as ParentAnnotation, Parent as ParentAnnotation,
Unbounded as UnboundedAnnotation
} from '../annotations_impl/visibility'; } from '../annotations_impl/visibility';

View File

@ -10,7 +10,6 @@ export class DirectiveResolver {
if (isPresent(annotations)) { if (isPresent(annotations)) {
for (var i = 0; i < annotations.length; i++) { for (var i = 0; i < annotations.length; i++) {
var annotation = annotations[i]; var annotation = annotations[i];
if (annotation instanceof Directive) { if (annotation instanceof Directive) {
return annotation; return annotation;
} }

View File

@ -1,19 +1,20 @@
import {Directive} from 'angular2/src/core/annotations_impl/annotations'; import {Directive, Ancestor} from 'angular2/src/core/annotations/decorators';
import {Ancestor} from 'angular2/src/core/annotations_impl/visibility'; import {Optional} from 'angular2/src/di/decorators';
import {ElementRef} from 'angular2/src/core/compiler/element_ref'; import {ElementRef} from 'angular2/src/core/compiler/element_ref';
import {Optional} from 'angular2/src/di/annotations_impl';
import {Renderer} from 'angular2/src/render/api'; import {Renderer} from 'angular2/src/render/api';
import {isPresent, isString, CONST_EXPR, isBlank, BaseException} from 'angular2/src/facade/lang'; import {
isPresent,
isString,
CONST_EXPR,
isBlank,
BaseException,
Type
} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection'; import {ListWrapper} from 'angular2/src/facade/collection';
import {ControlGroup, Control, isControl} from './model'; import {ControlGroup, Control, isControl} from './model';
import {Validators} from './validators'; import {Validators} from './validators';
//export interface ControlValueAccessor { function _lookupControl(groupDirective: ControlGroupDirective, controlOrName: any): any {
// writeValue(value):void{}
// set onChange(fn){}
//}
function _lookupControl(groupDirective:ControlGroupDirective, controlOrName:any):any {
if (isControl(controlOrName)) { if (isControl(controlOrName)) {
return controlOrName; return controlOrName;
} }
@ -31,8 +32,10 @@ function _lookupControl(groupDirective:ControlGroupDirective, controlOrName:any)
return control; return control;
} }
/** /**
* The default accessor for writing a value and listening to changes that is used by a {@link Control} directive. * The default accessor for writing a value and listening to changes that is used by a {@link
* Control} directive.
* *
* This is the default strategy that Angular uses when no other accessor is applied. * This is the default strategy that Angular uses when no other accessor is applied.
* *
@ -45,143 +48,17 @@ function _lookupControl(groupDirective:ControlGroupDirective, controlOrName:any)
*/ */
@Directive({ @Directive({
selector: '[control]', selector: '[control]',
hostListeners: { hostListeners:
'change' : 'onChange($event.target.value)', {'change': 'onChange($event.target.value)', 'input': 'onChange($event.target.value)'},
'input' : 'onChange($event.target.value)' hostProperties: {'value': 'value'}
},
hostProperties: {
'value' : 'value'
}
}) })
export class DefaultValueAccessor { export class DefaultValueAccessor {
value; value;
onChange:Function; onChange: Function;
constructor() { constructor() { this.onChange = (_) => {}; }
this.onChange = (_) => {};
}
writeValue(value) { writeValue(value) { this.value = value }
this.value = value
}
}
/**
* The accessor for writing a value and listening to changes on a checkbox input element.
*
*
* # Example
* ```
* <input type="checkbox" [control]="rememberLogin">
* ```
*
* @exportedAs angular2/forms
*/
@Directive({
selector: 'input[type=checkbox][control]',
hostListeners: {
'change' : 'onChange($event.target.checked)'
},
hostProperties: {
'checked' : 'checked'
}
})
export class CheckboxControlValueAccessor {
_elementRef:ElementRef;
_renderer:Renderer;
checked:boolean;
onChange:Function;
constructor(cd:ControlDirective, elementRef:ElementRef, renderer:Renderer) {
this.onChange = (_) => {};
this._elementRef = elementRef;
this._renderer = renderer;
cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective
}
writeValue(value) {
this._renderer.setElementProperty(this._elementRef.parentView.render, this._elementRef.boundElementIndex,
'checked', value)
}
}
/**
* Binds a control to a DOM element.
*
* # Example
*
* In this example, we bind the control to an input element. When the value of the input element changes, the value of
* the control will reflect that change. Likewise, if the value of the control changes, the input element reflects that
* change.
*
* Here we use {@link formDirectives}, rather than importing each form directive individually, e.g.
* `ControlDirective`, `ControlGroupDirective`. This is just a shorthand for the same end result.
*
* ```
* @Component({selector: "login-comp"})
* @View({
* directives: [formDirectives],
* inline: "<input type='text' [control]='loginControl'>"
* })
* class LoginComp {
* loginControl:Control;
*
* constructor() {
* this.loginControl = new Control('');
* }
* }
*
* ```
*
* @exportedAs angular2/forms
*/
@Directive({
selector: '[control]',
properties: {
'controlOrName' : 'control'
}
})
export class ControlDirective {
_groupDirective:ControlGroupDirective;
_controlOrName:any;
valueAccessor:any; //ControlValueAccessor
validator:Function;
constructor(@Optional() @Ancestor() groupDirective:ControlGroupDirective, valueAccessor:DefaultValueAccessor) {
this._groupDirective = groupDirective;
this._controlOrName = null;
this.valueAccessor = valueAccessor;
this.validator = Validators.nullValidator;
}
set controlOrName(controlOrName) {
this._controlOrName = controlOrName;
if(isPresent(this._groupDirective)) {
this._groupDirective.addDirective(this);
}
var c = this._control();
c.validator = Validators.compose([c.validator, this.validator]);
this._updateDomValue();
this._setUpUpdateControlValue();
}
_updateDomValue() {
this.valueAccessor.writeValue(this._control().value);
}
_setUpUpdateControlValue() {
this.valueAccessor.onChange = (newValue) => this._control().updateValue(newValue);
}
_control() {
return _lookupControl(this._groupDirective, this._controlOrName);
}
} }
/** /**
@ -189,7 +66,8 @@ export class ControlDirective {
* *
* # Example * # Example
* *
* In this example, we bind the control group to the form element, and we bind the login and password controls to the * In this example, we bind the control group to the form element, and we bind the login and
* password controls to the
* login and password elements. * login and password elements.
* *
* Here we use {@link formDirectives}, rather than importing each form directive individually, e.g. * Here we use {@link formDirectives}, rather than importing each form directive individually, e.g.
@ -224,18 +102,13 @@ export class ControlDirective {
* *
* @exportedAs angular2/forms * @exportedAs angular2/forms
*/ */
@Directive({ @Directive({selector: '[control-group]', properties: {'controlOrName': 'control-group'}})
selector: '[control-group]',
properties: {
'controlOrName' : 'control-group'
}
})
export class ControlGroupDirective { export class ControlGroupDirective {
_groupDirective:ControlGroupDirective; _groupDirective: ControlGroupDirective;
_directives:List<ControlDirective>; _directives: List<ControlDirective>;
_controlOrName:any; _controlOrName: any;
constructor(@Optional() @Ancestor() groupDirective:ControlGroupDirective) { constructor(@Optional() @Ancestor() groupDirective: ControlGroupDirective) {
this._groupDirective = groupDirective; this._groupDirective = groupDirective;
this._directives = ListWrapper.create(); this._directives = ListWrapper.create();
} }
@ -245,23 +118,126 @@ export class ControlGroupDirective {
this._updateDomValue(); this._updateDomValue();
} }
_updateDomValue() { _updateDomValue() { ListWrapper.forEach(this._directives, (cd) => cd._updateDomValue()); }
ListWrapper.forEach(this._directives, (cd) => cd._updateDomValue());
}
addDirective(c:ControlDirective) { addDirective(c: ControlDirective) { ListWrapper.push(this._directives, c); }
ListWrapper.push(this._directives, c);
}
findControl(name:string):any { findControl(name: string): any { return this._getControlGroup().controls[name]; }
return this._getControlGroup().controls[name];
}
_getControlGroup():ControlGroup { _getControlGroup(): ControlGroup {
return _lookupControl(this._groupDirective, this._controlOrName); return _lookupControl(this._groupDirective, this._controlOrName);
} }
} }
/**
* Binds a control to a DOM element.
*
* # Example
*
* In this example, we bind the control to an input element. When the value of the input element
* changes, the value of
* the control will reflect that change. Likewise, if the value of the control changes, the input
* element reflects that
* change.
*
* Here we use {@link formDirectives}, rather than importing each form directive individually, e.g.
* `ControlDirective`, `ControlGroupDirective`. This is just a shorthand for the same end result.
*
* ```
* @Component({selector: "login-comp"})
* @View({
* directives: [formDirectives],
* inline: "<input type='text' [control]='loginControl'>"
* })
* class LoginComp {
* loginControl:Control;
*
* constructor() {
* this.loginControl = new Control('');
* }
* }
*
* ```
*
* @exportedAs angular2/forms
*/
@Directive({selector: '[control]', properties: {'controlOrName': 'control'}})
export class ControlDirective {
_groupDirective: ControlGroupDirective;
_controlOrName: any;
valueAccessor: any; // ControlValueAccessor
validator: Function;
constructor(@Optional() @Ancestor() groupDirective: ControlGroupDirective,
valueAccessor: DefaultValueAccessor) {
this._groupDirective = groupDirective;
this._controlOrName = null;
this.valueAccessor = valueAccessor;
this.validator = Validators.nullValidator;
}
set controlOrName(controlOrName) {
this._controlOrName = controlOrName;
if (isPresent(this._groupDirective)) {
this._groupDirective.addDirective(this);
}
var c = this._control();
c.validator = Validators.compose([c.validator, this.validator]);
this._updateDomValue();
this._setUpUpdateControlValue();
}
_updateDomValue() { this.valueAccessor.writeValue(this._control().value); }
_setUpUpdateControlValue() {
this.valueAccessor.onChange = (newValue) => this._control().updateValue(newValue);
}
_control() { return _lookupControl(this._groupDirective, this._controlOrName); }
}
/**
* The accessor for writing a value and listening to changes on a checkbox input element.
*
*
* # Example
* ```
* <input type="checkbox" [control]="rememberLogin">
* ```
*
* @exportedAs angular2/forms
*/
@Directive({
selector: 'input[type=checkbox][control]',
hostListeners: {'change': 'onChange($event.target.checked)'},
hostProperties: {'checked': 'checked'}
})
export class CheckboxControlValueAccessor {
_elementRef: ElementRef;
_renderer: Renderer;
checked: boolean;
onChange: Function;
constructor(cd: ControlDirective, elementRef: ElementRef, renderer: Renderer) {
this.onChange = (_) => {};
this._elementRef = elementRef;
this._renderer = renderer;
cd.valueAccessor = this; // ControlDirective should inject CheckboxControlDirective
}
writeValue(value) {
this._renderer.setElementProperty(this._elementRef.parentView.render,
this._elementRef.boundElementIndex, 'checked', value)
}
}
/** /**
* *
* A list of all the form directives used as part of a `@View` annotation. * A list of all the form directives used as part of a `@View` annotation.
@ -270,6 +246,5 @@ export class ControlGroupDirective {
* *
* @exportedAs angular2/forms * @exportedAs angular2/forms
*/ */
export const formDirectives:List = CONST_EXPR([ export const formDirectives: List<Type> = CONST_EXPR(
ControlGroupDirective, ControlDirective, CheckboxControlValueAccessor, DefaultValueAccessor [ControlGroupDirective, ControlDirective, CheckboxControlValueAccessor, DefaultValueAccessor]);
]);

View File

@ -51,7 +51,8 @@ import * as modelModule from './model';
* bootstrap(LoginComp) * bootstrap(LoginComp)
* ``` * ```
* *
* This example creates a {@link ControlGroup} that consists of a `login` {@link Control}, and a nested * This example creates a {@link ControlGroup} that consists of a `login` {@link Control}, and a
* nested
* {@link ControlGroup} that defines a `password` and a `passwordConfirmation` {@link Control}: * {@link ControlGroup} that defines a `password` and a `passwordConfirmation` {@link Control}:
* *
* ``` * ```
@ -68,7 +69,8 @@ import * as modelModule from './model';
* @exportedAs angular2/forms * @exportedAs angular2/forms
*/ */
export class FormBuilder { export class FormBuilder {
group(controlsConfig, extra = null):modelModule.ControlGroup { group(controlsConfig: StringMap<string, any>,
extra: StringMap<string, any> = null): modelModule.ControlGroup {
var controls = this._reduceControls(controlsConfig); var controls = this._reduceControls(controlsConfig);
var optionals = isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null; var optionals = isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null;
var validator = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null; var validator = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
@ -80,7 +82,7 @@ export class FormBuilder {
} }
} }
control(value, validator:Function = null):modelModule.Control { control(value: Object, validator: Function = null): modelModule.Control {
if (isPresent(validator)) { if (isPresent(validator)) {
return new modelModule.Control(value, validator); return new modelModule.Control(value, validator);
} else { } else {
@ -88,7 +90,7 @@ export class FormBuilder {
} }
} }
array(controlsConfig:List, validator:Function = null):modelModule.ControlArray { array(controlsConfig: List<any>, validator: Function = null): modelModule.ControlArray {
var controls = ListWrapper.map(controlsConfig, (c) => this._createControl(c)); var controls = ListWrapper.map(controlsConfig, (c) => this._createControl(c));
if (isPresent(validator)) { if (isPresent(validator)) {
return new modelModule.ControlArray(controls, validator); return new modelModule.ControlArray(controls, validator);
@ -97,7 +99,7 @@ export class FormBuilder {
} }
} }
_reduceControls(controlsConfig) { _reduceControls(controlsConfig: any): StringMap<string, modelModule.AbstractControl> {
var controls = {}; var controls = {};
StringMapWrapper.forEach(controlsConfig, (controlConfig, controlName) => { StringMapWrapper.forEach(controlsConfig, (controlConfig, controlName) => {
controls[controlName] = this._createControl(controlConfig); controls[controlName] = this._createControl(controlConfig);
@ -105,10 +107,10 @@ export class FormBuilder {
return controls; return controls;
} }
_createControl(controlConfig) { _createControl(controlConfig: any): modelModule.AbstractControl {
if (controlConfig instanceof modelModule.Control || if (controlConfig instanceof modelModule.Control || controlConfig instanceof
controlConfig instanceof modelModule.ControlGroup || modelModule.ControlGroup || controlConfig instanceof
controlConfig instanceof modelModule.ControlArray) { modelModule.ControlArray) {
return controlConfig; return controlConfig;
} else if (ListWrapper.isList(controlConfig)) { } else if (ListWrapper.isList(controlConfig)) {

View File

@ -17,19 +17,7 @@ export const VALID = "VALID";
*/ */
export const INVALID = "INVALID"; export const INVALID = "INVALID";
//interface IControl { export function isControl(c: Object): boolean {
// get value():any;
// validator:Function;
// get status():string;
// get valid():boolean;
// get errors():Map;
// get pristine():boolean;
// get dirty():boolean;
// updateValue(value:any){}
// setParent(parent){}
//}
export function isControl(c:Object):boolean {
return c instanceof AbstractControl; return c instanceof AbstractControl;
} }
@ -37,55 +25,39 @@ export function isControl(c:Object):boolean {
/** /**
* Omitting from external API doc as this is really an abstract internal concept. * Omitting from external API doc as this is really an abstract internal concept.
*/ */
class AbstractControl { export class AbstractControl {
_value:any; _value: any;
_status:string; _status: string;
_errors:StringMap; _errors: StringMap<string, any>;
_pristine:boolean; _pristine: boolean;
_parent:any; /* ControlGroup | ControlArray */ _parent: any; /* ControlGroup | ControlArray */
validator:Function; validator: Function;
_valueChanges:EventEmitter; _valueChanges: EventEmitter;
constructor(validator:Function) { constructor(validator: Function) {
this.validator = validator; this.validator = validator;
this._pristine = true; this._pristine = true;
} }
get value():any { get value(): any { return this._value; }
return this._value;
}
get status():string { get status(): string { return this._status; }
return this._status;
}
get valid():boolean { get valid(): boolean { return this._status === VALID; }
return this._status === VALID;
}
get errors():StringMap { get errors(): StringMap<string, any> { return this._errors; }
return this._errors;
}
get pristine():boolean { get pristine(): boolean { return this._pristine; }
return this._pristine;
}
get dirty():boolean { get dirty(): boolean { return !this.pristine; }
return ! this.pristine;
}
get valueChanges():Observable { get valueChanges(): Observable { return this._valueChanges; }
return this._valueChanges;
}
setParent(parent){ setParent(parent) { this._parent = parent; }
this._parent = parent;
}
_updateParent() { _updateParent() {
if (isPresent(this._parent)){ if (isPresent(this._parent)) {
this._parent._updateValue(); this._parent._updateValue();
} }
} }
@ -94,19 +66,20 @@ 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 with * `Control` is one of the three fundamental building blocks used to define forms in Angular, along
* with
* {@link ControlGroup} and {@link ControlArray}. * {@link ControlGroup} and {@link ControlArray}.
* *
* @exportedAs angular2/forms * @exportedAs angular2/forms
*/ */
export class Control extends AbstractControl { export class Control extends AbstractControl {
constructor(value:any, validator:Function = Validators.nullValidator) { constructor(value: any, validator: Function = Validators.nullValidator) {
super(validator); super(validator);
this._setValueErrorsStatus(value); this._setValueErrorsStatus(value);
this._valueChanges = new EventEmitter(); this._valueChanges = new EventEmitter();
} }
updateValue(value:any):void { updateValue(value: any): void {
this._setValueErrorsStatus(value); this._setValueErrorsStatus(value);
this._pristine = false; this._pristine = false;
@ -115,7 +88,7 @@ export class Control extends AbstractControl {
this._updateParent(); this._updateParent();
} }
_setValueErrorsStatus(value) { _setValueErrorsStatus(value) {
this._value = value; this._value = value;
this._errors = this.validator(this); this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID; this._status = isPresent(this._errors) ? INVALID : VALID;
@ -125,21 +98,27 @@ 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 one of the controls * A ControlGroup aggregates the values and errors of each {@link Control} in the group. Thus, if
* in a group is invalid, the entire group is invalid. Similarly, if a control changes its value, the entire group * 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. * changes as well.
* *
* `ControlGroup` is one of the three fundamental building blocks used to define forms in Angular, along with * `ControlGroup` is one of the three fundamental building blocks used to define forms in Angular,
* {@link Control} and {@link ControlArray}. {@link ControlArray} can also contain other controls, but is of variable * along with
* {@link Control} and {@link ControlArray}. {@link ControlArray} can also contain other controls,
* but is of variable
* length. * length.
* *
* @exportedAs angular2/forms * @exportedAs angular2/forms
*/ */
export class ControlGroup extends AbstractControl { export class ControlGroup extends AbstractControl {
controls:StringMap; controls: StringMap<string, AbstractControl>;
_optionals:StringMap; _optionals: StringMap<string, boolean>;
constructor(controls:StringMap, optionals:StringMap = null, validator:Function = Validators.group) { constructor(controls: StringMap<String, AbstractControl>,
optionals: StringMap<String, boolean> = null,
validator: Function = Validators.group) {
super(validator); super(validator);
this.controls = controls; this.controls = controls;
this._optionals = isPresent(optionals) ? optionals : {}; this._optionals = isPresent(optionals) ? optionals : {};
@ -150,25 +129,23 @@ export class ControlGroup extends AbstractControl {
this._setValueErrorsStatus(); this._setValueErrorsStatus();
} }
include(controlName:string):void { include(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, true); StringMapWrapper.set(this._optionals, controlName, true);
this._updateValue(); this._updateValue();
} }
exclude(controlName:string):void { exclude(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, false); StringMapWrapper.set(this._optionals, controlName, false);
this._updateValue(); this._updateValue();
} }
contains(controlName:string):boolean { contains(controlName: string): boolean {
var c = StringMapWrapper.contains(this.controls, controlName); var c = StringMapWrapper.contains(this.controls, controlName);
return c && this._included(controlName); return c && this._included(controlName);
} }
_setParentForControls() { _setParentForControls() {
StringMapWrapper.forEach(this.controls, (control, name) => { StringMapWrapper.forEach(this.controls, (control, name) => { control.setParent(this); });
control.setParent(this);
});
} }
_updateValue() { _updateValue() {
@ -180,7 +157,7 @@ export class ControlGroup extends AbstractControl {
this._updateParent(); this._updateParent();
} }
_setValueErrorsStatus() { _setValueErrorsStatus() {
this._value = this._reduceValue(); this._value = this._reduceValue();
this._errors = this.validator(this); this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID; this._status = isPresent(this._errors) ? INVALID : VALID;
@ -193,7 +170,7 @@ export class ControlGroup extends AbstractControl {
}); });
} }
_reduceChildren(initValue:any, fn:Function) { _reduceChildren(initValue: any, fn: Function) {
var res = initValue; var res = initValue;
StringMapWrapper.forEach(this.controls, (control, name) => { StringMapWrapper.forEach(this.controls, (control, name) => {
if (this._included(name)) { if (this._included(name)) {
@ -203,7 +180,7 @@ export class ControlGroup extends AbstractControl {
return res; return res;
} }
_included(controlName:string):boolean { _included(controlName: string): boolean {
var isOptional = StringMapWrapper.contains(this._optionals, controlName); var isOptional = StringMapWrapper.contains(this._optionals, controlName);
return !isOptional || StringMapWrapper.get(this._optionals, controlName); return !isOptional || StringMapWrapper.get(this._optionals, controlName);
} }
@ -212,20 +189,24 @@ 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 one of the controls * A `ControlArray` aggregates the values and errors of each {@link Control} in the group. Thus, if
* in a group is invalid, the entire group is invalid. Similarly, if a control changes its value, the entire group * 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. * changes as well.
* *
* `ControlArray` is one of the three fundamental building blocks used to define forms in Angular, along with * `ControlArray` is one of the three fundamental building blocks used to define forms in Angular,
* {@link Control} and {@link ControlGroup}. {@link ControlGroup} can also contain other controls, but is of fixed * along with
* {@link Control} and {@link ControlGroup}. {@link ControlGroup} can also contain other controls,
* but is of fixed
* length. * length.
* *
* @exportedAs angular2/forms * @exportedAs angular2/forms
*/ */
export class ControlArray extends AbstractControl { export class ControlArray extends AbstractControl {
controls:List; controls: List<AbstractControl>;
constructor(controls:List<AbstractControl>, validator:Function = Validators.array) { constructor(controls: List<AbstractControl>, validator: Function = Validators.array) {
super(validator); super(validator);
this.controls = controls; this.controls = controls;
@ -235,30 +216,26 @@ export class ControlArray extends AbstractControl {
this._setValueErrorsStatus(); this._setValueErrorsStatus();
} }
at(index:number):AbstractControl { at(index: number): AbstractControl { return this.controls[index]; }
return this.controls[index];
}
push(control:AbstractControl):void { push(control: AbstractControl): void {
ListWrapper.push(this.controls, control); ListWrapper.push(this.controls, control);
control.setParent(this); control.setParent(this);
this._updateValue(); this._updateValue();
} }
insert(index:number, control:AbstractControl):void { insert(index: number, control: AbstractControl): void {
ListWrapper.insert(this.controls, index, control); ListWrapper.insert(this.controls, index, control);
control.setParent(this); control.setParent(this);
this._updateValue(); this._updateValue();
} }
removeAt(index:number):void { removeAt(index: number): void {
ListWrapper.removeAt(this.controls, index); ListWrapper.removeAt(this.controls, index);
this._updateValue(); this._updateValue();
} }
get length():number { get length(): number { return this.controls.length; }
return this.controls.length;
}
_updateValue() { _updateValue() {
this._setValueErrorsStatus(); this._setValueErrorsStatus();
@ -270,12 +247,10 @@ export class ControlArray extends AbstractControl {
} }
_setParentForControls() { _setParentForControls() {
ListWrapper.forEach(this.controls, (control) => { ListWrapper.forEach(this.controls, (control) => { control.setParent(this); });
control.setParent(this);
});
} }
_setValueErrorsStatus() { _setValueErrorsStatus() {
this._value = ListWrapper.map(this.controls, (c) => c.value); this._value = ListWrapper.map(this.controls, (c) => c.value);
this._errors = this.validator(this); this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID; this._status = isPresent(this._errors) ? INVALID : VALID;

View File

@ -1,13 +1,11 @@
import {Directive} from 'angular2/src/core/annotations_impl/annotations'; import {Directive} from 'angular2/src/core/annotations/decorators';
import {Validators} from './validators'; import {Validators} from './validators';
import {ControlDirective} from './directives'; import {ControlDirective} from './directives';
@Directive({ @Directive({selector: '[required]'})
selector: '[required]'
})
export class RequiredValidatorDirective { export class RequiredValidatorDirective {
constructor(c:ControlDirective) { constructor(c: ControlDirective) {
c.validator = Validators.compose([c.validator, Validators.required]); c.validator = Validators.compose([c.validator, Validators.required]);
} }
} }

View File

@ -15,16 +15,14 @@ import * as modelModule from './model';
* @exportedAs angular2/forms * @exportedAs angular2/forms
*/ */
export class Validators { export class Validators {
static required(c:modelModule.Control) { static required(c: modelModule.Control): StringMap<string, boolean> {
return isBlank(c.value) || c.value == "" ? {"required": true} : null; return isBlank(c.value) || c.value == "" ? {"required": true} : null;
} }
static nullValidator(c:any) { static nullValidator(c: any): StringMap<string, boolean> { return null; }
return null;
}
static compose(validators:List<Function>):Function { static compose(validators: List<Function>): Function {
return function (c:modelModule.Control) { return function(c: modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => { var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c); var errors = validator(c);
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
@ -33,7 +31,7 @@ export class Validators {
} }
} }
static group(c:modelModule.ControlGroup) { static group(c: modelModule.ControlGroup): StringMap<string, boolean> {
var res = {}; var res = {};
StringMapWrapper.forEach(c.controls, (control, name) => { StringMapWrapper.forEach(c.controls, (control, name) => {
if (c.contains(name) && isPresent(control.errors)) { if (c.contains(name) && isPresent(control.errors)) {
@ -43,7 +41,7 @@ export class Validators {
return StringMapWrapper.isEmpty(res) ? null : res; return StringMapWrapper.isEmpty(res) ? null : res;
} }
static array(c:modelModule.ControlArray) { static array(c: modelModule.ControlArray): StringMap<string, boolean> {
var res = {}; var res = {};
ListWrapper.forEach(c.controls, (control) => { ListWrapper.forEach(c.controls, (control) => {
if (isPresent(control.errors)) { if (isPresent(control.errors)) {
@ -53,7 +51,7 @@ export class Validators {
return StringMapWrapper.isEmpty(res) ? null : res; return StringMapWrapper.isEmpty(res) ? null : res;
} }
static _mergeErrors(control, res) { static _mergeErrors(control: modelModule.AbstractControl, res: StringMap<string, any>): void {
StringMapWrapper.forEach(control.errors, (value, error) => { StringMapWrapper.forEach(control.errors, (value, error) => {
if (!StringMapWrapper.contains(res, error)) { if (!StringMapWrapper.contains(res, error)) {
res[error] = []; res[error] = [];

View File

@ -1,8 +1,7 @@
import {global} from 'angular2/src/facade/lang'; import {global} from 'angular2/src/facade/lang';
export function makeDecorator(annotationCls) { export function makeDecorator(annotationCls) {
return function() { return function(... args) {
var args = arguments;
var Reflect = global.Reflect; var Reflect = global.Reflect;
if (!(Reflect && Reflect.getMetadata)) { if (!(Reflect && Reflect.getMetadata)) {
throw 'reflect-metadata shim is required when using class decorators'; throw 'reflect-metadata shim is required when using class decorators';
@ -19,7 +18,7 @@ export function makeDecorator(annotationCls) {
} }
} }
export function makeParamDecorator(annotationCls) { export function makeParamDecorator(annotationCls): any {
return function(... args) { return function(... args) {
var Reflect = global.Reflect; var Reflect = global.Reflect;
if (!(Reflect && Reflect.getMetadata)) { if (!(Reflect && Reflect.getMetadata)) {
@ -28,14 +27,19 @@ export function makeParamDecorator(annotationCls) {
var annotationInstance = Object.create(annotationCls.prototype); var annotationInstance = Object.create(annotationCls.prototype);
annotationCls.apply(annotationInstance, args); annotationCls.apply(annotationInstance, args);
return function(cls, unusedKey, index) { return function(cls, unusedKey, index) {
var parameters = Reflect.getMetadata('parameters', cls); var parameters: Array<Array<any>> = Reflect.getMetadata('parameters', cls);
parameters = parameters || []; parameters = parameters || [];
// there might be gaps if some in between parameters do not have annotations. // there might be gaps if some in between parameters do not have annotations.
// we pad with nulls. // we pad with nulls.
while (parameters.length <= index) { while (parameters.length <= index) {
parameters.push(null); parameters.push(null);
} }
parameters[index] = annotationInstance;
parameters[index] = parameters[index] || [];
var annotationsForParam: Array<any> = parameters[index];
annotationsForParam.push(annotationInstance);
Reflect.defineMetadata('parameters', parameters, cls); Reflect.defineMetadata('parameters', parameters, cls);
return cls; return cls;
} }

View File

@ -45,6 +45,7 @@ export function main() {
}); });
})); }));
it("should update the control group values on DOM change", it("should update the control group values on DOM change",
inject([TestBed, AsyncTestCompleter], (tb, async) => { inject([TestBed, AsyncTestCompleter], (tb, async) => {
var form = new ControlGroup({ var form = new ControlGroup({