From 00c3693daa52f418840195e606e1ea52f3565493 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 20 May 2015 18:10:30 -0700 Subject: [PATCH] feat(forms): migrated forms to typescript --- modules/angular2/{forms.js => forms.ts} | 9 +- .../src/core/annotations/decorators.dart | 3 + .../src/core/annotations/decorators.ts | 11 +- .../src/core/annotations/visibility.ts | 2 + .../src/core/compiler/directive_resolver.ts | 1 - .../forms/{directives.js => directives.ts} | 309 ++++++++---------- .../{form_builder.js => form_builder.ts} | 20 +- .../angular2/src/forms/{model.js => model.ts} | 147 ++++----- ..._directives.js => validator_directives.ts} | 8 +- .../forms/{validators.js => validators.ts} | 16 +- modules/angular2/src/util/decorators.ts | 14 +- .../angular2/test/forms/integration_spec.js | 1 + 12 files changed, 254 insertions(+), 287 deletions(-) rename modules/angular2/{forms.js => forms.ts} (76%) rename modules/angular2/src/forms/{directives.js => directives.ts} (62%) rename modules/angular2/src/forms/{form_builder.js => form_builder.ts} (81%) rename modules/angular2/src/forms/{model.js => model.ts} (65%) rename modules/angular2/src/forms/{validator_directives.js => validator_directives.ts} (58%) rename modules/angular2/src/forms/{validators.js => validators.ts} (74%) diff --git a/modules/angular2/forms.js b/modules/angular2/forms.ts similarity index 76% rename from modules/angular2/forms.js rename to modules/angular2/forms.ts index 6e84373929..eb525a7868 100644 --- a/modules/angular2/forms.js +++ b/modules/angular2/forms.ts @@ -2,11 +2,14 @@ * @module * @public * @description - * This module is used for handling user input, by defining and building a {@link ControlGroup} that consists of - * {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used to read information + * This module is used for handling user input, by defining and building a {@link ControlGroup} that + * 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. * - * 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. * */ diff --git a/modules/angular2/src/core/annotations/decorators.dart b/modules/angular2/src/core/annotations/decorators.dart index ec9f91bd46..676088c9e7 100644 --- a/modules/angular2/src/core/annotations/decorators.dart +++ b/modules/angular2/src/core/annotations/decorators.dart @@ -1,3 +1,6 @@ library angular2.core.decorators; +export '../annotations_impl/annotations.dart'; +export '../annotations_impl/visibility.dart'; + /* This file is empty because, Dart does not have decorators. */ diff --git a/modules/angular2/src/core/annotations/decorators.ts b/modules/angular2/src/core/annotations/decorators.ts index f5e80b89e5..1829f79579 100644 --- a/modules/angular2/src/core/annotations/decorators.ts +++ b/modules/angular2/src/core/annotations/decorators.ts @@ -1,6 +1,11 @@ import {ComponentAnnotation, DirectiveAnnotation} from './annotations'; import {ViewAnnotation} from './view'; -import {AncestorAnnotation, ParentAnnotation} from './visibility'; +import { + SelfAnnotation, + ParentAnnotation, + AncestorAnnotation, + UnboundedAnnotation +} from './visibility'; import {AttributeAnnotation, QueryAnnotation} from './di'; import {makeDecorator, makeParamDecorator} from '../../util/decorators'; @@ -12,8 +17,10 @@ export var Directive = makeDecorator(DirectiveAnnotation); export var View = makeDecorator(ViewAnnotation); /* from visibility */ -export var Ancestor = makeParamDecorator(AncestorAnnotation); +export var Self = makeParamDecorator(SelfAnnotation); export var Parent = makeParamDecorator(ParentAnnotation); +export var Ancestor = makeParamDecorator(AncestorAnnotation); +export var Unbounded = makeParamDecorator(UnboundedAnnotation); /* from di */ export var Attribute = makeParamDecorator(AttributeAnnotation); diff --git a/modules/angular2/src/core/annotations/visibility.ts b/modules/angular2/src/core/annotations/visibility.ts index 53acffc1fb..eded081cea 100644 --- a/modules/angular2/src/core/annotations/visibility.ts +++ b/modules/angular2/src/core/annotations/visibility.ts @@ -1,4 +1,6 @@ export { + Self as SelfAnnotation, Ancestor as AncestorAnnotation, Parent as ParentAnnotation, + Unbounded as UnboundedAnnotation } from '../annotations_impl/visibility'; diff --git a/modules/angular2/src/core/compiler/directive_resolver.ts b/modules/angular2/src/core/compiler/directive_resolver.ts index 0879db0738..0517336f84 100644 --- a/modules/angular2/src/core/compiler/directive_resolver.ts +++ b/modules/angular2/src/core/compiler/directive_resolver.ts @@ -10,7 +10,6 @@ export class DirectiveResolver { if (isPresent(annotations)) { for (var i = 0; i < annotations.length; i++) { var annotation = annotations[i]; - if (annotation instanceof Directive) { return annotation; } diff --git a/modules/angular2/src/forms/directives.js b/modules/angular2/src/forms/directives.ts similarity index 62% rename from modules/angular2/src/forms/directives.js rename to modules/angular2/src/forms/directives.ts index 97df5a9de0..6b8bf05c4a 100644 --- a/modules/angular2/src/forms/directives.js +++ b/modules/angular2/src/forms/directives.ts @@ -1,19 +1,20 @@ -import {Directive} from 'angular2/src/core/annotations_impl/annotations'; -import {Ancestor} from 'angular2/src/core/annotations_impl/visibility'; +import {Directive, Ancestor} from 'angular2/src/core/annotations/decorators'; +import {Optional} from 'angular2/src/di/decorators'; 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 {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 {ControlGroup, Control, isControl} from './model'; import {Validators} from './validators'; -//export interface ControlValueAccessor { -// writeValue(value):void{} -// set onChange(fn){} -//} - -function _lookupControl(groupDirective:ControlGroupDirective, controlOrName:any):any { +function _lookupControl(groupDirective: ControlGroupDirective, controlOrName: any): any { if (isControl(controlOrName)) { return controlOrName; } @@ -31,8 +32,10 @@ function _lookupControl(groupDirective:ControlGroupDirective, controlOrName:any) 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. * @@ -45,143 +48,17 @@ function _lookupControl(groupDirective:ControlGroupDirective, controlOrName:any) */ @Directive({ selector: '[control]', - hostListeners: { - 'change' : 'onChange($event.target.value)', - 'input' : 'onChange($event.target.value)' - }, - hostProperties: { - 'value' : 'value' - } + hostListeners: + {'change': 'onChange($event.target.value)', 'input': 'onChange($event.target.value)'}, + hostProperties: {'value': 'value'} }) export class DefaultValueAccessor { value; - onChange:Function; + onChange: Function; - constructor() { - this.onChange = (_) => {}; - } + constructor() { this.onChange = (_) => {}; } - writeValue(value) { - this.value = value - } -} - -/** - * The accessor for writing a value and listening to changes on a checkbox input element. - * - * - * # Example - * ``` - * - * ``` - * - * @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: "" - * }) - * 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); - } + writeValue(value) { this.value = value } } /** @@ -189,7 +66,8 @@ export class ControlDirective { * * # 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. * * Here we use {@link formDirectives}, rather than importing each form directive individually, e.g. @@ -224,18 +102,13 @@ export class ControlDirective { * * @exportedAs angular2/forms */ -@Directive({ - selector: '[control-group]', - properties: { - 'controlOrName' : 'control-group' - } -}) +@Directive({selector: '[control-group]', properties: {'controlOrName': 'control-group'}}) export class ControlGroupDirective { - _groupDirective:ControlGroupDirective; - _directives:List; - _controlOrName:any; + _groupDirective: ControlGroupDirective; + _directives: List; + _controlOrName: any; - constructor(@Optional() @Ancestor() groupDirective:ControlGroupDirective) { + constructor(@Optional() @Ancestor() groupDirective: ControlGroupDirective) { this._groupDirective = groupDirective; this._directives = ListWrapper.create(); } @@ -245,23 +118,126 @@ export class ControlGroupDirective { this._updateDomValue(); } - _updateDomValue() { - ListWrapper.forEach(this._directives, (cd) => cd._updateDomValue()); - } + _updateDomValue() { ListWrapper.forEach(this._directives, (cd) => cd._updateDomValue()); } - addDirective(c:ControlDirective) { - ListWrapper.push(this._directives, c); - } + addDirective(c: ControlDirective) { ListWrapper.push(this._directives, c); } - findControl(name:string):any { - return this._getControlGroup().controls[name]; - } + findControl(name: string): any { return this._getControlGroup().controls[name]; } - _getControlGroup():ControlGroup { + _getControlGroup(): ControlGroup { 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: "" + * }) + * 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 + * ``` + * + * ``` + * + * @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. @@ -270,6 +246,5 @@ export class ControlGroupDirective { * * @exportedAs angular2/forms */ -export const formDirectives:List = CONST_EXPR([ - ControlGroupDirective, ControlDirective, CheckboxControlValueAccessor, DefaultValueAccessor -]); +export const formDirectives: List = CONST_EXPR( + [ControlGroupDirective, ControlDirective, CheckboxControlValueAccessor, DefaultValueAccessor]); diff --git a/modules/angular2/src/forms/form_builder.js b/modules/angular2/src/forms/form_builder.ts similarity index 81% rename from modules/angular2/src/forms/form_builder.js rename to modules/angular2/src/forms/form_builder.ts index ee58dd6c94..917267c6a9 100644 --- a/modules/angular2/src/forms/form_builder.js +++ b/modules/angular2/src/forms/form_builder.ts @@ -51,7 +51,8 @@ import * as modelModule from './model'; * 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}: * * ``` @@ -68,7 +69,8 @@ import * as modelModule from './model'; * @exportedAs angular2/forms */ export class FormBuilder { - group(controlsConfig, extra = null):modelModule.ControlGroup { + group(controlsConfig: StringMap, + extra: StringMap = null): modelModule.ControlGroup { var controls = this._reduceControls(controlsConfig); var optionals = isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : 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)) { return new modelModule.Control(value, validator); } else { @@ -88,7 +90,7 @@ export class FormBuilder { } } - array(controlsConfig:List, validator:Function = null):modelModule.ControlArray { + array(controlsConfig: List, validator: Function = null): modelModule.ControlArray { var controls = ListWrapper.map(controlsConfig, (c) => this._createControl(c)); if (isPresent(validator)) { return new modelModule.ControlArray(controls, validator); @@ -97,7 +99,7 @@ export class FormBuilder { } } - _reduceControls(controlsConfig) { + _reduceControls(controlsConfig: any): StringMap { var controls = {}; StringMapWrapper.forEach(controlsConfig, (controlConfig, controlName) => { controls[controlName] = this._createControl(controlConfig); @@ -105,10 +107,10 @@ export class FormBuilder { return controls; } - _createControl(controlConfig) { - if (controlConfig instanceof modelModule.Control || - controlConfig instanceof modelModule.ControlGroup || - controlConfig instanceof modelModule.ControlArray) { + _createControl(controlConfig: any): modelModule.AbstractControl { + if (controlConfig instanceof modelModule.Control || controlConfig instanceof + modelModule.ControlGroup || controlConfig instanceof + modelModule.ControlArray) { return controlConfig; } else if (ListWrapper.isList(controlConfig)) { diff --git a/modules/angular2/src/forms/model.js b/modules/angular2/src/forms/model.ts similarity index 65% rename from modules/angular2/src/forms/model.js rename to modules/angular2/src/forms/model.ts index 027da5e55c..e0ef41ed63 100644 --- a/modules/angular2/src/forms/model.js +++ b/modules/angular2/src/forms/model.ts @@ -17,19 +17,7 @@ export const VALID = "VALID"; */ export const INVALID = "INVALID"; -//interface IControl { -// 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 { +export function isControl(c: Object): boolean { 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. */ -class AbstractControl { - _value:any; - _status:string; - _errors:StringMap; - _pristine:boolean; - _parent:any; /* ControlGroup | ControlArray */ - validator:Function; +export class AbstractControl { + _value: any; + _status: string; + _errors: StringMap; + _pristine: boolean; + _parent: any; /* ControlGroup | ControlArray */ + validator: Function; - _valueChanges:EventEmitter; + _valueChanges: EventEmitter; - constructor(validator:Function) { + constructor(validator: Function) { this.validator = validator; this._pristine = true; } - get value():any { - return this._value; - } + get value(): any { return this._value; } - get status():string { - return this._status; - } + get status(): string { return this._status; } - get valid():boolean { - return this._status === VALID; - } + get valid(): boolean { return this._status === VALID; } - get errors():StringMap { - return this._errors; - } + get errors(): StringMap { return this._errors; } - get pristine():boolean { - return this._pristine; - } + get pristine(): boolean { return this._pristine; } - get dirty():boolean { - return ! this.pristine; - } + get dirty(): boolean { return !this.pristine; } - get valueChanges():Observable { - return this._valueChanges; - } + get valueChanges(): Observable { return this._valueChanges; } - setParent(parent){ - this._parent = parent; - } + setParent(parent) { this._parent = parent; } _updateParent() { - if (isPresent(this._parent)){ + if (isPresent(this._parent)) { this._parent._updateValue(); } } @@ -94,19 +66,20 @@ class AbstractControl { /** * 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}. * * @exportedAs angular2/forms */ export class Control extends AbstractControl { - constructor(value:any, validator:Function = Validators.nullValidator) { + constructor(value: any, validator: Function = Validators.nullValidator) { super(validator); this._setValueErrorsStatus(value); this._valueChanges = new EventEmitter(); } - updateValue(value:any):void { + updateValue(value: any): void { this._setValueErrorsStatus(value); this._pristine = false; @@ -115,7 +88,7 @@ export class Control extends AbstractControl { this._updateParent(); } - _setValueErrorsStatus(value) { + _setValueErrorsStatus(value) { this._value = value; this._errors = this.validator(this); 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. * - * 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 + * 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 + * `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. * * @exportedAs angular2/forms */ export class ControlGroup extends AbstractControl { - controls:StringMap; - _optionals:StringMap; + controls: StringMap; + _optionals: StringMap; - constructor(controls:StringMap, optionals:StringMap = null, validator:Function = Validators.group) { + constructor(controls: StringMap, + optionals: StringMap = null, + validator: Function = Validators.group) { super(validator); this.controls = controls; this._optionals = isPresent(optionals) ? optionals : {}; @@ -150,25 +129,23 @@ export class ControlGroup extends AbstractControl { this._setValueErrorsStatus(); } - include(controlName:string):void { + include(controlName: string): void { StringMapWrapper.set(this._optionals, controlName, true); this._updateValue(); } - exclude(controlName:string):void { + exclude(controlName: string): void { StringMapWrapper.set(this._optionals, controlName, false); this._updateValue(); } - contains(controlName:string):boolean { + 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); - }); + StringMapWrapper.forEach(this.controls, (control, name) => { control.setParent(this); }); } _updateValue() { @@ -180,7 +157,7 @@ export class ControlGroup extends AbstractControl { this._updateParent(); } - _setValueErrorsStatus() { + _setValueErrorsStatus() { this._value = this._reduceValue(); this._errors = this.validator(this); 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; StringMapWrapper.forEach(this.controls, (control, name) => { if (this._included(name)) { @@ -203,7 +180,7 @@ export class ControlGroup extends AbstractControl { return res; } - _included(controlName:string):boolean { + _included(controlName: string): boolean { var isOptional = StringMapWrapper.contains(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. * - * 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 + * 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 + * `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. * * @exportedAs angular2/forms */ export class ControlArray extends AbstractControl { - controls:List; + controls: List; - constructor(controls:List, validator:Function = Validators.array) { + constructor(controls: List, validator: Function = Validators.array) { super(validator); this.controls = controls; @@ -235,30 +216,26 @@ export class ControlArray extends AbstractControl { this._setValueErrorsStatus(); } - at(index:number):AbstractControl { - return this.controls[index]; - } + at(index: number): AbstractControl { return this.controls[index]; } - push(control:AbstractControl):void { + push(control: AbstractControl): void { ListWrapper.push(this.controls, control); control.setParent(this); this._updateValue(); } - insert(index:number, control:AbstractControl):void { + insert(index: number, control: AbstractControl): void { ListWrapper.insert(this.controls, index, control); control.setParent(this); this._updateValue(); } - removeAt(index:number):void { + removeAt(index: number): void { ListWrapper.removeAt(this.controls, index); this._updateValue(); } - get length():number { - return this.controls.length; - } + get length(): number { return this.controls.length; } _updateValue() { this._setValueErrorsStatus(); @@ -270,12 +247,10 @@ export class ControlArray extends AbstractControl { } _setParentForControls() { - ListWrapper.forEach(this.controls, (control) => { - control.setParent(this); - }); + ListWrapper.forEach(this.controls, (control) => { control.setParent(this); }); } - _setValueErrorsStatus() { + _setValueErrorsStatus() { this._value = ListWrapper.map(this.controls, (c) => c.value); this._errors = this.validator(this); this._status = isPresent(this._errors) ? INVALID : VALID; diff --git a/modules/angular2/src/forms/validator_directives.js b/modules/angular2/src/forms/validator_directives.ts similarity index 58% rename from modules/angular2/src/forms/validator_directives.js rename to modules/angular2/src/forms/validator_directives.ts index f0cef9909a..8c13920bfe 100644 --- a/modules/angular2/src/forms/validator_directives.js +++ b/modules/angular2/src/forms/validator_directives.ts @@ -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 {ControlDirective} from './directives'; -@Directive({ - selector: '[required]' -}) +@Directive({selector: '[required]'}) export class RequiredValidatorDirective { - constructor(c:ControlDirective) { + constructor(c: ControlDirective) { c.validator = Validators.compose([c.validator, Validators.required]); } } diff --git a/modules/angular2/src/forms/validators.js b/modules/angular2/src/forms/validators.ts similarity index 74% rename from modules/angular2/src/forms/validators.js rename to modules/angular2/src/forms/validators.ts index ac08b56412..0692f12e3d 100644 --- a/modules/angular2/src/forms/validators.js +++ b/modules/angular2/src/forms/validators.ts @@ -15,16 +15,14 @@ import * as modelModule from './model'; * @exportedAs angular2/forms */ export class Validators { - static required(c:modelModule.Control) { + static required(c: modelModule.Control): StringMap { return isBlank(c.value) || c.value == "" ? {"required": true} : null; } - static nullValidator(c:any) { - return null; - } + static nullValidator(c: any): StringMap { return null; } - static compose(validators:List):Function { - return function (c:modelModule.Control) { + static compose(validators: List): Function { + return function(c: modelModule.Control) { var res = ListWrapper.reduce(validators, (res, validator) => { var errors = validator(c); 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 { var res = {}; StringMapWrapper.forEach(c.controls, (control, name) => { if (c.contains(name) && isPresent(control.errors)) { @@ -43,7 +41,7 @@ export class Validators { return StringMapWrapper.isEmpty(res) ? null : res; } - static array(c:modelModule.ControlArray) { + static array(c: modelModule.ControlArray): StringMap { var res = {}; ListWrapper.forEach(c.controls, (control) => { if (isPresent(control.errors)) { @@ -53,7 +51,7 @@ export class Validators { return StringMapWrapper.isEmpty(res) ? null : res; } - static _mergeErrors(control, res) { + static _mergeErrors(control: modelModule.AbstractControl, res: StringMap): void { StringMapWrapper.forEach(control.errors, (value, error) => { if (!StringMapWrapper.contains(res, error)) { res[error] = []; diff --git a/modules/angular2/src/util/decorators.ts b/modules/angular2/src/util/decorators.ts index 7a7d5a02a3..b2e63ae296 100644 --- a/modules/angular2/src/util/decorators.ts +++ b/modules/angular2/src/util/decorators.ts @@ -1,8 +1,7 @@ import {global} from 'angular2/src/facade/lang'; export function makeDecorator(annotationCls) { - return function() { - var args = arguments; + return function(... args) { var Reflect = global.Reflect; if (!(Reflect && Reflect.getMetadata)) { 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) { var Reflect = global.Reflect; if (!(Reflect && Reflect.getMetadata)) { @@ -28,14 +27,19 @@ export function makeParamDecorator(annotationCls) { var annotationInstance = Object.create(annotationCls.prototype); annotationCls.apply(annotationInstance, args); return function(cls, unusedKey, index) { - var parameters = Reflect.getMetadata('parameters', cls); + var parameters: Array> = Reflect.getMetadata('parameters', cls); parameters = parameters || []; + // there might be gaps if some in between parameters do not have annotations. // we pad with nulls. while (parameters.length <= index) { parameters.push(null); } - parameters[index] = annotationInstance; + + parameters[index] = parameters[index] || []; + var annotationsForParam: Array = parameters[index]; + annotationsForParam.push(annotationInstance); + Reflect.defineMetadata('parameters', parameters, cls); return cls; } diff --git a/modules/angular2/test/forms/integration_spec.js b/modules/angular2/test/forms/integration_spec.js index 7418727aeb..fa3df617e8 100644 --- a/modules/angular2/test/forms/integration_spec.js +++ b/modules/angular2/test/forms/integration_spec.js @@ -45,6 +45,7 @@ export function main() { }); })); + it("should update the control group values on DOM change", inject([TestBed, AsyncTestCompleter], (tb, async) => { var form = new ControlGroup({