feat(forms): add easy way to switch between forms modules (#9202)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export 'index.dart';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './src/forms';
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@angular/forms",
|
||||
"version": "0.0.0-PLACEHOLDER",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"jsnext:main": "esm/index.js",
|
||||
"typings": "index.d.ts",
|
||||
"author": "angular",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@angular/core": "0.0.0-PLACEHOLDER",
|
||||
"@angular/common": "0.0.0-PLACEHOLDER",
|
||||
"@angular/compiler": "0.0.0-PLACEHOLDER"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.git"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
export default {
|
||||
entry: '../../../dist/packages-dist/forms/esm/index.js',
|
||||
dest: '../../../dist/packages-dist/forms/esm/forms.umd.js',
|
||||
format: 'umd',
|
||||
moduleName: 'ng.forms',
|
||||
globals: {
|
||||
'@angular/core': 'ng.core',
|
||||
'@angular/common': 'ng.common',
|
||||
'@angular/compiler': 'ng.compiler',
|
||||
'rxjs/Subject': 'Rx',
|
||||
'rxjs/observable/PromiseObservable': 'Rx', // this is wrong, but this stuff has changed in rxjs b.6 so we need to fix it when we update.
|
||||
'rxjs/operator/toPromise': 'Rx.Observable.prototype',
|
||||
'rxjs/Observable': 'Rx'
|
||||
},
|
||||
plugins: [
|
||||
// nodeResolve({ jsnext: true, main: true }),
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
import {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
import {NgControlStatus} from './directives/ng_control_status';
|
||||
import {NgForm} from './directives/ng_form';
|
||||
import {NgModel} from './directives/ng_model';
|
||||
import {NgModelGroup} from './directives/ng_model_group';
|
||||
import {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
import {FormControlDirective} from './directives/reactive_directives/form_control_directive';
|
||||
import {FormControlName} from './directives/reactive_directives/form_control_name';
|
||||
import {FormGroupDirective} from './directives/reactive_directives/form_group_directive';
|
||||
import {FormGroupName} from './directives/reactive_directives/form_group_name';
|
||||
import {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
import {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
import {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||
|
||||
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
export {ControlValueAccessor} from './directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
export {NgControl} from './directives/ng_control';
|
||||
export {NgControlStatus} from './directives/ng_control_status';
|
||||
export {NgForm} from './directives/ng_form';
|
||||
export {NgModel} from './directives/ng_model';
|
||||
export {NgModelGroup} from './directives/ng_model_group';
|
||||
export {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
export {RadioButtonState, RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
export {FormControlDirective} from './directives/reactive_directives/form_control_directive';
|
||||
export {FormControlName} from './directives/reactive_directives/form_control_name';
|
||||
export {FormGroupDirective} from './directives/reactive_directives/form_group_directive';
|
||||
export {FormGroupName} from './directives/reactive_directives/form_group_name';
|
||||
export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
export {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* A list of all the form directives used as part of a `@Component` annotation.
|
||||
*
|
||||
* This is a shorthand for importing them each individually.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* directives: [FORM_DIRECTIVES]
|
||||
* })
|
||||
* class MyApp {}
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[
|
||||
NgModel, NgModelGroup, NgForm,
|
||||
|
||||
NgSelectOption, NgSelectMultipleOption, DefaultValueAccessor, NumberValueAccessor,
|
||||
CheckboxControlValueAccessor, SelectControlValueAccessor, SelectMultipleControlValueAccessor,
|
||||
RadioControlValueAccessor, NgControlStatus,
|
||||
|
||||
RequiredValidator, MinLengthValidator, MaxLengthValidator, PatternValidator
|
||||
];
|
||||
|
||||
export const REACTIVE_FORM_DIRECTIVES: Type[] =
|
||||
/*@ts2dart_const*/[FormControlDirective, FormGroupDirective, FormControlName, FormGroupName];
|
||||
@@ -0,0 +1,33 @@
|
||||
import {unimplemented} from '../facade/exceptions';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {AbstractControl} from '../model';
|
||||
|
||||
|
||||
/**
|
||||
* Base class for control directives.
|
||||
*
|
||||
* Only used internally in the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class AbstractControlDirective {
|
||||
get control(): AbstractControl { return unimplemented(); }
|
||||
|
||||
get value(): any { return isPresent(this.control) ? this.control.value : null; }
|
||||
|
||||
get valid(): boolean { return isPresent(this.control) ? this.control.valid : null; }
|
||||
|
||||
get errors(): {[key: string]: any} {
|
||||
return isPresent(this.control) ? this.control.errors : null;
|
||||
}
|
||||
|
||||
get pristine(): boolean { return isPresent(this.control) ? this.control.pristine : null; }
|
||||
|
||||
get dirty(): boolean { return isPresent(this.control) ? this.control.dirty : null; }
|
||||
|
||||
get touched(): boolean { return isPresent(this.control) ? this.control.touched : null; }
|
||||
|
||||
get untouched(): boolean { return isPresent(this.control) ? this.control.untouched : null; }
|
||||
|
||||
get path(): string[] { return null; }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {OnDestroy, OnInit} from '@angular/core';
|
||||
|
||||
import {FormGroup} from '../model';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
import {composeAsyncValidators, composeValidators, controlPath} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
/**
|
||||
This is a base class for code shared between {@link NgModelGroup} and {@link FormGroupName}.
|
||||
*/
|
||||
|
||||
export class AbstractFormGroupDirective extends ControlContainer implements OnInit, OnDestroy {
|
||||
/** @internal */
|
||||
_parent: ControlContainer;
|
||||
|
||||
/** @internal */
|
||||
_validators: any[];
|
||||
|
||||
/** @internal */
|
||||
_asyncValidators: any[];
|
||||
|
||||
ngOnInit(): void { this.formDirective.addFormGroup(this); }
|
||||
|
||||
ngOnDestroy(): void { this.formDirective.removeFormGroup(this); }
|
||||
|
||||
/**
|
||||
* Get the {@link FormGroup} backing this binding.
|
||||
*/
|
||||
get control(): FormGroup { return this.formDirective.getFormGroup(this); }
|
||||
|
||||
/**
|
||||
* Get the path to this control group.
|
||||
*/
|
||||
get path(): string[] { return controlPath(this.name, this._parent); }
|
||||
|
||||
/**
|
||||
* Get the {@link Form} to which this group belongs.
|
||||
*/
|
||||
get formDirective(): Form { return this._parent.formDirective; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const CHECKBOX_VALUE_ACCESSOR: any = /*@ts2dart_const*/ {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => CheckboxControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The accessor for writing a value and listening to changes on a checkbox input element.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* <input type="checkbox" name="rememberLogin" ngModel>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]',
|
||||
host: {'(change)': 'onChange($event.target.checked)', '(blur)': 'onTouched()'},
|
||||
providers: [CHECKBOX_VALUE_ACCESSOR]
|
||||
})
|
||||
export class CheckboxControlValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', value);
|
||||
}
|
||||
registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
|
||||
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {Form} from './form_interface';
|
||||
|
||||
|
||||
/**
|
||||
* A directive that contains multiple {@link NgControl}s.
|
||||
*
|
||||
* Only used by the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ControlContainer extends AbstractControlDirective {
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Get the form to which this container belongs.
|
||||
*/
|
||||
get formDirective(): Form { return null; }
|
||||
|
||||
/**
|
||||
* Get the path to this container.
|
||||
*/
|
||||
get path(): string[] { return null; }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
|
||||
/**
|
||||
* A bridge between a control and a native element.
|
||||
*
|
||||
* A `ControlValueAccessor` abstracts the operations of writing a new value to a
|
||||
* DOM element representing an input control.
|
||||
*
|
||||
* Please see {@link DefaultValueAccessor} for more information.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface ControlValueAccessor {
|
||||
/**
|
||||
* Write a new value to the element.
|
||||
*/
|
||||
writeValue(obj: any): void;
|
||||
|
||||
/**
|
||||
* Set the function to be called when the control receives a change event.
|
||||
*/
|
||||
registerOnChange(fn: any): void;
|
||||
|
||||
/**
|
||||
* Set the function to be called when the control receives a touch event.
|
||||
*/
|
||||
registerOnTouched(fn: any): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to provide a {@link ControlValueAccessor} for form controls.
|
||||
*
|
||||
* See {@link DefaultValueAccessor} for how to implement one.
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_VALUE_ACCESSOR: OpaqueToken =
|
||||
/*@ts2dart_const*/ new OpaqueToken('NgValueAccessor');
|
||||
@@ -0,0 +1,47 @@
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {isBlank} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const DEFAULT_VALUE_ACCESSOR: any = /*@ts2dart_const*/
|
||||
/* @ts2dart_Provider */ {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DefaultValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The default accessor for writing a value and listening to changes that is used by the
|
||||
* {@link NgModel}, {@link FormControlDirective}, and {@link FormControlName} directives.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* <input type="text" name="searchQuery" ngModel>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]',
|
||||
// TODO: vsavkin replace the above selector with the one below it once
|
||||
// https://github.com/angular/angular/issues/3011 is implemented
|
||||
// selector: '[ngControl],[ngModel],[ngFormControl]',
|
||||
host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
|
||||
providers: [DEFAULT_VALUE_ACCESSOR]
|
||||
})
|
||||
export class DefaultValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
var normalizedValue = isBlank(value) ? '' : value;
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
|
||||
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {FormControl, FormGroup} from '../model';
|
||||
|
||||
import {AbstractFormGroupDirective} from './abstract_form_group_directive';
|
||||
import {NgControl} from './ng_control';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface that {@link FormGroupDirective} and {@link NgForm} implement.
|
||||
*
|
||||
* Only used by the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface Form {
|
||||
/**
|
||||
* Add a control to this form.
|
||||
*/
|
||||
addControl(dir: NgControl): FormControl;
|
||||
|
||||
/**
|
||||
* Remove a control from this form.
|
||||
*/
|
||||
removeControl(dir: NgControl): void;
|
||||
|
||||
/**
|
||||
* Look up the {@link FormControl} associated with a particular {@link NgControl}.
|
||||
*/
|
||||
getControl(dir: NgControl): FormControl;
|
||||
|
||||
/**
|
||||
* Add a group of controls to this form.
|
||||
*/
|
||||
addFormGroup(dir: AbstractFormGroupDirective): void;
|
||||
|
||||
/**
|
||||
* Remove a group of controls from this form.
|
||||
*/
|
||||
removeFormGroup(dir: AbstractFormGroupDirective): void;
|
||||
|
||||
/**
|
||||
* Look up the {@link FormGroup} associated with a particular {@link AbstractFormGroupDirective}.
|
||||
*/
|
||||
getFormGroup(dir: AbstractFormGroupDirective): FormGroup;
|
||||
|
||||
/**
|
||||
* Update the model for a particular control with a new value.
|
||||
*/
|
||||
updateModel(dir: NgControl, value: any): void;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {unimplemented} from '../facade/exceptions';
|
||||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
|
||||
/**
|
||||
* A base class that all control directive extend.
|
||||
* It binds a {@link Control} object to a DOM element.
|
||||
*
|
||||
* Used internally by Angular forms.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class NgControl extends AbstractControlDirective {
|
||||
name: string = null;
|
||||
valueAccessor: ControlValueAccessor = null;
|
||||
|
||||
get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
|
||||
get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }
|
||||
|
||||
abstract viewToModelUpdate(newValue: any): void;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import {Directive, Self} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
import {NgControl} from './ng_control';
|
||||
|
||||
|
||||
/**
|
||||
* Directive automatically applied to Angular forms that sets CSS classes
|
||||
* based on control status (valid/invalid/dirty/etc).
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[formControlName],[ngModel],[formControl]',
|
||||
host: {
|
||||
'[class.ng-untouched]': 'ngClassUntouched',
|
||||
'[class.ng-touched]': 'ngClassTouched',
|
||||
'[class.ng-pristine]': 'ngClassPristine',
|
||||
'[class.ng-dirty]': 'ngClassDirty',
|
||||
'[class.ng-valid]': 'ngClassValid',
|
||||
'[class.ng-invalid]': 'ngClassInvalid'
|
||||
}
|
||||
})
|
||||
export class NgControlStatus {
|
||||
private _cd: NgControl;
|
||||
|
||||
constructor(@Self() cd: NgControl) { this._cd = cd; }
|
||||
|
||||
get ngClassUntouched(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.untouched : false;
|
||||
}
|
||||
get ngClassTouched(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.touched : false;
|
||||
}
|
||||
get ngClassPristine(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.pristine : false;
|
||||
}
|
||||
get ngClassDirty(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.dirty : false;
|
||||
}
|
||||
get ngClassValid(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.valid : false;
|
||||
}
|
||||
get ngClassInvalid(): boolean {
|
||||
return isPresent(this._cd.control) ? !this._cd.control.valid : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import {Directive, Inject, Optional, Self, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter, ObservableWrapper, PromiseWrapper} from '../facade/async';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {AbstractControl, FormControl, FormGroup} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgModelGroup} from './ng_model_group';
|
||||
import {composeAsyncValidators, composeValidators, setUpControl, setUpFormGroup} from './shared';
|
||||
|
||||
export const formDirectiveProvider: any =
|
||||
/*@ts2dart_const*/ {provide: ControlContainer, useExisting: forwardRef(() => NgForm)};
|
||||
|
||||
/**
|
||||
* If `NgForm` is bound in a component, `<form>` elements in that component will be
|
||||
* upgraded to use the Angular form system.
|
||||
*
|
||||
* ### Typical Use
|
||||
*
|
||||
* Include `FORM_DIRECTIVES` in the `directives` section of a {@link View} annotation
|
||||
* to use `NgForm` and its associated controls.
|
||||
*
|
||||
* ### Structure
|
||||
*
|
||||
* An Angular form is a collection of `FormControl`s in some hierarchy.
|
||||
* `FormControl`s can be at the top level or can be organized in `FormGroup`s
|
||||
* or `FormArray`s. This hierarchy is reflected in the form's `value`, a
|
||||
* JSON object that mirrors the form structure.
|
||||
*
|
||||
* ### Submission
|
||||
*
|
||||
* The `ngSubmit` event signals when the user triggers a form submission.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <p>Submit the form to see the data object Angular builds</p>
|
||||
* <h2>NgForm demo</h2>
|
||||
* <form #f="ngForm" (ngSubmit)="onSubmit(f.value)">
|
||||
* <h3>Control group: credentials</h3>
|
||||
* <div ngModelGroup="credentials">
|
||||
* <p>Login: <input type="text" name="login" ngModel></p>
|
||||
* <p>Password: <input type="password" name="password" ngModel></p>
|
||||
* </div>
|
||||
* <h3>Control group: person</h3>
|
||||
* <div ngModelGroup="person">
|
||||
* <p>First name: <input type="text" name="firstName" ngModel></p>
|
||||
* <p>Last name: <input type="text" name="lastName" ngModel></p>
|
||||
* </div>
|
||||
* <button type="submit">Submit Form</button>
|
||||
* <p>Form data submitted:</p>
|
||||
* </form>
|
||||
* <pre>{{data}}</pre>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: []
|
||||
* })
|
||||
* export class App {
|
||||
* constructor() {}
|
||||
*
|
||||
* data: string;
|
||||
*
|
||||
* onSubmit(data) {
|
||||
* this.data = JSON.stringify(data, null, 2);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'form:not([ngNoForm]):not([formGroup]),ngForm,[ngForm]',
|
||||
providers: [formDirectiveProvider],
|
||||
host: {
|
||||
'(submit)': 'onSubmit()',
|
||||
},
|
||||
outputs: ['ngSubmit'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgForm extends ControlContainer implements Form {
|
||||
private _submitted: boolean = false;
|
||||
|
||||
form: FormGroup;
|
||||
ngSubmit = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
|
||||
super();
|
||||
this.form = new FormGroup(
|
||||
{}, null, composeValidators(validators), composeAsyncValidators(asyncValidators));
|
||||
}
|
||||
|
||||
get submitted(): boolean { return this._submitted; }
|
||||
|
||||
get formDirective(): Form { return this; }
|
||||
|
||||
get control(): FormGroup { return this.form; }
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }
|
||||
|
||||
addControl(dir: NgControl): FormControl {
|
||||
const ctrl = new FormControl();
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
const container = this._findContainer(dir.path);
|
||||
setUpControl(ctrl, dir);
|
||||
container.registerControl(dir.name, ctrl);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
});
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
getControl(dir: NgControl): FormControl { return <FormControl>this.form.find(dir.path); }
|
||||
|
||||
removeControl(dir: NgControl): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addFormGroup(dir: NgModelGroup): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
var group = new FormGroup({});
|
||||
setUpFormGroup(group, dir);
|
||||
container.registerControl(dir.name, group);
|
||||
group.updateValueAndValidity({emitEvent: false});
|
||||
});
|
||||
}
|
||||
|
||||
removeFormGroup(dir: NgModelGroup): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getFormGroup(dir: NgModelGroup): FormGroup { return <FormGroup>this.form.find(dir.path); }
|
||||
|
||||
updateModel(dir: NgControl, value: any): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
var ctrl = <FormControl>this.form.find(dir.path);
|
||||
ctrl.updateValue(value);
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
this._submitted = true;
|
||||
ObservableWrapper.callEmit(this.ngSubmit, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_findContainer(path: string[]): FormGroup {
|
||||
path.pop();
|
||||
return ListWrapper.isEmpty(path) ? this.form : <FormGroup>this.form.find(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import {Directive, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter, ObservableWrapper} from '../facade/async';
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
import {FormControl} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
export const formControlBinding: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgModel)
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds a domain model to a form control.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* `ngModel` binds an existing domain model to a form control. For a
|
||||
* two-way binding, use `[(ngModel)]` to ensure the model updates in
|
||||
* both directions.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "search-comp",
|
||||
* directives: [],
|
||||
* template: `<input type='text' [(ngModel)]="searchQuery">`
|
||||
* })
|
||||
* class SearchComp {
|
||||
* searchQuery: string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngModel]:not([formControlName]):not([formControl])',
|
||||
providers: [formControlBinding],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgModel extends NgControl implements OnChanges,
|
||||
OnDestroy {
|
||||
/** @internal */
|
||||
_control: FormControl;
|
||||
/** @internal */
|
||||
_added = false;
|
||||
viewModel: any;
|
||||
|
||||
@Input('ngModel') model: any;
|
||||
@Input() name: string;
|
||||
@Input('ngModelOptions') options: {name?: string};
|
||||
@Output('ngModelChange') update = new EventEmitter();
|
||||
|
||||
constructor(@Optional() @Host() private _parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
if (!this._parent) this._control = new FormControl();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this._checkName();
|
||||
if (!this._added) this._addControl();
|
||||
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this._control.updateValue(this.model);
|
||||
this.viewModel = this.model;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this.formDirective && this.formDirective.removeControl(this); }
|
||||
|
||||
get control(): FormControl { return this._control; }
|
||||
|
||||
get path(): string[] {
|
||||
return this._parent ? controlPath(this.name, this._parent) : [];
|
||||
}
|
||||
|
||||
get formDirective(): any { return this._parent ? this._parent.formDirective : null; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
ObservableWrapper.callEmit(this.update, newValue);
|
||||
}
|
||||
|
||||
private _addControl(): void {
|
||||
this._control = this.formDirective ? this.formDirective.addControl(this) :
|
||||
this._addStandaloneControl();
|
||||
this._added = true;
|
||||
}
|
||||
|
||||
private _addStandaloneControl(): FormControl {
|
||||
setUpControl(this._control, this);
|
||||
this._control.updateValueAndValidity({emitEvent: false});
|
||||
return this._control;
|
||||
}
|
||||
|
||||
private _checkName() {
|
||||
if (this.options && this.options.name) this.name = this.options.name;
|
||||
|
||||
if (this._parent && !this.name) {
|
||||
throw new BaseException(
|
||||
`Name attribute must be set if ngModel is used within a form.
|
||||
Example: <input [(ngModel)]="person.firstName" name="first">`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import {Directive, Host, Inject, Input, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
|
||||
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {AbstractFormGroupDirective} from './abstract_form_group_directive';
|
||||
import {ControlContainer} from './control_container';
|
||||
|
||||
export const modelGroupProvider: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgModelGroup)
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and binds a model group to a DOM element.
|
||||
*
|
||||
* This directive can only be used as a child of {@link NgForm}.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Angular forms Example</h2>
|
||||
* <form #f="ngForm">
|
||||
* <div ngModelGroup="name" #mgName="ngModelGroup">
|
||||
* <h3>Enter your name:</h3>
|
||||
* <p>First: <input name="first" ngModel required></p>
|
||||
* <p>Middle: <input name="middle" ngModel></p>
|
||||
* <p>Last: <input name="last" ngModel required></p>
|
||||
* </div>
|
||||
* <h3>Name value:</h3>
|
||||
* <pre>{{ mgName | json }}</pre>
|
||||
* <p>Name is {{mgName?.valid ? "valid" : "invalid"}}</p>
|
||||
* <h3>What's your favorite food?</h3>
|
||||
* <p><input name="food" ngModel></p>
|
||||
* <h3>Form value</h3>
|
||||
* <pre>{{ f | json }}</pre>
|
||||
* </form>
|
||||
* </div>
|
||||
* `
|
||||
* })
|
||||
* export class App {}
|
||||
* ```
|
||||
*
|
||||
* This example declares a model group for a user's name. The value and validation state of
|
||||
* this group can be accessed separately from the overall form.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngModelGroup]', providers: [modelGroupProvider], exportAs: 'ngModelGroup'})
|
||||
export class NgModelGroup extends AbstractFormGroupDirective implements OnInit, OnDestroy {
|
||||
@Input('ngModelGroup') name: string;
|
||||
|
||||
constructor(
|
||||
@Host() @SkipSelf() parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
|
||||
super();
|
||||
this._parent = parent;
|
||||
this._validators = validators;
|
||||
this._asyncValidators = asyncValidators;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
library angular2.core.forms.normalize_validators;
|
||||
|
||||
import 'package:angular2/src/common/forms/directives/validators.dart' show Validator;
|
||||
|
||||
Function normalizeValidator(dynamic validator){
|
||||
if (validator is Validator) {
|
||||
return (c) => validator.validate(c);
|
||||
} else {
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Function normalizeAsyncValidator(dynamic validator){
|
||||
if (validator is Validator) {
|
||||
return (c) => validator.validate(c);
|
||||
} else {
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import {AbstractControl} from '../model';
|
||||
|
||||
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
||||
|
||||
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c: AbstractControl) => (<Validator>validator).validate(c);
|
||||
} else {
|
||||
return <ValidatorFn>validator;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c: AbstractControl) => Promise.resolve((<Validator>validator).validate(c));
|
||||
} else {
|
||||
return <AsyncValidatorFn>validator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {NumberWrapper} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const NUMBER_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => NumberValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The accessor for writing a number value and listening to changes that is used by the
|
||||
* {@link NgModel}, {@link FormControlDirective}, and {@link FormControlName} directives.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* <input type="number" [(ngModel)]="age">
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]',
|
||||
host: {
|
||||
'(change)': 'onChange($event.target.value)',
|
||||
'(input)': 'onChange($event.target.value)',
|
||||
'(blur)': 'onTouched()'
|
||||
},
|
||||
providers: [NUMBER_VALUE_ACCESSOR]
|
||||
})
|
||||
export class NumberValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: number): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value);
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: number) => void): void {
|
||||
this.onChange = (value) => { fn(value == '' ? null : NumberWrapper.parseFloat(value)); };
|
||||
}
|
||||
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import {Directive, ElementRef, Injectable, Injector, Input, OnDestroy, OnInit, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
|
||||
export const RADIO_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => RadioControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal class used by Angular to uncheck radio buttons with the matching name.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RadioControlRegistry {
|
||||
private _accessors: any[] = [];
|
||||
|
||||
add(control: NgControl, accessor: RadioControlValueAccessor) {
|
||||
this._accessors.push([control, accessor]);
|
||||
}
|
||||
|
||||
remove(accessor: RadioControlValueAccessor) {
|
||||
var indexToRemove = -1;
|
||||
for (var i = 0; i < this._accessors.length; ++i) {
|
||||
if (this._accessors[i][1] === accessor) {
|
||||
indexToRemove = i;
|
||||
}
|
||||
}
|
||||
ListWrapper.removeAt(this._accessors, indexToRemove);
|
||||
}
|
||||
|
||||
select(accessor: RadioControlValueAccessor) {
|
||||
this._accessors.forEach((c) => {
|
||||
if (this._isSameGroup(c, accessor) && c[1] !== accessor) {
|
||||
c[1].fireUncheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _isSameGroup(
|
||||
controlPair: [NgControl, RadioControlValueAccessor], accessor: RadioControlValueAccessor) {
|
||||
return controlPair[0].control.root === accessor._control.control.root &&
|
||||
controlPair[1].name === accessor.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The value provided by the forms API for radio buttons.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class RadioButtonState {
|
||||
constructor(public checked: boolean, public value: string) {}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The accessor for writing a radio control value and listening to changes that is used by the
|
||||
* {@link NgModel}, {@link FormControlDirective}, and {@link FormControlName} directives.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* @Component({
|
||||
* template: `
|
||||
* <input type="radio" name="food" [(ngModel)]="foodChicken">
|
||||
* <input type="radio" name="food" [(ngModel)]="foodFish">
|
||||
* `
|
||||
* })
|
||||
* class FoodCmp {
|
||||
* foodChicken = new RadioButtonState(true, "chicken");
|
||||
* foodFish = new RadioButtonState(false, "fish");
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]',
|
||||
host: {'(change)': 'onChange()', '(blur)': 'onTouched()'},
|
||||
providers: [RADIO_VALUE_ACCESSOR]
|
||||
})
|
||||
export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||
OnDestroy, OnInit {
|
||||
/** @internal */
|
||||
_state: RadioButtonState;
|
||||
/** @internal */
|
||||
_control: NgControl;
|
||||
@Input() name: string;
|
||||
/** @internal */
|
||||
_fn: Function;
|
||||
onChange = () => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(
|
||||
private _renderer: Renderer, private _elementRef: ElementRef,
|
||||
private _registry: RadioControlRegistry, private _injector: Injector) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._control = this._injector.get(NgControl);
|
||||
this._registry.add(this._control, this);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._registry.remove(this); }
|
||||
|
||||
writeValue(value: any): void {
|
||||
this._state = value;
|
||||
if (isPresent(value) && value.checked) {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: any) => {}): void {
|
||||
this._fn = fn;
|
||||
this.onChange = () => {
|
||||
fn(new RadioButtonState(true, this._state.value));
|
||||
this._registry.select(this);
|
||||
};
|
||||
}
|
||||
|
||||
fireUncheck(): void { this._fn(new RadioButtonState(false, this._state.value)); }
|
||||
|
||||
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import {Directive, Inject, Input, OnChanges, Optional, Output, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter, ObservableWrapper} from '../../facade/async';
|
||||
import {StringMapWrapper} from '../../facade/collection';
|
||||
import {FormControl} from '../../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor';
|
||||
import {NgControl} from '../ng_control';
|
||||
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from '../validators';
|
||||
|
||||
export const formControlBinding: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => FormControlDirective)
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds an existing {@link FormControl} to a DOM element.
|
||||
**
|
||||
* 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.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Bind existing control example</h2>
|
||||
* <form>
|
||||
* <p>Element with existing control: <input type="text"
|
||||
* [formControl]="loginControl"></p>
|
||||
* <p>Value of existing control: {{loginControl.value}}</p>
|
||||
* </form>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: [REACTIVE_FORM_DIRECTIVES]
|
||||
* })
|
||||
* export class App {
|
||||
* loginControl: FormControl = new FormControl('');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ### ngModel
|
||||
*
|
||||
* We can also use `ngModel` to bind a domain model to the form.
|
||||
**
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "login-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: "<input type='text' [formControl]='loginControl' [(ngModel)]='login'>"
|
||||
* })
|
||||
* class LoginComp {
|
||||
* loginControl: FormControl = new FormControl('');
|
||||
* login:string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[formControl]', providers: [formControlBinding], exportAs: 'ngForm'})
|
||||
|
||||
export class FormControlDirective extends NgControl implements OnChanges {
|
||||
viewModel: any;
|
||||
|
||||
@Input('formControl') form: FormControl;
|
||||
@Input('ngModel') model: any;
|
||||
@Output('ngModelChange') update = new EventEmitter();
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this._isControlChanged(changes)) {
|
||||
setUpControl(this.form, this);
|
||||
this.form.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.form.updateValue(this.model);
|
||||
this.viewModel = this.model;
|
||||
}
|
||||
}
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
get control(): FormControl { return this.form; }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
ObservableWrapper.callEmit(this.update, newValue);
|
||||
}
|
||||
|
||||
private _isControlChanged(changes: {[key: string]: any}): boolean {
|
||||
return StringMapWrapper.contains(changes, 'form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import {Directive, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, SkipSelf, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter, ObservableWrapper} from '../../facade/async';
|
||||
import {FormControl} from '../../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
|
||||
|
||||
import {ControlContainer} from '../control_container';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor';
|
||||
import {NgControl} from '../ng_control';
|
||||
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from '../validators';
|
||||
|
||||
|
||||
export const controlNameBinding: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => FormControlName)
|
||||
};
|
||||
|
||||
/**
|
||||
* Syncs an existing form control with the specified name to a DOM element.
|
||||
*
|
||||
* This directive can only be used as a child of {@link FormGroupDirective}.
|
||||
|
||||
* ### Example
|
||||
*
|
||||
* In this example, we create the login and password controls.
|
||||
* We can work with each control separately: check its validity, get its value, listen to its
|
||||
* changes.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: "login-comp",
|
||||
* directives: [REACTIVE_FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <form [formGroup]="myForm" (submit)="onLogIn()">
|
||||
* Login <input type="text" formControlName="login">
|
||||
* <div *ngIf="!loginCtrl.valid">Login is invalid</div>
|
||||
* Password <input type="password" formControlName="password">
|
||||
* <button type="submit">Log in!</button>
|
||||
* </form>
|
||||
* `})
|
||||
* class LoginComp {
|
||||
* loginCtrl = new Control();
|
||||
* passwordCtrl = new Control();
|
||||
* myForm = new FormGroup({
|
||||
* login: loginCtrl,
|
||||
* password: passwordCtrl
|
||||
* });
|
||||
* onLogIn(): void {
|
||||
* // value === {login: 'some login', password: 'some password'}
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* TODO(kara): Remove ngModel example with reactive paradigm
|
||||
* We can also use ngModel to bind a domain model to the form, if you don't want to provide
|
||||
* individual init values to each control.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: "login-comp",
|
||||
* directives: [REACTIVE_FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <form [formGroup]="myForm" (submit)='onLogIn()'>
|
||||
* Login <input type='text' formControlName='login' [(ngModel)]="credentials.login">
|
||||
* Password <input type='password' formControlName='password'
|
||||
* [(ngModel)]="credentials.password">
|
||||
* <button type='submit'>Log in!</button>
|
||||
* </form>
|
||||
* `})
|
||||
* class LoginComp {
|
||||
* credentials: {login:string, password:string};
|
||||
* myForm = new FormGroup({
|
||||
* login: new Control(this.credentials.login),
|
||||
* password: new Control(this.credentials.password)
|
||||
* });
|
||||
*
|
||||
* onLogIn(): void {
|
||||
* // this.credentials.login === "some login"
|
||||
* // this.credentials.password === "some password"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[formControlName]', providers: [controlNameBinding]})
|
||||
export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||
/** @internal */
|
||||
viewModel: any;
|
||||
private _added = false;
|
||||
|
||||
@Input('formControlName') name: string;
|
||||
|
||||
// TODO(kara): Replace ngModel with reactive API
|
||||
@Input('ngModel') model: any;
|
||||
@Output('ngModelChange') update = new EventEmitter();
|
||||
|
||||
constructor(@Host() @SkipSelf() private _parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this._added) {
|
||||
this.formDirective.addControl(this);
|
||||
this._added = true;
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.viewModel = this.model;
|
||||
this.formDirective.updateModel(this, this.model);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this.formDirective.removeControl(this); }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
ObservableWrapper.callEmit(this.update, newValue);
|
||||
}
|
||||
|
||||
get path(): string[] { return controlPath(this.name, this._parent); }
|
||||
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
get control(): FormControl { return this.formDirective.getControl(this); }
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import {Directive, Inject, Input, OnChanges, Optional, Output, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter, ObservableWrapper} from '../../facade/async';
|
||||
import {ListWrapper, StringMapWrapper} from '../../facade/collection';
|
||||
import {BaseException} from '../../facade/exceptions';
|
||||
import {isBlank} from '../../facade/lang';
|
||||
import {FormControl, FormGroup} from '../../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../../validators';
|
||||
import {ControlContainer} from '../control_container';
|
||||
import {Form} from '../form_interface';
|
||||
import {NgControl} from '../ng_control';
|
||||
import {composeAsyncValidators, composeValidators, setUpControl, setUpFormGroup} from '../shared';
|
||||
|
||||
import {FormGroupName} from './form_group_name';
|
||||
|
||||
export const formDirectiveProvider: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => FormGroupDirective)
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds an existing form group to a DOM element.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/jqrVirudY8anJxTMUjTP?p=preview))
|
||||
*
|
||||
* In this example, we bind the form group to the form element, and we bind the login and
|
||||
* password controls to the login and password elements.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Binding an existing form group</h2>
|
||||
* <form [formGroup]="loginForm">
|
||||
* <p>Login: <input type="text" formControlName="login"></p>
|
||||
* <p>Password: <input type="password" formControlName="password"></p>
|
||||
* </form>
|
||||
* <p>Value:</p>
|
||||
* <pre>{{value}}</pre>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: [REACTIVE_FORM_DIRECTIVES]
|
||||
* })
|
||||
* export class App {
|
||||
* loginForm: FormGroup;
|
||||
*
|
||||
* constructor() {
|
||||
* this.loginForm = new FormGroup({
|
||||
* login: new FormControl(""),
|
||||
* password: new FormControl("")
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* get value(): string {
|
||||
* return JSON.stringify(this.loginForm.value, null, 2);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* We can also use ngModel to bind a domain model to the form.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "login-comp",
|
||||
* directives: [REACTIVE_FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <form [formGroup]='loginForm'>
|
||||
* Login <input type='text' formControlName='login' [(ngModel)]='credentials.login'>
|
||||
* Password <input type='password' formControlName='password'
|
||||
* [(ngModel)]='credentials.password'>
|
||||
* <button (click)="onLogin()">Login</button>
|
||||
* </form>`
|
||||
* })
|
||||
* class LoginComp {
|
||||
* credentials: {login: string, password: string};
|
||||
* loginForm: FormGroup;
|
||||
*
|
||||
* constructor() {
|
||||
* this.loginForm = new FormGroup({
|
||||
* login: new FormControl(""),
|
||||
* password: new FormControl("")
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* onLogin(): void {
|
||||
* // this.credentials.login === 'some login'
|
||||
* // this.credentials.password === 'some password'
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[formGroup]',
|
||||
providers: [formDirectiveProvider],
|
||||
host: {'(submit)': 'onSubmit()'},
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class FormGroupDirective extends ControlContainer implements Form,
|
||||
OnChanges {
|
||||
private _submitted: boolean = false;
|
||||
directives: NgControl[] = [];
|
||||
|
||||
@Input('formGroup') form: FormGroup = null;
|
||||
@Output() ngSubmit = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this._checkFormPresent();
|
||||
if (StringMapWrapper.contains(changes, 'form')) {
|
||||
var sync = composeValidators(this._validators);
|
||||
this.form.validator = Validators.compose([this.form.validator, sync]);
|
||||
|
||||
var async = composeAsyncValidators(this._asyncValidators);
|
||||
this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]);
|
||||
|
||||
this.form.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
}
|
||||
|
||||
this._updateDomValue();
|
||||
}
|
||||
|
||||
get submitted(): boolean { return this._submitted; }
|
||||
|
||||
get formDirective(): Form { return this; }
|
||||
|
||||
get control(): FormGroup { return this.form; }
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
addControl(dir: NgControl): FormControl {
|
||||
const ctrl: any = this.form.find(dir.path);
|
||||
setUpControl(ctrl, dir);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
this.directives.push(dir);
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
getControl(dir: NgControl): FormControl { return <FormControl>this.form.find(dir.path); }
|
||||
|
||||
removeControl(dir: NgControl): void { ListWrapper.remove(this.directives, dir); }
|
||||
|
||||
addFormGroup(dir: FormGroupName) {
|
||||
var ctrl: any = this.form.find(dir.path);
|
||||
setUpFormGroup(ctrl, dir);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
|
||||
removeFormGroup(dir: FormGroupName) {}
|
||||
|
||||
getFormGroup(dir: FormGroupName): FormGroup { return <FormGroup>this.form.find(dir.path); }
|
||||
|
||||
updateModel(dir: NgControl, value: any): void {
|
||||
var ctrl = <FormControl>this.form.find(dir.path);
|
||||
ctrl.updateValue(value);
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
this._submitted = true;
|
||||
ObservableWrapper.callEmit(this.ngSubmit, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateDomValue() {
|
||||
this.directives.forEach(dir => {
|
||||
var ctrl: any = this.form.find(dir.path);
|
||||
dir.valueAccessor.writeValue(ctrl.value);
|
||||
});
|
||||
}
|
||||
|
||||
private _checkFormPresent() {
|
||||
if (isBlank(this.form)) {
|
||||
throw new BaseException(`formGroup expects a FormGroup instance. Please pass one in.
|
||||
Example: <form [formGroup]="myFormGroup">
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import {Directive, Host, Inject, Input, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
|
||||
import {AbstractFormGroupDirective} from '../abstract_form_group_directive';
|
||||
import {ControlContainer} from '../control_container';
|
||||
|
||||
export const formGroupNameProvider: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => FormGroupName)
|
||||
};
|
||||
|
||||
/**
|
||||
* Syncs an existing form group to a DOM element.
|
||||
*
|
||||
* This directive can only be used as a child of {@link FormGroupDirective}.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Angular FormGroup Example</h2>
|
||||
* <form [formGroup]="myForm">
|
||||
* <div formGroupName="name">
|
||||
* <h3>Enter your name:</h3>
|
||||
* <p>First: <input formControlName="first"></p>
|
||||
* <p>Middle: <input formControlName="middle"></p>
|
||||
* <p>Last: <input formControlName="last"></p>
|
||||
* </div>
|
||||
* <h3>Name value:</h3>
|
||||
* <pre>{{ nameGroup | json }}</pre>
|
||||
* <p>Name is {{nameGroup?.valid ? "valid" : "invalid"}}</p>
|
||||
* <h3>What's your favorite food?</h3>
|
||||
* <p><input formControlName="food"></p>
|
||||
* <h3>Form value</h3>
|
||||
* <pre> {{ myForm | json }} </pre>
|
||||
* </form>
|
||||
* </div>
|
||||
* `
|
||||
* })
|
||||
* export class App {
|
||||
* nameGroup = new FormGroup({
|
||||
* first: new FormControl('', Validators.required),
|
||||
* middle: new FormControl(''),
|
||||
* last: new FormControl('', Validators.required)
|
||||
* });
|
||||
*
|
||||
* myForm = new FormGroup({
|
||||
* name: this.nameGroup,
|
||||
* food: new FormControl()
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This example syncs the form group for the user's name. The value and validation state of
|
||||
* this group can be accessed separately from the overall form.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[formGroupName]', providers: [formGroupNameProvider]})
|
||||
export class FormGroupName extends AbstractFormGroupDirective implements OnInit, OnDestroy {
|
||||
@Input('formGroupName') name: string;
|
||||
|
||||
constructor(
|
||||
@Host() @SkipSelf() parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
|
||||
super();
|
||||
this._parent = parent;
|
||||
this._validators = validators;
|
||||
this._asyncValidators = asyncValidators;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {MapWrapper} from '../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent, isPrimitive, looseIdentical} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const SELECT_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
function _buildValueString(id: string, value: any): string {
|
||||
if (isBlank(id)) return `${value}`;
|
||||
if (!isPrimitive(value)) value = 'Object';
|
||||
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
|
||||
}
|
||||
|
||||
function _extractId(valueString: string): string {
|
||||
return valueString.split(':')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* The accessor for writing a value and listening to changes on a select element.
|
||||
*
|
||||
* Note: We have to listen to the 'change' event because 'input' events aren't fired
|
||||
* for selects in Firefox and IE:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
|
||||
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045/
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]',
|
||||
host: {'(change)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
|
||||
providers: [SELECT_VALUE_ACCESSOR]
|
||||
})
|
||||
export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
value: any;
|
||||
/** @internal */
|
||||
_optionMap: Map<string, any> = new Map<string, any>();
|
||||
/** @internal */
|
||||
_idCounter: number = 0;
|
||||
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value;
|
||||
var valueString = _buildValueString(this._getOptionId(value), value);
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', valueString);
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => any): void {
|
||||
this.onChange = (valueString: string) => {
|
||||
this.value = valueString;
|
||||
fn(this._getOptionValue(valueString));
|
||||
};
|
||||
}
|
||||
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
|
||||
|
||||
/** @internal */
|
||||
_registerOption(): string { return (this._idCounter++).toString(); }
|
||||
|
||||
/** @internal */
|
||||
_getOptionId(value: any): string {
|
||||
for (let id of MapWrapper.keys(this._optionMap)) {
|
||||
if (looseIdentical(this._optionMap.get(id), value)) return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getOptionValue(valueString: string): any {
|
||||
let value = this._optionMap.get(_extractId(valueString));
|
||||
return isPresent(value) ? value : valueString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks `<option>` as dynamic, so Angular can be notified when options change.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <select name="city" ngModel>
|
||||
* <option *ngFor="let c of cities" [value]="c"></option>
|
||||
* </select>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: 'option'})
|
||||
export class NgSelectOption implements OnDestroy {
|
||||
id: string;
|
||||
|
||||
constructor(
|
||||
private _element: ElementRef, private _renderer: Renderer,
|
||||
@Optional() @Host() private _select: SelectControlValueAccessor) {
|
||||
if (isPresent(this._select)) this.id = this._select._registerOption();
|
||||
}
|
||||
|
||||
@Input('ngValue')
|
||||
set ngValue(value: any) {
|
||||
if (this._select == null) return;
|
||||
this._select._optionMap.set(this.id, value);
|
||||
this._setElementValue(_buildValueString(this.id, value));
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
|
||||
@Input('value')
|
||||
set value(value: any) {
|
||||
this._setElementValue(value);
|
||||
if (isPresent(this._select)) this._select.writeValue(this._select.value);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setElementValue(value: string): void {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (isPresent(this._select)) {
|
||||
this._select._optionMap.delete(this.id);
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {MapWrapper} from '../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
const SELECT_MULTIPLE_VALUE_ACCESSOR = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
function _buildValueString(id: string, value: any): string {
|
||||
if (isBlank(id)) return `${value}`;
|
||||
if (isString(value)) value = `'${value}'`;
|
||||
if (!isPrimitive(value)) value = 'Object';
|
||||
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
|
||||
}
|
||||
|
||||
function _extractId(valueString: string): string {
|
||||
return valueString.split(':')[0];
|
||||
}
|
||||
|
||||
/** Mock interface for HTML Options */
|
||||
interface HTMLOption {
|
||||
value: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
/** Mock interface for HTMLCollection */
|
||||
abstract class HTMLCollection {
|
||||
length: number;
|
||||
abstract item(_: number): HTMLOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* The accessor for writing a value and listening to changes on a select element.
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'select[multiple][formControlName],select[multiple][formControl],select[multiple][ngModel]',
|
||||
host: {'(input)': 'onChange($event.target)', '(blur)': 'onTouched()'},
|
||||
providers: [SELECT_MULTIPLE_VALUE_ACCESSOR]
|
||||
})
|
||||
export class SelectMultipleControlValueAccessor implements ControlValueAccessor {
|
||||
value: any;
|
||||
/** @internal */
|
||||
_optionMap: Map<string, NgSelectMultipleOption> = new Map<string, NgSelectMultipleOption>();
|
||||
/** @internal */
|
||||
_idCounter: number = 0;
|
||||
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value;
|
||||
if (value == null) return;
|
||||
let values: Array<any> = <Array<any>>value;
|
||||
// convert values to ids
|
||||
let ids = values.map((v) => this._getOptionId(v));
|
||||
this._optionMap.forEach((opt, o) => { opt._setSelected(ids.indexOf(o.toString()) > -1); });
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => any): void {
|
||||
this.onChange = (_: any) => {
|
||||
let selected: Array<any> = [];
|
||||
if (_.hasOwnProperty('selectedOptions')) {
|
||||
let options: HTMLCollection = _.selectedOptions;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
let opt: any = options.item(i);
|
||||
let val: any = this._getOptionValue(opt.value);
|
||||
selected.push(val);
|
||||
}
|
||||
}
|
||||
// Degrade on IE
|
||||
else {
|
||||
let options: HTMLCollection = <HTMLCollection>_.options;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
let opt: HTMLOption = options.item(i);
|
||||
if (opt.selected) {
|
||||
let val: any = this._getOptionValue(opt.value);
|
||||
selected.push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn(selected);
|
||||
};
|
||||
}
|
||||
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
|
||||
|
||||
/** @internal */
|
||||
_registerOption(value: NgSelectMultipleOption): string {
|
||||
let id: string = (this._idCounter++).toString();
|
||||
this._optionMap.set(id, value);
|
||||
return id;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getOptionId(value: any): string {
|
||||
for (let id of MapWrapper.keys(this._optionMap)) {
|
||||
if (looseIdentical(this._optionMap.get(id)._value, value)) return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getOptionValue(valueString: string): any {
|
||||
let opt = this._optionMap.get(_extractId(valueString));
|
||||
return isPresent(opt) ? opt._value : valueString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks `<option>` as dynamic, so Angular can be notified when options change.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <select multiple name="city" ngModel>
|
||||
* <option *ngFor="let c of cities" [value]="c"></option>
|
||||
* </select>
|
||||
* ```
|
||||
*/
|
||||
@Directive({selector: 'option'})
|
||||
export class NgSelectMultipleOption implements OnDestroy {
|
||||
id: string;
|
||||
/** @internal */
|
||||
_value: any;
|
||||
|
||||
constructor(
|
||||
private _element: ElementRef, private _renderer: Renderer,
|
||||
@Optional() @Host() private _select: SelectMultipleControlValueAccessor) {
|
||||
if (isPresent(this._select)) {
|
||||
this.id = this._select._registerOption(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Input('ngValue')
|
||||
set ngValue(value: any) {
|
||||
if (this._select == null) return;
|
||||
this._value = value;
|
||||
this._setElementValue(_buildValueString(this.id, value));
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
|
||||
@Input('value')
|
||||
set value(value: any) {
|
||||
if (isPresent(this._select)) {
|
||||
this._value = value;
|
||||
this._setElementValue(_buildValueString(this.id, value));
|
||||
this._select.writeValue(this._select.value);
|
||||
} else {
|
||||
this._setElementValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setElementValue(value: string): void {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setSelected(selected: boolean) {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (isPresent(this._select)) {
|
||||
this._select._optionMap.delete(this.id);
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];
|
||||
@@ -0,0 +1,111 @@
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
import {hasConstructor, isBlank, isPresent, looseIdentical} from '../facade/lang';
|
||||
import {FormControl, FormGroup} from '../model';
|
||||
import {Validators} from '../validators';
|
||||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {AbstractFormGroupDirective} from './abstract_form_group_directive';
|
||||
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
|
||||
import {ControlContainer} from './control_container';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {DefaultValueAccessor} from './default_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator';
|
||||
import {NumberValueAccessor} from './number_value_accessor';
|
||||
import {RadioControlValueAccessor} from './radio_control_value_accessor';
|
||||
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
|
||||
export function controlPath(name: string, parent: ControlContainer): string[] {
|
||||
var p = ListWrapper.clone(parent.path);
|
||||
p.push(name);
|
||||
return p;
|
||||
}
|
||||
|
||||
export function setUpControl(control: FormControl, dir: NgControl): void {
|
||||
if (isBlank(control)) _throwError(dir, 'Cannot find control');
|
||||
if (isBlank(dir.valueAccessor)) _throwError(dir, 'No value accessor for');
|
||||
|
||||
control.validator = Validators.compose([control.validator, dir.validator]);
|
||||
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
||||
dir.valueAccessor.writeValue(control.value);
|
||||
|
||||
// view -> model
|
||||
dir.valueAccessor.registerOnChange((newValue: any) => {
|
||||
dir.viewToModelUpdate(newValue);
|
||||
control.updateValue(newValue, {emitModelToViewChange: false});
|
||||
control.markAsDirty();
|
||||
});
|
||||
|
||||
// model -> view
|
||||
control.registerOnChange((newValue: any) => dir.valueAccessor.writeValue(newValue));
|
||||
|
||||
// touched
|
||||
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
|
||||
}
|
||||
|
||||
export function setUpFormGroup(control: FormGroup, dir: AbstractFormGroupDirective) {
|
||||
if (isBlank(control)) _throwError(dir, 'Cannot find control');
|
||||
control.validator = Validators.compose([control.validator, dir.validator]);
|
||||
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
||||
}
|
||||
|
||||
function _throwError(dir: AbstractControlDirective, message: string): void {
|
||||
var path = dir.path.join(' -> ');
|
||||
throw new BaseException(`${message} '${path}'`);
|
||||
}
|
||||
|
||||
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): ValidatorFn {
|
||||
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
|
||||
}
|
||||
|
||||
export function composeAsyncValidators(validators: /* Array<Validator|Function> */ any[]):
|
||||
AsyncValidatorFn {
|
||||
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
|
||||
null;
|
||||
}
|
||||
|
||||
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
|
||||
if (!StringMapWrapper.contains(changes, 'model')) return false;
|
||||
var change = changes['model'];
|
||||
|
||||
if (change.isFirstChange()) return true;
|
||||
return !looseIdentical(viewModel, change.currentValue);
|
||||
}
|
||||
|
||||
// TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented
|
||||
export function selectValueAccessor(
|
||||
dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
|
||||
if (isBlank(valueAccessors)) return null;
|
||||
|
||||
var defaultAccessor: ControlValueAccessor;
|
||||
var builtinAccessor: ControlValueAccessor;
|
||||
var customAccessor: ControlValueAccessor;
|
||||
valueAccessors.forEach((v: ControlValueAccessor) => {
|
||||
if (hasConstructor(v, DefaultValueAccessor)) {
|
||||
defaultAccessor = v;
|
||||
|
||||
} else if (
|
||||
hasConstructor(v, CheckboxControlValueAccessor) || hasConstructor(v, NumberValueAccessor) ||
|
||||
hasConstructor(v, SelectControlValueAccessor) ||
|
||||
hasConstructor(v, RadioControlValueAccessor)) {
|
||||
if (isPresent(builtinAccessor))
|
||||
_throwError(dir, 'More than one built-in value accessor matches');
|
||||
builtinAccessor = v;
|
||||
|
||||
} else {
|
||||
if (isPresent(customAccessor))
|
||||
_throwError(dir, 'More than one custom value accessor matches');
|
||||
customAccessor = v;
|
||||
}
|
||||
});
|
||||
|
||||
if (isPresent(customAccessor)) return customAccessor;
|
||||
if (isPresent(builtinAccessor)) return builtinAccessor;
|
||||
if (isPresent(defaultAccessor)) return defaultAccessor;
|
||||
|
||||
_throwError(dir, 'No valid value accessor for');
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import {Attribute, Directive, forwardRef} from '@angular/core';
|
||||
|
||||
import {NumberWrapper} from '../facade/lang';
|
||||
import {AbstractControl} from '../model';
|
||||
import {NG_VALIDATORS, Validators} from '../validators';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface that can be implemented by classes that can act as validators.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```typescript
|
||||
* @Directive({
|
||||
* selector: '[custom-validator]',
|
||||
* providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
|
||||
* })
|
||||
* class CustomValidatorDirective implements Validator {
|
||||
* validate(c: Control): {[key: string]: any} {
|
||||
* return {"custom": true};
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface Validator { validate(c: AbstractControl): {[key: string]: any}; }
|
||||
|
||||
const REQUIRED = /*@ts2dart_const*/ Validators.required;
|
||||
|
||||
export const REQUIRED_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
provide: NG_VALIDATORS,
|
||||
useValue: REQUIRED,
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* A Directive that adds the `required` validator to any controls marked with the
|
||||
* `required` attribute, via the {@link NG_VALIDATORS} binding.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <input name="fullName" ngModel required>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[required][formControlName],[required][formControl],[required][ngModel]',
|
||||
providers: [REQUIRED_VALIDATOR]
|
||||
})
|
||||
export class RequiredValidator {
|
||||
}
|
||||
|
||||
export interface ValidatorFn { (c: AbstractControl): {[key: string]: any}; }
|
||||
export interface AsyncValidatorFn {
|
||||
(c: AbstractControl): any /*Promise<{[key: string]: any}>|Observable<{[key: string]: any}>*/;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provivder which adds {@link MinLengthValidator} to {@link NG_VALIDATORS}.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* {@example common/forms/ts/validators/validators.ts region='min'}
|
||||
*/
|
||||
export const MIN_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MinLengthValidator),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* A directive which installs the {@link MinLengthValidator} for any `formControlName`,
|
||||
* `formControl`, or control with `ngModel` that also has a `minlength` attribute.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[minlength][formControlName],[minlength][formControl],[minlength][ngModel]',
|
||||
providers: [MIN_LENGTH_VALIDATOR]
|
||||
})
|
||||
export class MinLengthValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute('minlength') minLength: string) {
|
||||
this._validator = Validators.minLength(NumberWrapper.parseInt(minLength, 10));
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider which adds {@link MaxLengthValidator} to {@link NG_VALIDATORS}.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* {@example common/forms/ts/validators/validators.ts region='max'}
|
||||
*/
|
||||
export const MAX_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MaxLengthValidator),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* A directive which installs the {@link MaxLengthValidator} for any `formControlName,
|
||||
* `formControl`,
|
||||
* or control with `ngModel` that also has a `maxlength` attribute.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]',
|
||||
providers: [MAX_LENGTH_VALIDATOR]
|
||||
})
|
||||
export class MaxLengthValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute('maxlength') maxLength: string) {
|
||||
this._validator = Validators.maxLength(NumberWrapper.parseInt(maxLength, 10));
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
||||
|
||||
export const PATTERN_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => PatternValidator),
|
||||
multi: true
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A Directive that adds the `pattern` validator to any controls marked with the
|
||||
* `pattern` attribute, via the {@link NG_VALIDATORS} binding. Uses attribute value
|
||||
* as the regex to validate Control value against. Follows pattern attribute
|
||||
* semantics; i.e. regex must match entire Control value.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <input [name]="fullName" pattern="[a-zA-Z ]*" ngModel>
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[pattern][formControlName],[pattern][formControl],[pattern][ngModel]',
|
||||
providers: [PATTERN_VALIDATOR]
|
||||
})
|
||||
export class PatternValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute('pattern') pattern: string) {
|
||||
this._validator = Validators.pattern(pattern);
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
library angular2.core.facade.async;
|
||||
|
||||
import 'dart:async';
|
||||
export 'dart:async' show Stream, StreamController, StreamSubscription;
|
||||
|
||||
export 'promise.dart';
|
||||
|
||||
class TimerWrapper {
|
||||
static Timer setTimeout(fn(), int millis) =>
|
||||
new Timer(new Duration(milliseconds: millis), fn);
|
||||
static void clearTimeout(Timer timer) {
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
static Timer setInterval(fn(), int millis) {
|
||||
var interval = new Duration(milliseconds: millis);
|
||||
return new Timer.periodic(interval, (Timer timer) {
|
||||
fn();
|
||||
});
|
||||
}
|
||||
|
||||
static void clearInterval(Timer timer) {
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableWrapper {
|
||||
static StreamSubscription subscribe/*<T>*/(Stream s, onNext(/*=T*/ value),
|
||||
[onError, onComplete]) {
|
||||
return s.listen(onNext,
|
||||
onError: onError, onDone: onComplete, cancelOnError: true);
|
||||
}
|
||||
|
||||
static bool isObservable(obs) {
|
||||
return obs is Stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether `emitter` has any subscribers listening to events.
|
||||
*/
|
||||
static bool hasSubscribers(EventEmitter emitter) {
|
||||
return emitter._controller.hasListener;
|
||||
}
|
||||
|
||||
static void dispose(StreamSubscription s) {
|
||||
s.cancel();
|
||||
}
|
||||
|
||||
@Deprecated('Use callEmit() instead')
|
||||
static void callNext(EventEmitter emitter, value) {
|
||||
emitter.add(value);
|
||||
}
|
||||
|
||||
static void callEmit(EventEmitter emitter, value) {
|
||||
emitter.add(value);
|
||||
}
|
||||
|
||||
static void callError(EventEmitter emitter, error) {
|
||||
emitter.addError(error);
|
||||
}
|
||||
|
||||
static void callComplete(EventEmitter emitter) {
|
||||
emitter.close();
|
||||
}
|
||||
|
||||
static Stream fromPromise(Future f) {
|
||||
return new Stream.fromFuture(f);
|
||||
}
|
||||
|
||||
static Future toPromise(Stream s) {
|
||||
return s.single;
|
||||
}
|
||||
}
|
||||
|
||||
class EventEmitter<T> extends Stream<T> {
|
||||
StreamController<T> _controller;
|
||||
|
||||
/// Creates an instance of [EventEmitter], which depending on [isAsync],
|
||||
/// delivers events synchronously or asynchronously.
|
||||
EventEmitter([bool isAsync = true]) {
|
||||
_controller = new StreamController<T>.broadcast(sync: !isAsync);
|
||||
}
|
||||
|
||||
StreamSubscription<T> listen(void onData(T event),
|
||||
{Function onError, void onDone(), bool cancelOnError}) {
|
||||
return _controller.stream.listen(onData,
|
||||
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
|
||||
void add(value) {
|
||||
_controller.add(value);
|
||||
}
|
||||
|
||||
void emit(value) {
|
||||
_controller.add(value);
|
||||
}
|
||||
|
||||
void addError(error) {
|
||||
_controller.addError(error);
|
||||
}
|
||||
|
||||
void close() {
|
||||
_controller.close();
|
||||
}
|
||||
}
|
||||
|
||||
//todo(robwormald): maybe fix in ts2dart?
|
||||
class Subject<T> extends Stream<T> {
|
||||
StreamController<T> _controller;
|
||||
|
||||
Subject([bool isAsync = true]) {
|
||||
_controller = new StreamController<T>.broadcast(sync: !isAsync);
|
||||
}
|
||||
|
||||
StreamSubscription<T> listen(void onData(T data),
|
||||
{Function onError, void onDone(), bool cancelOnError}) {
|
||||
return _controller.stream.listen(onData,
|
||||
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
|
||||
void add(value) {
|
||||
_controller.add(value);
|
||||
}
|
||||
|
||||
void addError(error) {
|
||||
_controller.addError(error);
|
||||
}
|
||||
|
||||
void close() {
|
||||
_controller.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {PromiseObservable} from 'rxjs/observable/PromiseObservable';
|
||||
import {toPromise} from 'rxjs/operator/toPromise';
|
||||
|
||||
import {global, noop} from './lang';
|
||||
|
||||
export {Observable} from 'rxjs/Observable';
|
||||
export {Subject} from 'rxjs/Subject';
|
||||
export {PromiseCompleter, PromiseWrapper} from './promise';
|
||||
|
||||
export class TimerWrapper {
|
||||
static setTimeout(fn: (...args: any[]) => void, millis: number): number {
|
||||
return global.setTimeout(fn, millis);
|
||||
}
|
||||
static clearTimeout(id: number): void { global.clearTimeout(id); }
|
||||
|
||||
static setInterval(fn: (...args: any[]) => void, millis: number): number {
|
||||
return global.setInterval(fn, millis);
|
||||
}
|
||||
static clearInterval(id: number): void { global.clearInterval(id); }
|
||||
}
|
||||
|
||||
export class ObservableWrapper {
|
||||
// TODO(vsavkin): when we use rxnext, try inferring the generic type from the first arg
|
||||
static subscribe<T>(
|
||||
emitter: any, onNext: (value: T) => void, onError?: (exception: any) => void,
|
||||
onComplete: () => void = () => {}): Object {
|
||||
onError = (typeof onError === 'function') && onError || noop;
|
||||
onComplete = (typeof onComplete === 'function') && onComplete || noop;
|
||||
return emitter.subscribe({next: onNext, error: onError, complete: onComplete});
|
||||
}
|
||||
|
||||
static isObservable(obs: any): boolean { return !!obs.subscribe; }
|
||||
|
||||
/**
|
||||
* Returns whether `obs` has any subscribers listening to events.
|
||||
*/
|
||||
static hasSubscribers(obs: EventEmitter<any>): boolean { return obs.observers.length > 0; }
|
||||
|
||||
static dispose(subscription: any) { subscription.unsubscribe(); }
|
||||
|
||||
/**
|
||||
* @deprecated - use callEmit() instead
|
||||
*/
|
||||
static callNext(emitter: EventEmitter<any>, value: any) { emitter.next(value); }
|
||||
|
||||
static callEmit(emitter: EventEmitter<any>, value: any) { emitter.emit(value); }
|
||||
|
||||
static callError(emitter: EventEmitter<any>, error: any) { emitter.error(error); }
|
||||
|
||||
static callComplete(emitter: EventEmitter<any>) { emitter.complete(); }
|
||||
|
||||
static fromPromise(promise: Promise<any>): Observable<any> {
|
||||
return PromiseObservable.create(promise);
|
||||
}
|
||||
|
||||
static toPromise(obj: Observable<any>): Promise<any> { return toPromise.call(obj); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Use by directives and components to emit custom Events.
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
* In the following example, `Zippy` alternatively emits `open` and `close` events when its
|
||||
* title gets clicked:
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: 'zippy',
|
||||
* template: `
|
||||
* <div class="zippy">
|
||||
* <div (click)="toggle()">Toggle</div>
|
||||
* <div [hidden]="!visible">
|
||||
* <ng-content></ng-content>
|
||||
* </div>
|
||||
* </div>`})
|
||||
* export class Zippy {
|
||||
* visible: boolean = true;
|
||||
* @Output() open: EventEmitter<any> = new EventEmitter();
|
||||
* @Output() close: EventEmitter<any> = new EventEmitter();
|
||||
*
|
||||
* toggle() {
|
||||
* this.visible = !this.visible;
|
||||
* if (this.visible) {
|
||||
* this.open.emit(null);
|
||||
* } else {
|
||||
* this.close.emit(null);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The events payload can be accessed by the parameter `$event` on the components output event
|
||||
* handler:
|
||||
*
|
||||
* ```
|
||||
* <zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>
|
||||
* ```
|
||||
*
|
||||
* Uses Rx.Observable but provides an adapter to make it work as specified here:
|
||||
* https://github.com/jhusain/observable-spec
|
||||
*
|
||||
* Once a reference implementation of the spec is available, switch to it.
|
||||
* @stable
|
||||
*/
|
||||
export class EventEmitter<T> extends Subject<T> {
|
||||
// TODO: mark this as internal once all the facades are gone
|
||||
// we can't mark it as internal now because EventEmitter exported via @angular/core would not
|
||||
// contain this property making it incompatible with all the code that uses EventEmitter via
|
||||
// facades, which are local to the code and do not have this property stripped.
|
||||
// tslint:disable-next-line
|
||||
__isAsync: boolean;
|
||||
|
||||
/**
|
||||
* Creates an instance of [EventEmitter], which depending on [isAsync],
|
||||
* delivers events synchronously or asynchronously.
|
||||
*/
|
||||
constructor(isAsync: boolean = false) {
|
||||
super();
|
||||
this.__isAsync = isAsync;
|
||||
}
|
||||
|
||||
emit(value: T) { super.next(value); }
|
||||
|
||||
/**
|
||||
* @deprecated - use .emit(value) instead
|
||||
*/
|
||||
next(value: any) { super.next(value); }
|
||||
|
||||
subscribe(generatorOrNext?: any, error?: any, complete?: any): any {
|
||||
let schedulerFn: any /** TODO #9100 */;
|
||||
let errorFn = (err: any): any /** TODO #9100 */ => null;
|
||||
let completeFn = (): any /** TODO #9100 */ => null;
|
||||
|
||||
if (generatorOrNext && typeof generatorOrNext === 'object') {
|
||||
schedulerFn = this.__isAsync ? (value: any /** TODO #9100 */) => {
|
||||
setTimeout(() => generatorOrNext.next(value));
|
||||
} : (value: any /** TODO #9100 */) => { generatorOrNext.next(value); };
|
||||
|
||||
if (generatorOrNext.error) {
|
||||
errorFn = this.__isAsync ? (err) => { setTimeout(() => generatorOrNext.error(err)); } :
|
||||
(err) => { generatorOrNext.error(err); };
|
||||
}
|
||||
|
||||
if (generatorOrNext.complete) {
|
||||
completeFn = this.__isAsync ? () => { setTimeout(() => generatorOrNext.complete()); } :
|
||||
() => { generatorOrNext.complete(); };
|
||||
}
|
||||
} else {
|
||||
schedulerFn = this.__isAsync ? (value: any /** TODO #9100 */) => {
|
||||
setTimeout(() => generatorOrNext(value));
|
||||
} : (value: any /** TODO #9100 */) => { generatorOrNext(value); };
|
||||
|
||||
if (error) {
|
||||
errorFn =
|
||||
this.__isAsync ? (err) => { setTimeout(() => error(err)); } : (err) => { error(err); };
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
completeFn =
|
||||
this.__isAsync ? () => { setTimeout(() => complete()); } : () => { complete(); };
|
||||
}
|
||||
}
|
||||
|
||||
return super.subscribe(schedulerFn, errorFn, completeFn);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
library angular.core.facade.base_wrapped_exception;
|
||||
|
||||
/**
|
||||
* A base class for the WrappedException that can be used to identify
|
||||
* a WrappedException from ExceptionHandler without adding circular
|
||||
* dependency.
|
||||
*/
|
||||
class BaseWrappedException extends Error {
|
||||
BaseWrappedException();
|
||||
|
||||
get originalException => null;
|
||||
get originalStack => null;
|
||||
|
||||
String get message => '';
|
||||
String get wrapperMessage => '';
|
||||
dynamic get context => null;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* A base class for the WrappedException that can be used to identify
|
||||
* a WrappedException from ExceptionHandler without adding circular
|
||||
* dependency.
|
||||
*/
|
||||
export class BaseWrappedException extends Error {
|
||||
constructor(message: string) { super(message); }
|
||||
|
||||
get wrapperMessage(): string { return ''; }
|
||||
get wrapperStack(): any { return null; }
|
||||
get originalException(): any { return null; }
|
||||
get originalStack(): any { return null; }
|
||||
get context(): any { return null; }
|
||||
get message(): string { return ''; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Dart version of browser APIs. This library depends on 'dart:html' and
|
||||
* therefore can only run in the browser.
|
||||
*/
|
||||
library angular2.src.facade.browser;
|
||||
|
||||
import 'dart:js' show context;
|
||||
import 'dart:html' show Location, window;
|
||||
|
||||
export 'dart:html'
|
||||
show
|
||||
document,
|
||||
window,
|
||||
Element,
|
||||
Node,
|
||||
MouseEvent,
|
||||
KeyboardEvent,
|
||||
Event,
|
||||
EventTarget,
|
||||
History,
|
||||
Location,
|
||||
EventListener;
|
||||
|
||||
Location get location => window.location;
|
||||
|
||||
final _gc = context['gc'];
|
||||
|
||||
void gc() {
|
||||
if (_gc != null) {
|
||||
_gc.apply(const []);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* JS version of browser APIs. This library can only run in the browser.
|
||||
*/
|
||||
var win = typeof window !== 'undefined' && window || <any>{};
|
||||
|
||||
export {win as window};
|
||||
export var document = win.document;
|
||||
export var location = win.location;
|
||||
export var gc = win['gc'] ? () => win['gc']() : (): any /** TODO #9100 */ => null;
|
||||
export var performance = win['performance'] ? win['performance'] : null;
|
||||
export const Event = win['Event'];
|
||||
export const MouseEvent = win['MouseEvent'];
|
||||
export const KeyboardEvent = win['KeyboardEvent'];
|
||||
export const EventTarget = win['EventTarget'];
|
||||
export const History = win['History'];
|
||||
export const Location = win['Location'];
|
||||
export const EventListener = win['EventListener'];
|
||||
@@ -0,0 +1,287 @@
|
||||
library facade.collection;
|
||||
|
||||
import 'dart:collection' show IterableBase;
|
||||
import 'dart:convert' show JsonEncoder;
|
||||
export 'dart:core' show Iterator, Map, List, Set;
|
||||
import 'dart:math' show max, min;
|
||||
|
||||
var jsonEncoder = new JsonEncoder();
|
||||
|
||||
class MapIterator extends Iterator<List> {
|
||||
final Iterator _iterator;
|
||||
final Map _map;
|
||||
|
||||
MapIterator(Map map)
|
||||
: _map = map,
|
||||
_iterator = map.keys.iterator;
|
||||
|
||||
bool moveNext() => _iterator.moveNext();
|
||||
|
||||
List get current {
|
||||
return _iterator.current != null
|
||||
? [_iterator.current, _map[_iterator.current]]
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class IterableMap extends IterableBase<List> {
|
||||
final Map _map;
|
||||
|
||||
IterableMap(Map map) : _map = map;
|
||||
|
||||
Iterator<List> get iterator => new MapIterator(_map);
|
||||
}
|
||||
|
||||
class MapWrapper {
|
||||
static Map/*<K,V>*/ clone/*<K,V>*/(Map/*<K,V>*/ m) => new Map.from(m);
|
||||
|
||||
// in opposite to JS, Dart does not create a new map
|
||||
static Map/*<K,V>*/ createFromStringMap/*<K,V>*/(Map/*<K,V>*/ m) => m;
|
||||
|
||||
// in opposite to JS, Dart does not create a new map
|
||||
static Map/*<K,V>*/ toStringMap/*<K,V>*/(Map/*<K,V>*/ m) => m;
|
||||
|
||||
static Map/*<K,V>*/ createFromPairs/*<K,V>*/(List pairs) => pairs.fold(/*<K,V>*/{}, (m, p) {
|
||||
m[p[0]] = p[1];
|
||||
return m;
|
||||
});
|
||||
|
||||
static void clearValues(Map m) {
|
||||
for (var k in m.keys) {
|
||||
m[k] = null;
|
||||
}
|
||||
}
|
||||
|
||||
static Iterable/*<List<dynamic>>*/ iterable/*<K,V>*/(Map/*<K,V>*/ m) => new IterableMap(m);
|
||||
static List/*<K>*/ keys/*<K,V>*/(Map/*<K,V>*/ m) => m.keys.toList();
|
||||
static List/*<V>*/ values/*<K,V>*/(Map/*<K,V>*/ m) => m.values.toList();
|
||||
}
|
||||
|
||||
class StringMapWrapper {
|
||||
static Map/*<String,V>*/ create/*<V>*/() => {};
|
||||
static bool contains/*<V>*/(Map/*<String,V>*/ map, String key) => map.containsKey(key);
|
||||
static get/*<V>*/(Map/*<String,V>*/ map, String key) => map[key];
|
||||
static void set/*<V>*/(Map/*<String,V>*/ map, String key, /*=V*/value) {
|
||||
map[key] = value;
|
||||
}
|
||||
|
||||
static void delete/*<V>*/(Map/*<String,V>*/ m, String k) {
|
||||
m.remove(k);
|
||||
}
|
||||
|
||||
static void forEach/*<V>*/(Map/*<String,V>*/ m, fn(/*=V*/ v, String k)) {
|
||||
m.forEach((k, v) => fn(v, k));
|
||||
}
|
||||
|
||||
static Map/*<String,V>*/ merge/*<V>*/(Map/*<String,V>*/ a, Map/*<String,V>*/ b) {
|
||||
var m = new Map/*<String,V>*/.from(a);
|
||||
if (b != null) {
|
||||
b.forEach((k, v) => m[k] = v);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static List<String> keys(Map<String, dynamic> a) {
|
||||
return a.keys.toList();
|
||||
}
|
||||
|
||||
static List values(Map<String, dynamic> a) {
|
||||
return a.values.toList();
|
||||
}
|
||||
|
||||
static bool isEmpty(Map m) => m.isEmpty;
|
||||
static bool equals/*<V>*/(Map/*<String,V>*/ m1, Map/*<String,V>*/ m2) {
|
||||
if (m1.length != m2.length) {
|
||||
return false;
|
||||
}
|
||||
for (var key in m1.keys) {
|
||||
if (m1[key] != m2[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
typedef bool Predicate<T>(T item);
|
||||
|
||||
class ListWrapper {
|
||||
static List/*<T>*/ clone/*<T>*/(Iterable/*<T>*/ l) => new List.from(l);
|
||||
static List/*<T>*/ createFixedSize/*<T>*/(int size) => new List(size);
|
||||
static List/*<T>*/ createGrowableSize/*<T>*/(int size) =>
|
||||
new List.generate(size, (_) => null, growable: true);
|
||||
|
||||
static bool contains(List m, k) => m.contains(k);
|
||||
static int indexOf(List list, value, [int startIndex = 0]) =>
|
||||
list.indexOf(value, startIndex);
|
||||
static int lastIndexOf(List list, value, [int startIndex = null]) =>
|
||||
list.lastIndexOf(value, startIndex == null ? list.length : startIndex);
|
||||
|
||||
static void forEachWithIndex/*<T>*/(List/*<T>*/ list, fn(/*=T*/ item, int index)) {
|
||||
for (var i = 0; i < list.length; ++i) {
|
||||
fn(list[i], i);
|
||||
}
|
||||
}
|
||||
static /*=T*/ first/*<T>*/(List/*<T>*/ list) => list.isEmpty ? null : list.first;
|
||||
static /*=T*/ last/*<T>*/(List/*<T>*/ list) => list.isEmpty ? null : list.last;
|
||||
static List/*<T>*/ reversed/*<T>*/(List/*<T>*/ list) => list.reversed.toList();
|
||||
static List/*<T>*/ concat/*<T>*/(List/*<T>*/ a, List/*<T>*/ b) {
|
||||
return new List()
|
||||
..length = a.length + b.length
|
||||
..setRange(0, a.length, a)
|
||||
..setRange(a.length, a.length + b.length, b);
|
||||
}
|
||||
|
||||
static void insert/*<T>*/(List/*<T>*/ l, int index, /*=T*/ value) {
|
||||
l.insert(index, value);
|
||||
}
|
||||
|
||||
static removeAt(List l, int index) => l.removeAt(index);
|
||||
static void removeAll/*<T>*/(List/*<T>*/ list, List/*<T>*/ items) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
list.remove(items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool remove/*<T>*/(List/*<T>*/ list, /*=T*/ item) => list.remove(item);
|
||||
static void clear(List l) {
|
||||
l.clear();
|
||||
}
|
||||
|
||||
static bool isEmpty(Iterable list) => list.isEmpty;
|
||||
static void fill/*<T>*/(List/*<T>*/ l, /*=T*/ value, [int start = 0, int end]) {
|
||||
l.fillRange(_startOffset(l, start), _endOffset(l, end), value);
|
||||
}
|
||||
|
||||
static bool equals/*<T>*/(List/*<T>*/ a, List/*<T>*/ b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static List/*<T>*/ slice/*<T>*/(List/*<T>*/ l, [int from = 0, int to]) {
|
||||
from = _startOffset(l, from);
|
||||
to = _endOffset(l, to);
|
||||
//in JS if from > to an empty array is returned
|
||||
if (to != null && from > to) {
|
||||
return [];
|
||||
}
|
||||
return l.sublist(from, to);
|
||||
}
|
||||
|
||||
static List/*<T>*/ splice/*<T>*/(List/*<T>*/ l, int from, int length) {
|
||||
from = _startOffset(l, from);
|
||||
var to = from + length;
|
||||
var sub = l.sublist(from, to);
|
||||
l.removeRange(from, to);
|
||||
return sub;
|
||||
}
|
||||
|
||||
static void sort/*<T>*/(List/*<T>*/ l, [int compareFn(/*=T*/a, /*=T*/b) = null]) {
|
||||
if (compareFn == null) {
|
||||
l.sort();
|
||||
} else {
|
||||
l.sort(compareFn);
|
||||
}
|
||||
}
|
||||
|
||||
static String toJSON(List l) {
|
||||
return jsonEncoder.convert(l);
|
||||
}
|
||||
|
||||
// JS splice, slice, fill functions can take start < 0 which indicates a position relative to
|
||||
// the end of the list
|
||||
static int _startOffset(List l, int start) {
|
||||
int len = l.length;
|
||||
return start < 0 ? max(len + start, 0) : min(start, len);
|
||||
}
|
||||
|
||||
// JS splice, slice, fill functions can take end < 0 which indicates a position relative to
|
||||
// the end of the list
|
||||
static int _endOffset(List l, int end) {
|
||||
int len = l.length;
|
||||
if (end == null) return len;
|
||||
return end < 0 ? max(len + end, 0) : min(end, len);
|
||||
}
|
||||
|
||||
static maximum(List l, fn(item)) {
|
||||
if (l.length == 0) {
|
||||
return null;
|
||||
}
|
||||
var solution = null;
|
||||
var maxValue = double.NEGATIVE_INFINITY;
|
||||
for (var index = 0; index < l.length; index++) {
|
||||
var candidate = l[index];
|
||||
if (candidate == null) {
|
||||
continue;
|
||||
}
|
||||
var candidateValue = fn(candidate);
|
||||
if (candidateValue > maxValue) {
|
||||
solution = candidate;
|
||||
maxValue = candidateValue;
|
||||
}
|
||||
}
|
||||
return solution;
|
||||
}
|
||||
|
||||
static List flatten(List l) {
|
||||
var target = [];
|
||||
_flattenArray(l, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
static addAll(List l, List source) {
|
||||
l.addAll(source);
|
||||
}
|
||||
}
|
||||
|
||||
List _flattenArray(List source, List target) {
|
||||
if (source != null) {
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
var item = source[i];
|
||||
if (item is List) {
|
||||
_flattenArray(item, target);
|
||||
} else {
|
||||
target.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
bool isListLikeIterable(obj) => obj is Iterable;
|
||||
|
||||
bool areIterablesEqual/*<T>*/(
|
||||
Iterable/*<T>*/ a,
|
||||
Iterable/*<T>*/ b,
|
||||
bool comparator(/*=T*/a, /*=T*/b))
|
||||
{
|
||||
var iterator1 = a.iterator;
|
||||
var iterator2 = b.iterator;
|
||||
|
||||
while (true) {
|
||||
var done1 = !iterator1.moveNext();
|
||||
var done2 = !iterator2.moveNext();
|
||||
if (done1 && done2) return true;
|
||||
if (done1 || done2) return false;
|
||||
if (!comparator(iterator1.current, iterator2.current)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
void iterateListLike/*<T>*/(Iterable/*<T>*/ iter, fn(/*=T*/item)) {
|
||||
assert(iter is Iterable);
|
||||
for (var item in iter) {
|
||||
fn(item);
|
||||
}
|
||||
}
|
||||
|
||||
class SetWrapper {
|
||||
static Set/*<T>*/ createFromList/*<T>*/(List/*<T>*/ l) => new Set.from(l);
|
||||
static bool has/*<T>*/(Set/*<T>*/ s, /*=T*/key) => s.contains(key);
|
||||
static void delete/*<T>*/(Set/*<T>*/ m, /*=T*/k) {
|
||||
m.remove(k);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
import {getSymbolIterator, global, isArray, isBlank, isJsObject, isPresent} from './lang';
|
||||
|
||||
export var Map = global.Map;
|
||||
export var Set = global.Set;
|
||||
|
||||
// Safari and Internet Explorer do not support the iterable parameter to the
|
||||
// Map constructor. We work around that by manually adding the items.
|
||||
var createMapFromPairs: {(pairs: any[]): Map<any, any>} = (function() {
|
||||
try {
|
||||
if (new Map(<any>[[1, 2]]).size === 1) {
|
||||
return function createMapFromPairs(pairs: any[]): Map<any, any> { return new Map(pairs); };
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
return function createMapAndPopulateFromPairs(pairs: any[]): Map<any, any> {
|
||||
var map = new Map();
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i];
|
||||
map.set(pair[0], pair[1]);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
})();
|
||||
var createMapFromMap: {(m: Map<any, any>): Map<any, any>} = (function() {
|
||||
try {
|
||||
if (new Map(<any>new Map())) {
|
||||
return function createMapFromMap(m: Map<any, any>): Map<any, any> { return new Map(<any>m); };
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
return function createMapAndPopulateFromMap(m: Map<any, any>): Map<any, any> {
|
||||
var map = new Map();
|
||||
m.forEach((v, k) => { map.set(k, v); });
|
||||
return map;
|
||||
};
|
||||
})();
|
||||
var _clearValues: {(m: Map<any, any>): void} = (function() {
|
||||
if ((<any>(new Map()).keys()).next) {
|
||||
return function _clearValues(m: Map<any, any>) {
|
||||
var keyIterator = m.keys();
|
||||
var k: any /** TODO #???? */;
|
||||
while (!((k = (<any>keyIterator).next()).done)) {
|
||||
m.set(k.value, null);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return function _clearValuesWithForeEach(m: Map<any, any>) {
|
||||
m.forEach((v, k) => { m.set(k, null); });
|
||||
};
|
||||
}
|
||||
})();
|
||||
// Safari doesn't implement MapIterator.next(), which is used is Traceur's polyfill of Array.from
|
||||
// TODO(mlaval): remove the work around once we have a working polyfill of Array.from
|
||||
var _arrayFromMap: {(m: Map<any, any>, getValues: boolean): any[]} = (function() {
|
||||
try {
|
||||
if ((<any>(new Map()).values()).next) {
|
||||
return function createArrayFromMap(m: Map<any, any>, getValues: boolean): any[] {
|
||||
return getValues ? (<any>Array).from(m.values()) : (<any>Array).from(m.keys());
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
return function createArrayFromMapWithForeach(m: Map<any, any>, getValues: boolean): any[] {
|
||||
var res = ListWrapper.createFixedSize(m.size), i = 0;
|
||||
m.forEach((v, k) => {
|
||||
res[i] = getValues ? v : k;
|
||||
i++;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
})();
|
||||
|
||||
export class MapWrapper {
|
||||
static clone<K, V>(m: Map<K, V>): Map<K, V> { return createMapFromMap(m); }
|
||||
static createFromStringMap<T>(stringMap: {[key: string]: T}): Map<string, T> {
|
||||
var result = new Map<string, T>();
|
||||
for (var prop in stringMap) {
|
||||
result.set(prop, stringMap[prop]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static toStringMap<T>(m: Map<string, T>): {[key: string]: T} {
|
||||
var r: {[key: string]: T} = {};
|
||||
m.forEach((v, k) => r[k] = v);
|
||||
return r;
|
||||
}
|
||||
static createFromPairs(pairs: any[]): Map<any, any> { return createMapFromPairs(pairs); }
|
||||
static clearValues(m: Map<any, any>) { _clearValues(m); }
|
||||
static iterable<T>(m: T): T { return m; }
|
||||
static keys<K>(m: Map<K, any>): K[] { return _arrayFromMap(m, false); }
|
||||
static values<V>(m: Map<any, V>): V[] { return _arrayFromMap(m, true); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps Javascript Objects
|
||||
*/
|
||||
export class StringMapWrapper {
|
||||
static create(): {[k: /*any*/ string]: any} {
|
||||
// Note: We are not using Object.create(null) here due to
|
||||
// performance!
|
||||
// http://jsperf.com/ng2-object-create-null
|
||||
return {};
|
||||
}
|
||||
static contains(map: {[key: string]: any}, key: string): boolean {
|
||||
return map.hasOwnProperty(key);
|
||||
}
|
||||
static get<V>(map: {[key: string]: V}, key: string): V {
|
||||
return map.hasOwnProperty(key) ? map[key] : undefined;
|
||||
}
|
||||
static set<V>(map: {[key: string]: V}, key: string, value: V) { map[key] = value; }
|
||||
static keys(map: {[key: string]: any}): string[] { return Object.keys(map); }
|
||||
static values<T>(map: {[key: string]: T}): T[] {
|
||||
return Object.keys(map).reduce((r, a) => {
|
||||
r.push(map[a]);
|
||||
return r;
|
||||
}, []);
|
||||
}
|
||||
static isEmpty(map: {[key: string]: any}): boolean {
|
||||
for (var prop in map) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static delete (map: {[key: string]: any}, key: string) { delete map[key]; }
|
||||
static forEach<K, V>(map: {[key: string]: V}, callback: /*(V, K) => void*/ Function) {
|
||||
for (var prop in map) {
|
||||
if (map.hasOwnProperty(prop)) {
|
||||
callback(map[prop], prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static merge<V>(m1: {[key: string]: V}, m2: {[key: string]: V}): {[key: string]: V} {
|
||||
var m: {[key: string]: V} = {};
|
||||
|
||||
for (var attr in m1) {
|
||||
if (m1.hasOwnProperty(attr)) {
|
||||
m[attr] = m1[attr];
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in m2) {
|
||||
if (m2.hasOwnProperty(attr)) {
|
||||
m[attr] = m2[attr];
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
static equals<V>(m1: {[key: string]: V}, m2: {[key: string]: V}): boolean {
|
||||
var k1 = Object.keys(m1);
|
||||
var k2 = Object.keys(m2);
|
||||
if (k1.length != k2.length) {
|
||||
return false;
|
||||
}
|
||||
var key: any /** TODO #???? */;
|
||||
for (var i = 0; i < k1.length; i++) {
|
||||
key = k1[i];
|
||||
if (m1[key] !== m2[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A boolean-valued function over a value, possibly including context information
|
||||
* regarding that value's position in an array.
|
||||
*/
|
||||
export interface Predicate<T> { (value: T, index?: number, array?: T[]): boolean; }
|
||||
|
||||
export class ListWrapper {
|
||||
// JS has no way to express a statically fixed size list, but dart does so we
|
||||
// keep both methods.
|
||||
static createFixedSize(size: number): any[] { return new Array(size); }
|
||||
static createGrowableSize(size: number): any[] { return new Array(size); }
|
||||
static clone<T>(array: T[]): T[] { return array.slice(0); }
|
||||
static forEachWithIndex<T>(array: T[], fn: (t: T, n: number) => void) {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
fn(array[i], i);
|
||||
}
|
||||
}
|
||||
static first<T>(array: T[]): T {
|
||||
if (!array) return null;
|
||||
return array[0];
|
||||
}
|
||||
static last<T>(array: T[]): T {
|
||||
if (!array || array.length == 0) return null;
|
||||
return array[array.length - 1];
|
||||
}
|
||||
static indexOf<T>(array: T[], value: T, startIndex: number = 0): number {
|
||||
return array.indexOf(value, startIndex);
|
||||
}
|
||||
static contains<T>(list: T[], el: T): boolean { return list.indexOf(el) !== -1; }
|
||||
static reversed<T>(array: T[]): T[] {
|
||||
var a = ListWrapper.clone(array);
|
||||
return a.reverse();
|
||||
}
|
||||
static concat(a: any[], b: any[]): any[] { return a.concat(b); }
|
||||
static insert<T>(list: T[], index: number, value: T) { list.splice(index, 0, value); }
|
||||
static removeAt<T>(list: T[], index: number): T {
|
||||
var res = list[index];
|
||||
list.splice(index, 1);
|
||||
return res;
|
||||
}
|
||||
static removeAll<T>(list: T[], items: T[]) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var index = list.indexOf(items[i]);
|
||||
list.splice(index, 1);
|
||||
}
|
||||
}
|
||||
static remove<T>(list: T[], el: T): boolean {
|
||||
var index = list.indexOf(el);
|
||||
if (index > -1) {
|
||||
list.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static clear(list: any[]) { list.length = 0; }
|
||||
static isEmpty(list: any[]): boolean { return list.length == 0; }
|
||||
static fill(list: any[], value: any, start: number = 0, end: number = null) {
|
||||
list.fill(value, start, end === null ? list.length : end);
|
||||
}
|
||||
static equals(a: any[], b: any[]): boolean {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static slice<T>(l: T[], from: number = 0, to: number = null): T[] {
|
||||
return l.slice(from, to === null ? undefined : to);
|
||||
}
|
||||
static splice<T>(l: T[], from: number, length: number): T[] { return l.splice(from, length); }
|
||||
static sort<T>(l: T[], compareFn?: (a: T, b: T) => number) {
|
||||
if (isPresent(compareFn)) {
|
||||
l.sort(compareFn);
|
||||
} else {
|
||||
l.sort();
|
||||
}
|
||||
}
|
||||
static toString<T>(l: T[]): string { return l.toString(); }
|
||||
static toJSON<T>(l: T[]): string { return JSON.stringify(l); }
|
||||
|
||||
static maximum<T>(list: T[], predicate: (t: T) => number): T {
|
||||
if (list.length == 0) {
|
||||
return null;
|
||||
}
|
||||
var solution: any /** TODO #???? */ = null;
|
||||
var maxValue = -Infinity;
|
||||
for (var index = 0; index < list.length; index++) {
|
||||
var candidate = list[index];
|
||||
if (isBlank(candidate)) {
|
||||
continue;
|
||||
}
|
||||
var candidateValue = predicate(candidate);
|
||||
if (candidateValue > maxValue) {
|
||||
solution = candidate;
|
||||
maxValue = candidateValue;
|
||||
}
|
||||
}
|
||||
return solution;
|
||||
}
|
||||
|
||||
static flatten<T>(list: Array<T|T[]>): T[] {
|
||||
var target: any[] /** TODO #???? */ = [];
|
||||
_flattenArray(list, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
static addAll<T>(list: Array<T>, source: Array<T>): void {
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
list.push(source[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _flattenArray(source: any[], target: any[]): any[] {
|
||||
if (isPresent(source)) {
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
var item = source[i];
|
||||
if (isArray(item)) {
|
||||
_flattenArray(item, target);
|
||||
} else {
|
||||
target.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
export function isListLikeIterable(obj: any): boolean {
|
||||
if (!isJsObject(obj)) return false;
|
||||
return isArray(obj) ||
|
||||
(!(obj instanceof Map) && // JS Map are iterables but return entries as [k, v]
|
||||
getSymbolIterator() in obj); // JS Iterable have a Symbol.iterator prop
|
||||
}
|
||||
|
||||
export function areIterablesEqual(a: any, b: any, comparator: Function): boolean {
|
||||
var iterator1 = a[getSymbolIterator()]();
|
||||
var iterator2 = b[getSymbolIterator()]();
|
||||
|
||||
while (true) {
|
||||
let item1 = iterator1.next();
|
||||
let item2 = iterator2.next();
|
||||
if (item1.done && item2.done) return true;
|
||||
if (item1.done || item2.done) return false;
|
||||
if (!comparator(item1.value, item2.value)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function iterateListLike(obj: any, fn: Function) {
|
||||
if (isArray(obj)) {
|
||||
for (var i = 0; i < obj.length; i++) {
|
||||
fn(obj[i]);
|
||||
}
|
||||
} else {
|
||||
var iterator = obj[getSymbolIterator()]();
|
||||
var item: any /** TODO #???? */;
|
||||
while (!((item = iterator.next()).done)) {
|
||||
fn(item.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safari and Internet Explorer do not support the iterable parameter to the
|
||||
// Set constructor. We work around that by manually adding the items.
|
||||
var createSetFromList: {(lst: any[]): Set<any>} = (function() {
|
||||
var test = new Set([1, 2, 3]);
|
||||
if (test.size === 3) {
|
||||
return function createSetFromList(lst: any[]): Set<any> { return new Set(lst); };
|
||||
} else {
|
||||
return function createSetAndPopulateFromList(lst: any[]): Set<any> {
|
||||
var res = new Set(lst);
|
||||
if (res.size !== lst.length) {
|
||||
for (var i = 0; i < lst.length; i++) {
|
||||
res.add(lst[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
})();
|
||||
export class SetWrapper {
|
||||
static createFromList<T>(lst: T[]): Set<T> { return createSetFromList(lst); }
|
||||
static has<T>(s: Set<T>, key: T): boolean { return s.has(key); }
|
||||
static delete<K>(m: Set<K>, k: K) { m.delete(k); }
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import {BaseWrappedException} from './base_wrapped_exception';
|
||||
import {isListLikeIterable} from './collection';
|
||||
import {isBlank, isPresent} from './lang';
|
||||
|
||||
class _ArrayLogger {
|
||||
res: any[] = [];
|
||||
log(s: any): void { this.res.push(s); }
|
||||
logError(s: any): void { this.res.push(s); }
|
||||
logGroup(s: any): void { this.res.push(s); }
|
||||
logGroupEnd(){};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a hook for centralized exception handling.
|
||||
*
|
||||
* The default implementation of `ExceptionHandler` prints error messages to the `Console`. To
|
||||
* intercept error handling,
|
||||
* write a custom exception handler that replaces this default as appropriate for your app.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```javascript
|
||||
*
|
||||
* class MyExceptionHandler implements ExceptionHandler {
|
||||
* call(error, stackTrace = null, reason = null) {
|
||||
* // do something with the exception
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* bootstrap(MyApp, {provide: ExceptionHandler, useClass: MyExceptionHandler}])
|
||||
*
|
||||
* ```
|
||||
* @stable
|
||||
*/
|
||||
export class ExceptionHandler {
|
||||
constructor(private _logger: any, private _rethrowException: boolean = true) {}
|
||||
|
||||
static exceptionToString(exception: any, stackTrace: any = null, reason: string = null): string {
|
||||
var l = new _ArrayLogger();
|
||||
var e = new ExceptionHandler(l, false);
|
||||
e.call(exception, stackTrace, reason);
|
||||
return l.res.join('\n');
|
||||
}
|
||||
|
||||
call(exception: any, stackTrace: any = null, reason: string = null): void {
|
||||
var originalException = this._findOriginalException(exception);
|
||||
var originalStack = this._findOriginalStack(exception);
|
||||
var context = this._findContext(exception);
|
||||
|
||||
this._logger.logGroup(`EXCEPTION: ${this._extractMessage(exception)}`);
|
||||
|
||||
if (isPresent(stackTrace) && isBlank(originalStack)) {
|
||||
this._logger.logError('STACKTRACE:');
|
||||
this._logger.logError(this._longStackTrace(stackTrace));
|
||||
}
|
||||
|
||||
if (isPresent(reason)) {
|
||||
this._logger.logError(`REASON: ${reason}`);
|
||||
}
|
||||
|
||||
if (isPresent(originalException)) {
|
||||
this._logger.logError(`ORIGINAL EXCEPTION: ${this._extractMessage(originalException)}`);
|
||||
}
|
||||
|
||||
if (isPresent(originalStack)) {
|
||||
this._logger.logError('ORIGINAL STACKTRACE:');
|
||||
this._logger.logError(this._longStackTrace(originalStack));
|
||||
}
|
||||
|
||||
if (isPresent(context)) {
|
||||
this._logger.logError('ERROR CONTEXT:');
|
||||
this._logger.logError(context);
|
||||
}
|
||||
|
||||
this._logger.logGroupEnd();
|
||||
|
||||
// We rethrow exceptions, so operations like 'bootstrap' will result in an error
|
||||
// when an exception happens. If we do not rethrow, bootstrap will always succeed.
|
||||
if (this._rethrowException) throw exception;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_extractMessage(exception: any): string {
|
||||
return exception instanceof BaseWrappedException ? exception.wrapperMessage :
|
||||
exception.toString();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_longStackTrace(stackTrace: any): any {
|
||||
return isListLikeIterable(stackTrace) ? (<any[]>stackTrace).join('\n\n-----async gap-----\n') :
|
||||
stackTrace.toString();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_findContext(exception: any): any {
|
||||
try {
|
||||
if (!(exception instanceof BaseWrappedException)) return null;
|
||||
return isPresent(exception.context) ? exception.context :
|
||||
this._findContext(exception.originalException);
|
||||
} catch (e) {
|
||||
// exception.context can throw an exception. if it happens, we ignore the context.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_findOriginalException(exception: any): any {
|
||||
if (!(exception instanceof BaseWrappedException)) return null;
|
||||
|
||||
var e = exception.originalException;
|
||||
while (e instanceof BaseWrappedException && isPresent(e.originalException)) {
|
||||
e = e.originalException;
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_findOriginalStack(exception: any): any {
|
||||
if (!(exception instanceof BaseWrappedException)) return null;
|
||||
|
||||
var e = exception;
|
||||
var stack = exception.originalStack;
|
||||
while (e instanceof BaseWrappedException && isPresent(e.originalException)) {
|
||||
e = e.originalException;
|
||||
if (e instanceof BaseWrappedException && isPresent(e.originalException)) {
|
||||
stack = e.originalStack;
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
library angular.core.facade.exceptions;
|
||||
|
||||
import 'base_wrapped_exception.dart';
|
||||
import 'exception_handler.dart';
|
||||
export 'exception_handler.dart';
|
||||
|
||||
class BaseException extends Error {
|
||||
final String _message;
|
||||
|
||||
BaseException([this._message]);
|
||||
|
||||
String get message => _message;
|
||||
|
||||
String toString() {
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
|
||||
class WrappedException extends BaseWrappedException {
|
||||
final dynamic _context;
|
||||
final String _wrapperMessage;
|
||||
final originalException;
|
||||
final originalStack;
|
||||
|
||||
WrappedException(
|
||||
[this._wrapperMessage,
|
||||
this.originalException,
|
||||
this.originalStack,
|
||||
this._context]);
|
||||
|
||||
String get message {
|
||||
return ExceptionHandler.exceptionToString(this);
|
||||
}
|
||||
|
||||
String toString() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
dynamic get context => _context;
|
||||
|
||||
String get wrapperMessage => _wrapperMessage;
|
||||
}
|
||||
|
||||
Error makeTypeError([String message = ""]) {
|
||||
return new BaseException(message);
|
||||
}
|
||||
|
||||
dynamic unimplemented() {
|
||||
throw new BaseException('unimplemented');
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import {BaseWrappedException} from './base_wrapped_exception';
|
||||
import {ExceptionHandler} from './exception_handler';
|
||||
|
||||
export {ExceptionHandler} from './exception_handler';
|
||||
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
export class BaseException extends Error {
|
||||
public stack: any;
|
||||
constructor(public message: string = '--') {
|
||||
super(message);
|
||||
this.stack = (<any>new Error(message)).stack;
|
||||
}
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an exception and provides additional context or information.
|
||||
* @stable
|
||||
*/
|
||||
export class WrappedException extends BaseWrappedException {
|
||||
private _wrapperStack: any;
|
||||
|
||||
constructor(
|
||||
private _wrapperMessage: string, private _originalException: any /** TODO #9100 */,
|
||||
private _originalStack?: any /** TODO #9100 */, private _context?: any /** TODO #9100 */) {
|
||||
super(_wrapperMessage);
|
||||
this._wrapperStack = (<any>new Error(_wrapperMessage)).stack;
|
||||
}
|
||||
|
||||
get wrapperMessage(): string { return this._wrapperMessage; }
|
||||
|
||||
get wrapperStack(): any { return this._wrapperStack; }
|
||||
|
||||
|
||||
get originalException(): any { return this._originalException; }
|
||||
|
||||
get originalStack(): any { return this._originalStack; }
|
||||
|
||||
|
||||
get context(): any { return this._context; }
|
||||
|
||||
get message(): string { return ExceptionHandler.exceptionToString(this); }
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
export function makeTypeError(message?: string): Error {
|
||||
return new TypeError(message);
|
||||
}
|
||||
|
||||
export function unimplemented(): any {
|
||||
throw new BaseException('unimplemented');
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
library facade.intl;
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String _normalizeLocale(String locale) => locale.replaceAll('-', '_');
|
||||
|
||||
enum NumberFormatStyle { Decimal, Percent, Currency }
|
||||
|
||||
class NumberFormatter {
|
||||
static String format(num number, String locale, NumberFormatStyle style,
|
||||
{int minimumIntegerDigits: 1,
|
||||
int minimumFractionDigits: 0,
|
||||
int maximumFractionDigits: 3,
|
||||
String currency,
|
||||
bool currencyAsSymbol: false}) {
|
||||
locale = _normalizeLocale(locale);
|
||||
NumberFormat formatter;
|
||||
switch (style) {
|
||||
case NumberFormatStyle.Decimal:
|
||||
formatter = new NumberFormat.decimalPattern(locale);
|
||||
break;
|
||||
case NumberFormatStyle.Percent:
|
||||
formatter = new NumberFormat.percentPattern(locale);
|
||||
break;
|
||||
case NumberFormatStyle.Currency:
|
||||
if (currencyAsSymbol) {
|
||||
// See https://github.com/dart-lang/intl/issues/59.
|
||||
throw new Exception(
|
||||
'Displaying currency as symbol is not supported.');
|
||||
}
|
||||
formatter = new NumberFormat.currencyPattern(locale, currency);
|
||||
break;
|
||||
}
|
||||
formatter.minimumIntegerDigits = minimumIntegerDigits;
|
||||
formatter.minimumFractionDigits = minimumFractionDigits;
|
||||
formatter.maximumFractionDigits = maximumFractionDigits;
|
||||
return formatter.format(number);
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatter {
|
||||
static RegExp _multiPartRegExp = new RegExp(r'^([yMdE]+)([Hjms]+)$');
|
||||
|
||||
static String format(DateTime date, String locale, String pattern) {
|
||||
locale = _normalizeLocale(locale);
|
||||
var formatter = new DateFormat(null, locale);
|
||||
var matches = _multiPartRegExp.firstMatch(pattern);
|
||||
if (matches != null) {
|
||||
// Support for patterns which have known date and time components.
|
||||
formatter.addPattern(matches[1]);
|
||||
formatter.addPattern(matches[2], ', ');
|
||||
} else {
|
||||
formatter.addPattern(pattern);
|
||||
}
|
||||
return formatter.format(date);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
export enum NumberFormatStyle {
|
||||
Decimal,
|
||||
Percent,
|
||||
Currency
|
||||
}
|
||||
|
||||
export class NumberFormatter {
|
||||
static format(
|
||||
num: number, locale: string, style: NumberFormatStyle,
|
||||
{minimumIntegerDigits = 1, minimumFractionDigits = 0, maximumFractionDigits = 3, currency,
|
||||
currencyAsSymbol = false}: {
|
||||
minimumIntegerDigits?: number,
|
||||
minimumFractionDigits?: number,
|
||||
maximumFractionDigits?: number,
|
||||
currency?: string,
|
||||
currencyAsSymbol?: boolean
|
||||
} = {}): string {
|
||||
var intlOptions: Intl.NumberFormatOptions = {
|
||||
minimumIntegerDigits: minimumIntegerDigits,
|
||||
minimumFractionDigits: minimumFractionDigits,
|
||||
maximumFractionDigits: maximumFractionDigits
|
||||
};
|
||||
intlOptions.style = NumberFormatStyle[style].toLowerCase();
|
||||
if (style == NumberFormatStyle.Currency) {
|
||||
intlOptions.currency = currency;
|
||||
intlOptions.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
|
||||
}
|
||||
return new Intl.NumberFormat(locale, intlOptions).format(num);
|
||||
}
|
||||
}
|
||||
var DATE_FORMATS_SPLIT =
|
||||
/((?:[^yMLdHhmsaZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|Z|G+|w+))(.*)/;
|
||||
|
||||
var PATTERN_ALIASES = {
|
||||
yMMMdjms: datePartGetterFactory(combine([
|
||||
digitCondition('year', 1),
|
||||
nameCondition('month', 3),
|
||||
digitCondition('day', 1),
|
||||
digitCondition('hour', 1),
|
||||
digitCondition('minute', 1),
|
||||
digitCondition('second', 1),
|
||||
])),
|
||||
yMdjm: datePartGetterFactory(combine([
|
||||
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
|
||||
digitCondition('hour', 1), digitCondition('minute', 1)
|
||||
])),
|
||||
yMMMMEEEEd: datePartGetterFactory(combine([
|
||||
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
|
||||
digitCondition('day', 1)
|
||||
])),
|
||||
yMMMMd: datePartGetterFactory(
|
||||
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
|
||||
yMMMd: datePartGetterFactory(
|
||||
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
|
||||
yMd: datePartGetterFactory(
|
||||
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
|
||||
jms: datePartGetterFactory(combine(
|
||||
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
|
||||
jm: datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
|
||||
};
|
||||
|
||||
var DATE_FORMATS = {
|
||||
yyyy: datePartGetterFactory(digitCondition('year', 4)),
|
||||
yy: datePartGetterFactory(digitCondition('year', 2)),
|
||||
y: datePartGetterFactory(digitCondition('year', 1)),
|
||||
MMMM: datePartGetterFactory(nameCondition('month', 4)),
|
||||
MMM: datePartGetterFactory(nameCondition('month', 3)),
|
||||
MM: datePartGetterFactory(digitCondition('month', 2)),
|
||||
M: datePartGetterFactory(digitCondition('month', 1)),
|
||||
LLLL: datePartGetterFactory(nameCondition('month', 4)),
|
||||
dd: datePartGetterFactory(digitCondition('day', 2)),
|
||||
d: datePartGetterFactory(digitCondition('day', 1)),
|
||||
HH: hourExtracter(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), false))),
|
||||
H: hourExtracter(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
|
||||
hh: hourExtracter(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), true))),
|
||||
h: hourExtracter(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
||||
jj: datePartGetterFactory(digitCondition('hour', 2)),
|
||||
j: datePartGetterFactory(digitCondition('hour', 1)),
|
||||
mm: datePartGetterFactory(digitCondition('minute', 2)),
|
||||
m: datePartGetterFactory(digitCondition('minute', 1)),
|
||||
ss: datePartGetterFactory(digitCondition('second', 2)),
|
||||
s: datePartGetterFactory(digitCondition('second', 1)),
|
||||
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
|
||||
// we can be just safely rely on using `sss` since we currently don't support single or two digit
|
||||
// fractions
|
||||
sss: datePartGetterFactory(digitCondition('second', 3)),
|
||||
EEEE: datePartGetterFactory(nameCondition('weekday', 4)),
|
||||
EEE: datePartGetterFactory(nameCondition('weekday', 3)),
|
||||
EE: datePartGetterFactory(nameCondition('weekday', 2)),
|
||||
E: datePartGetterFactory(nameCondition('weekday', 1)),
|
||||
a: hourClockExtracter(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
||||
Z: datePartGetterFactory({timeZoneName: 'long'}),
|
||||
z: datePartGetterFactory({timeZoneName: 'short'}),
|
||||
ww: datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
|
||||
// first Thursday of the year. not support ?
|
||||
w: datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
|
||||
// of the year not support ?
|
||||
G: datePartGetterFactory(nameCondition('era', 1)),
|
||||
GG: datePartGetterFactory(nameCondition('era', 2)),
|
||||
GGG: datePartGetterFactory(nameCondition('era', 3)),
|
||||
GGGG: datePartGetterFactory(nameCondition('era', 4))
|
||||
};
|
||||
|
||||
|
||||
function hourClockExtracter(inner: (date: Date, locale: string) => string): (
|
||||
date: Date, locale: string) => string {
|
||||
return function(date: Date, locale: string): string {
|
||||
var result = inner(date, locale);
|
||||
|
||||
return result.split(' ')[1];
|
||||
};
|
||||
}
|
||||
|
||||
function hourExtracter(inner: (date: Date, locale: string) => string): (
|
||||
date: Date, locale: string) => string {
|
||||
return function(date: Date, locale: string): string {
|
||||
var result = inner(date, locale);
|
||||
|
||||
return result.split(' ')[0];
|
||||
};
|
||||
}
|
||||
|
||||
function hour12Modify(
|
||||
options: Intl.DateTimeFormatOptions, value: boolean): Intl.DateTimeFormatOptions {
|
||||
options.hour12 = value;
|
||||
return options;
|
||||
}
|
||||
|
||||
function digitCondition(prop: string, len: number): Intl.DateTimeFormatOptions {
|
||||
var result = {};
|
||||
(result as any /** TODO #9100 */)[prop] = len == 2 ? '2-digit' : 'numeric';
|
||||
return result;
|
||||
}
|
||||
function nameCondition(prop: string, len: number): Intl.DateTimeFormatOptions {
|
||||
var result = {};
|
||||
(result as any /** TODO #9100 */)[prop] = len < 4 ? 'short' : 'long';
|
||||
return result;
|
||||
}
|
||||
|
||||
function combine(options: Intl.DateTimeFormatOptions[]): Intl.DateTimeFormatOptions {
|
||||
var result = {};
|
||||
|
||||
options.forEach(option => { (<any>Object).assign(result, option); });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function datePartGetterFactory(ret: Intl.DateTimeFormatOptions): (date: Date, locale: string) =>
|
||||
string {
|
||||
return function(date: Date, locale: string): string {
|
||||
return new Intl.DateTimeFormat(locale, ret).format(date);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var datePartsFormatterCache: Map<string, string[]> = new Map<string, string[]>();
|
||||
|
||||
function dateFormatter(format: string, date: Date, locale: string): string {
|
||||
var text = '';
|
||||
var match: any /** TODO #9100 */;
|
||||
var fn: any /** TODO #9100 */;
|
||||
var parts: string[] = [];
|
||||
if ((PATTERN_ALIASES as any /** TODO #9100 */)[format]) {
|
||||
return (PATTERN_ALIASES as any /** TODO #9100 */)[format](date, locale);
|
||||
}
|
||||
|
||||
|
||||
if (datePartsFormatterCache.has(format)) {
|
||||
parts = datePartsFormatterCache.get(format);
|
||||
} else {
|
||||
var matchs = DATE_FORMATS_SPLIT.exec(format);
|
||||
|
||||
while (format) {
|
||||
match = DATE_FORMATS_SPLIT.exec(format);
|
||||
if (match) {
|
||||
parts = concat(parts, match, 1);
|
||||
format = parts.pop();
|
||||
} else {
|
||||
parts.push(format);
|
||||
format = null;
|
||||
}
|
||||
}
|
||||
|
||||
datePartsFormatterCache.set(format, parts);
|
||||
}
|
||||
|
||||
parts.forEach(part => {
|
||||
fn = (DATE_FORMATS as any /** TODO #9100 */)[part];
|
||||
text += fn ? fn(date, locale) :
|
||||
part === '\'\'' ? '\'' : part.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
var slice = [].slice;
|
||||
function concat(
|
||||
array1: any /** TODO #9100 */, array2: any /** TODO #9100 */,
|
||||
index: any /** TODO #9100 */): string[] {
|
||||
return array1.concat(slice.call(array2, index));
|
||||
}
|
||||
|
||||
export class DateFormatter {
|
||||
static format(date: Date, locale: string, pattern: string): string {
|
||||
return dateFormatter(pattern, date, locale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
library angular.core.facade.lang;
|
||||
|
||||
export 'dart:core' show Type, RegExp, print, DateTime, Uri;
|
||||
import 'dart:math' as math;
|
||||
import 'dart:convert' as convert;
|
||||
import 'dart:async' show Future, Zone;
|
||||
|
||||
String getTypeNameForDebugging(Object type) => type.toString();
|
||||
|
||||
class Math {
|
||||
static final _random = new math.Random();
|
||||
static int floor(num n) => n.floor();
|
||||
static double random() => _random.nextDouble();
|
||||
static num min(num a, num b) => math.min(a, b);
|
||||
}
|
||||
|
||||
const IS_DART = true;
|
||||
|
||||
scheduleMicroTask(Function fn) {
|
||||
Zone.current.scheduleMicrotask(fn);
|
||||
}
|
||||
|
||||
bool isPresent(Object obj) => obj != null;
|
||||
bool isBlank(Object obj) => obj == null;
|
||||
bool isString(Object obj) => obj is String;
|
||||
bool isFunction(Object obj) => obj is Function;
|
||||
bool isType(Object obj) => obj is Type;
|
||||
bool isStringMap(Object obj) => obj is Map;
|
||||
bool isStrictStringMap(Object obj) => obj is Map;
|
||||
bool isArray(Object obj) => obj is List;
|
||||
bool isPromise(Object obj) => obj is Future;
|
||||
bool isNumber(Object obj) => obj is num;
|
||||
bool isBoolean(Object obj) => obj is bool;
|
||||
bool isDate(Object obj) => obj is DateTime;
|
||||
|
||||
String stringify(obj) {
|
||||
final exp = new RegExp(r"from Function '(\w+)'");
|
||||
final str = obj.toString();
|
||||
if (exp.firstMatch(str) != null) {
|
||||
return exp.firstMatch(str).group(1);
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
int serializeEnum(val) {
|
||||
return val.index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes an enum
|
||||
* val should be the indexed value of the enum (sa returned from @Link{serializeEnum})
|
||||
* values should be a map from indexes to values for the enum that you want to deserialize.
|
||||
*/
|
||||
dynamic deserializeEnum(num val, Map<num, dynamic> values) {
|
||||
return values[val];
|
||||
}
|
||||
|
||||
String resolveEnumToken(enumValue, val) {
|
||||
// turn Enum.Token -> Token
|
||||
return val.toString().replaceFirst(new RegExp('^.+\\.'),'');
|
||||
}
|
||||
|
||||
class StringWrapper {
|
||||
static String fromCharCode(int code) {
|
||||
return new String.fromCharCode(code);
|
||||
}
|
||||
|
||||
static int charCodeAt(String s, int index) {
|
||||
return s.codeUnitAt(index);
|
||||
}
|
||||
|
||||
static List<String> split(String s, RegExp regExp) {
|
||||
var parts = <String>[];
|
||||
var lastEnd = 0;
|
||||
regExp.allMatches(s).forEach((match) {
|
||||
parts.add(s.substring(lastEnd, match.start));
|
||||
lastEnd = match.end;
|
||||
for (var i = 0; i < match.groupCount; i++) {
|
||||
parts.add(match.group(i + 1));
|
||||
}
|
||||
});
|
||||
parts.add(s.substring(lastEnd));
|
||||
return parts;
|
||||
}
|
||||
|
||||
static bool equals(String s, String s2) {
|
||||
return s == s2;
|
||||
}
|
||||
|
||||
static String stripLeft(String s, String charVal) {
|
||||
if (isPresent(s) && s.length > 0) {
|
||||
var pos = 0;
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
if (s[i] != charVal) break;
|
||||
pos++;
|
||||
}
|
||||
s = s.substring(pos);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static String stripRight(String s, String charVal) {
|
||||
if (isPresent(s) && s.length > 0) {
|
||||
var pos = s.length;
|
||||
for (var i = s.length - 1; i >= 0; i--) {
|
||||
if (s[i] != charVal) break;
|
||||
pos--;
|
||||
}
|
||||
s = s.substring(0, pos);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static String replace(String s, Pattern from, String replace) {
|
||||
return s.replaceFirst(from, replace);
|
||||
}
|
||||
|
||||
static String replaceAll(String s, RegExp from, String replace) {
|
||||
return s.replaceAll(from, replace);
|
||||
}
|
||||
|
||||
static String slice(String s, [int start = 0, int end]) {
|
||||
start = _startOffset(s, start);
|
||||
end = _endOffset(s, end);
|
||||
//in JS if start > end an empty string is returned
|
||||
if (end != null && start > end) {
|
||||
return "";
|
||||
}
|
||||
return s.substring(start, end);
|
||||
}
|
||||
|
||||
static String replaceAllMapped(String s, RegExp from, Function cb) {
|
||||
return s.replaceAllMapped(from, cb);
|
||||
}
|
||||
|
||||
static bool contains(String s, String substr) {
|
||||
return s.contains(substr);
|
||||
}
|
||||
|
||||
static int compare(String a, String b) => a.compareTo(b);
|
||||
|
||||
// JS slice function can take start < 0 which indicates a position relative to
|
||||
// the end of the string
|
||||
static int _startOffset(String s, int start) {
|
||||
int len = s.length;
|
||||
return start < 0 ? math.max(len + start, 0) : math.min(start, len);
|
||||
}
|
||||
|
||||
// JS slice function can take end < 0 which indicates a position relative to
|
||||
// the end of the string
|
||||
static int _endOffset(String s, int end) {
|
||||
int len = s.length;
|
||||
if (end == null) return len;
|
||||
return end < 0 ? math.max(len + end, 0) : math.min(end, len);
|
||||
}
|
||||
}
|
||||
|
||||
class StringJoiner {
|
||||
final List<String> _parts = <String>[];
|
||||
|
||||
void add(String part) {
|
||||
_parts.add(part);
|
||||
}
|
||||
|
||||
String toString() => _parts.join("");
|
||||
}
|
||||
|
||||
class NumberWrapper {
|
||||
static String toFixed(num n, int fractionDigits) {
|
||||
return n.toStringAsFixed(fractionDigits);
|
||||
}
|
||||
|
||||
static bool equal(num a, num b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
static int parseIntAutoRadix(String text) {
|
||||
return int.parse(text);
|
||||
}
|
||||
|
||||
static int parseInt(String text, int radix) {
|
||||
return int.parse(text, radix: radix);
|
||||
}
|
||||
|
||||
static double parseFloat(String text) {
|
||||
return double.parse(text);
|
||||
}
|
||||
|
||||
static double get NaN => double.NAN;
|
||||
|
||||
static bool isNaN(num value) => value.isNaN;
|
||||
|
||||
static bool isInteger(value) => value is int;
|
||||
}
|
||||
|
||||
class RegExpWrapper {
|
||||
static RegExp create(regExpStr, [String flags = '']) {
|
||||
bool multiLine = flags.contains('m');
|
||||
bool caseSensitive = !flags.contains('i');
|
||||
return new RegExp(regExpStr,
|
||||
multiLine: multiLine, caseSensitive: caseSensitive);
|
||||
}
|
||||
|
||||
static Match firstMatch(RegExp regExp, String input) {
|
||||
return regExp.firstMatch(input);
|
||||
}
|
||||
|
||||
static bool test(RegExp regExp, String input) {
|
||||
return regExp.hasMatch(input);
|
||||
}
|
||||
|
||||
static Iterator<Match> matcher(RegExp regExp, String input) {
|
||||
return regExp.allMatches(input).iterator;
|
||||
}
|
||||
|
||||
static String replaceAll(RegExp regExp, String input, Function replace) {
|
||||
final m = RegExpWrapper.matcher(regExp, input);
|
||||
var res = "";
|
||||
var prev = 0;
|
||||
while(m.moveNext()) {
|
||||
var c = m.current;
|
||||
res += input.substring(prev, c.start);
|
||||
res += replace(c);
|
||||
prev = c.start + c[0].length;
|
||||
}
|
||||
res += input.substring(prev);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class RegExpMatcherWrapper {
|
||||
static _JSLikeMatch next(Iterator<Match> matcher) {
|
||||
if (matcher.moveNext()) {
|
||||
return new _JSLikeMatch(matcher.current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class _JSLikeMatch {
|
||||
Match _m;
|
||||
|
||||
_JSLikeMatch(this._m);
|
||||
|
||||
String operator [](index) => _m[index];
|
||||
int get index => _m.start;
|
||||
int get length => _m.groupCount + 1;
|
||||
}
|
||||
|
||||
class FunctionWrapper {
|
||||
static apply(Function fn, posArgs) {
|
||||
return Function.apply(fn, posArgs);
|
||||
}
|
||||
|
||||
static Function bind(Function fn, dynamic scope) {
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
|
||||
const _NAN_KEY = const Object();
|
||||
|
||||
// Dart VM implements `identical` as true reference identity. JavaScript does
|
||||
// not have this. The closest we have in JS is `===`. However, for strings JS
|
||||
// would actually compare the contents rather than references. `dart2js`
|
||||
// compiles `identical` to `===` and therefore there is a discrepancy between
|
||||
// Dart VM and `dart2js`. The implementation of `looseIdentical` attempts to
|
||||
// bridge the gap between the two while retaining good performance
|
||||
// characteristics. In JS we use simple `identical`, which compiles to `===`,
|
||||
// and in Dart VM we emulate the semantics of `===` by special-casing strings.
|
||||
// Note that the VM check is a compile-time constant. This allows `dart2js` to
|
||||
// evaluate the conditional during compilation and inline the entire function.
|
||||
//
|
||||
// See: dartbug.com/22496, dartbug.com/25270
|
||||
const _IS_DART_VM = !identical(1.0, 1); // a hack
|
||||
bool looseIdentical(a, b) => _IS_DART_VM
|
||||
? _looseIdentical(a, b)
|
||||
: identical(a, b);
|
||||
|
||||
// This function is intentionally separated from `looseIdentical` to keep the
|
||||
// number of AST nodes low enough for `dart2js` to inline the code.
|
||||
bool _looseIdentical(a, b) =>
|
||||
a is String && b is String ? a == b : identical(a, b);
|
||||
|
||||
// Dart compare map keys by equality and we can have NaN != NaN
|
||||
dynamic getMapKey(value) {
|
||||
if (value is! num) return value;
|
||||
return value.isNaN ? _NAN_KEY : value;
|
||||
}
|
||||
|
||||
// TODO: remove with https://github.com/angular/angular/issues/3055
|
||||
dynamic normalizeBlank(obj) => obj;
|
||||
|
||||
bool normalizeBool(bool obj) {
|
||||
return isBlank(obj) ? false : obj;
|
||||
}
|
||||
|
||||
bool isJsObject(o) {
|
||||
return false;
|
||||
}
|
||||
|
||||
warn(o) {
|
||||
print(o);
|
||||
}
|
||||
|
||||
// Functions below are noop in Dart. Imperatively controlling dev mode kills
|
||||
// tree shaking. We should only rely on `assertionsEnabled`.
|
||||
@Deprecated('Do not use this function. It is for JS only. There is no alternative.')
|
||||
void lockMode() {}
|
||||
@Deprecated('Do not use this function. It is for JS only. There is no alternative.')
|
||||
void enableDevMode() {}
|
||||
@Deprecated('Do not use this function. It is for JS only. There is no alternative.')
|
||||
void enableProdMode() {}
|
||||
|
||||
/// Use this function to guard debugging code. When Dart is compiled in
|
||||
/// production mode, the code guarded using this function will be tree
|
||||
/// shaken away, reducing code size.
|
||||
///
|
||||
/// WARNING: DO NOT CHANGE THIS METHOD! This method is designed to have no
|
||||
/// more AST nodes than the maximum allowed by dart2js to inline it. In
|
||||
/// addition, the use of `assert` allows the compiler to statically compute
|
||||
/// the value returned by this function and tree shake conditions guarded by
|
||||
/// it.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// if (assertionsEnabled()) {
|
||||
/// ...code here is tree shaken away in prod mode...
|
||||
/// }
|
||||
bool assertionsEnabled() {
|
||||
var k = false;
|
||||
assert((k = true));
|
||||
return k;
|
||||
}
|
||||
|
||||
// Can't be all uppercase as our transpiler would think it is a special directive...
|
||||
class Json {
|
||||
static parse(String s) => convert.JSON.decode(s);
|
||||
static String stringify(data) {
|
||||
var encoder = new convert.JsonEncoder.withIndent(" ");
|
||||
return encoder.convert(data);
|
||||
}
|
||||
}
|
||||
|
||||
class DateWrapper {
|
||||
static DateTime create(int year,
|
||||
[int month = 1,
|
||||
int day = 1,
|
||||
int hour = 0,
|
||||
int minutes = 0,
|
||||
int seconds = 0,
|
||||
int milliseconds = 0]) {
|
||||
return new DateTime(year, month, day, hour, minutes, seconds, milliseconds);
|
||||
}
|
||||
|
||||
static DateTime fromISOString(String str) {
|
||||
return DateTime.parse(str);
|
||||
}
|
||||
|
||||
static DateTime fromMillis(int ms) {
|
||||
return new DateTime.fromMillisecondsSinceEpoch(ms, isUtc: true);
|
||||
}
|
||||
|
||||
static int toMillis(DateTime date) {
|
||||
return date.millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
static DateTime now() {
|
||||
return new DateTime.now();
|
||||
}
|
||||
|
||||
static String toJson(DateTime date) {
|
||||
return date.toUtc().toIso8601String();
|
||||
}
|
||||
}
|
||||
|
||||
bool isPrimitive(Object obj) => obj is num || obj is bool || obj == null || obj is String;
|
||||
|
||||
// needed to match the exports from lang.js
|
||||
var global = null;
|
||||
|
||||
dynamic evalExpression(String sourceUrl, String expr, String declarations, Map<String, String> vars) {
|
||||
throw "Dart does not support evaluating expression during runtime!";
|
||||
}
|
||||
|
||||
bool hasConstructor(Object value, Type type) {
|
||||
return value.runtimeType == type;
|
||||
}
|
||||
|
||||
num bitWiseOr(List values) {
|
||||
var val = values.reduce((num a, num b) => (a as int) | (b as int));
|
||||
return val as num;
|
||||
}
|
||||
|
||||
num bitWiseAnd(List values) {
|
||||
var val = values.reduce((num a, num b) => (a as int) & (b as int));
|
||||
return val as num;
|
||||
}
|
||||
|
||||
String escape(String s) {
|
||||
return Uri.encodeComponent(s);
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
export interface BrowserNodeGlobal {
|
||||
Object: typeof Object;
|
||||
Array: typeof Array;
|
||||
Map: typeof Map;
|
||||
Set: typeof Set;
|
||||
Date: DateConstructor;
|
||||
RegExp: RegExpConstructor;
|
||||
JSON: typeof JSON;
|
||||
Math: any; // typeof Math;
|
||||
assert(condition: any): void;
|
||||
Reflect: any;
|
||||
getAngularTestability: Function;
|
||||
getAllAngularTestabilities: Function;
|
||||
getAllAngularRootElements: Function;
|
||||
frameworkStabilizers: Array<Function>;
|
||||
setTimeout: Function;
|
||||
clearTimeout: Function;
|
||||
setInterval: Function;
|
||||
clearInterval: Function;
|
||||
encodeURI: Function;
|
||||
}
|
||||
|
||||
// TODO(jteplitz602): Load WorkerGlobalScope from lib.webworker.d.ts file #3492
|
||||
declare var WorkerGlobalScope: any /** TODO #9100 */;
|
||||
// CommonJS / Node have global context exposed as "global" variable.
|
||||
// We don't want to include the whole node.d.ts this this compilation unit so we'll just fake
|
||||
// the global "global" var for now.
|
||||
declare var global: any /** TODO #9100 */;
|
||||
|
||||
var globalScope: BrowserNodeGlobal;
|
||||
if (typeof window === 'undefined') {
|
||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
|
||||
// TODO: Replace any with WorkerGlobalScope from lib.webworker.d.ts #3492
|
||||
globalScope = <any>self;
|
||||
} else {
|
||||
globalScope = <any>global;
|
||||
}
|
||||
} else {
|
||||
globalScope = <any>window;
|
||||
}
|
||||
|
||||
export function scheduleMicroTask(fn: Function) {
|
||||
Zone.current.scheduleMicroTask('scheduleMicrotask', fn);
|
||||
}
|
||||
|
||||
export const IS_DART = false;
|
||||
|
||||
// Need to declare a new variable for global here since TypeScript
|
||||
// exports the original value of the symbol.
|
||||
var _global: BrowserNodeGlobal = globalScope;
|
||||
|
||||
export {_global as global};
|
||||
|
||||
export var Type = Function;
|
||||
|
||||
/**
|
||||
* Runtime representation a type that a Component or other object is instances of.
|
||||
*
|
||||
* An example of a `Type` is `MyCustomComponent` class, which in JavaScript is be represented by
|
||||
* the `MyCustomComponent` constructor function.
|
||||
*/
|
||||
export interface Type extends Function {}
|
||||
|
||||
/**
|
||||
* Runtime representation of a type that is constructable (non-abstract).
|
||||
*/
|
||||
export interface ConcreteType extends Type { new (...args: any[] /** TODO #9100 */): any; }
|
||||
|
||||
export function getTypeNameForDebugging(type: Type): string {
|
||||
if (type['name']) {
|
||||
return type['name'];
|
||||
}
|
||||
return typeof type;
|
||||
}
|
||||
|
||||
|
||||
export var Math = _global.Math;
|
||||
export var Date = _global.Date;
|
||||
|
||||
var _devMode: boolean = true;
|
||||
var _modeLocked: boolean = false;
|
||||
|
||||
export function lockMode() {
|
||||
_modeLocked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Angular's development mode, which turns off assertions and other
|
||||
* checks within the framework.
|
||||
*
|
||||
* One important assertion this disables verifies that a change detection pass
|
||||
* does not result in additional changes to any bindings (also known as
|
||||
* unidirectional data flow).
|
||||
* @stable
|
||||
*/
|
||||
export function enableProdMode() {
|
||||
if (_modeLocked) {
|
||||
// Cannot use BaseException as that ends up importing from facade/lang.
|
||||
throw 'Cannot enable prod mode after platform setup.';
|
||||
}
|
||||
_devMode = false;
|
||||
}
|
||||
|
||||
export function assertionsEnabled(): boolean {
|
||||
return _devMode;
|
||||
}
|
||||
|
||||
// TODO: remove calls to assert in production environment
|
||||
// Note: Can't just export this and import in in other files
|
||||
// as `assert` is a reserved keyword in Dart
|
||||
_global.assert = function assert(condition) {
|
||||
// TODO: to be fixed properly via #2830, noop for now
|
||||
};
|
||||
|
||||
export function isPresent(obj: any): boolean {
|
||||
return obj !== undefined && obj !== null;
|
||||
}
|
||||
|
||||
export function isBlank(obj: any): boolean {
|
||||
return obj === undefined || obj === null;
|
||||
}
|
||||
|
||||
export function isBoolean(obj: any): boolean {
|
||||
return typeof obj === 'boolean';
|
||||
}
|
||||
|
||||
export function isNumber(obj: any): boolean {
|
||||
return typeof obj === 'number';
|
||||
}
|
||||
|
||||
export function isString(obj: any): obj is String {
|
||||
return typeof obj === 'string';
|
||||
}
|
||||
|
||||
export function isFunction(obj: any): boolean {
|
||||
return typeof obj === 'function';
|
||||
}
|
||||
|
||||
export function isType(obj: any): boolean {
|
||||
return isFunction(obj);
|
||||
}
|
||||
|
||||
export function isStringMap(obj: any): boolean {
|
||||
return typeof obj === 'object' && obj !== null;
|
||||
}
|
||||
|
||||
const STRING_MAP_PROTO = Object.getPrototypeOf({});
|
||||
export function isStrictStringMap(obj: any): boolean {
|
||||
return isStringMap(obj) && Object.getPrototypeOf(obj) === STRING_MAP_PROTO;
|
||||
}
|
||||
|
||||
export function isPromise(obj: any): boolean {
|
||||
return obj instanceof (<any>_global).Promise;
|
||||
}
|
||||
|
||||
export function isArray(obj: any): boolean {
|
||||
return Array.isArray(obj);
|
||||
}
|
||||
|
||||
export function isDate(obj: any): obj is Date {
|
||||
return obj instanceof Date && !isNaN(obj.valueOf());
|
||||
}
|
||||
|
||||
export function noop() {}
|
||||
|
||||
export function stringify(token: any): string {
|
||||
if (typeof token === 'string') {
|
||||
return token;
|
||||
}
|
||||
|
||||
if (token === undefined || token === null) {
|
||||
return '' + token;
|
||||
}
|
||||
|
||||
if (token.name) {
|
||||
return token.name;
|
||||
}
|
||||
if (token.overriddenName) {
|
||||
return token.overriddenName;
|
||||
}
|
||||
|
||||
var res = token.toString();
|
||||
var newLineIndex = res.indexOf('\n');
|
||||
return (newLineIndex === -1) ? res : res.substring(0, newLineIndex);
|
||||
}
|
||||
|
||||
// serialize / deserialize enum exist only for consistency with dart API
|
||||
// enums in typescript don't need to be serialized
|
||||
|
||||
export function serializeEnum(val: any): number {
|
||||
return val;
|
||||
}
|
||||
|
||||
export function deserializeEnum(val: any, values: Map<number, any>): any {
|
||||
return val;
|
||||
}
|
||||
|
||||
export function resolveEnumToken(enumValue: any, val: any): string {
|
||||
return enumValue[val];
|
||||
}
|
||||
|
||||
export class StringWrapper {
|
||||
static fromCharCode(code: number): string { return String.fromCharCode(code); }
|
||||
|
||||
static charCodeAt(s: string, index: number): number { return s.charCodeAt(index); }
|
||||
|
||||
static split(s: string, regExp: RegExp): string[] { return s.split(regExp); }
|
||||
|
||||
static equals(s: string, s2: string): boolean { return s === s2; }
|
||||
|
||||
static stripLeft(s: string, charVal: string): string {
|
||||
if (s && s.length) {
|
||||
var pos = 0;
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
if (s[i] != charVal) break;
|
||||
pos++;
|
||||
}
|
||||
s = s.substring(pos);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static stripRight(s: string, charVal: string): string {
|
||||
if (s && s.length) {
|
||||
var pos = s.length;
|
||||
for (var i = s.length - 1; i >= 0; i--) {
|
||||
if (s[i] != charVal) break;
|
||||
pos--;
|
||||
}
|
||||
s = s.substring(0, pos);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static replace(s: string, from: string, replace: string): string {
|
||||
return s.replace(from, replace);
|
||||
}
|
||||
|
||||
static replaceAll(s: string, from: RegExp, replace: string): string {
|
||||
return s.replace(from, replace);
|
||||
}
|
||||
|
||||
static slice<T>(s: string, from: number = 0, to: number = null): string {
|
||||
return s.slice(from, to === null ? undefined : to);
|
||||
}
|
||||
|
||||
static replaceAllMapped(s: string, from: RegExp, cb: Function): string {
|
||||
return s.replace(from, function(...matches: any[]) {
|
||||
// Remove offset & string from the result array
|
||||
matches.splice(-2, 2);
|
||||
// The callback receives match, p1, ..., pn
|
||||
return cb(matches);
|
||||
});
|
||||
}
|
||||
|
||||
static contains(s: string, substr: string): boolean { return s.indexOf(substr) != -1; }
|
||||
|
||||
static compare(a: string, b: string): number {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StringJoiner {
|
||||
constructor(public parts: string[] = []) {}
|
||||
|
||||
add(part: string): void { this.parts.push(part); }
|
||||
|
||||
toString(): string { return this.parts.join(''); }
|
||||
}
|
||||
|
||||
export class NumberParseError extends Error {
|
||||
name: string;
|
||||
|
||||
constructor(public message: string) { super(); }
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
|
||||
export class NumberWrapper {
|
||||
static toFixed(n: number, fractionDigits: number): string { return n.toFixed(fractionDigits); }
|
||||
|
||||
static equal(a: number, b: number): boolean { return a === b; }
|
||||
|
||||
static parseIntAutoRadix(text: string): number {
|
||||
var result: number = parseInt(text);
|
||||
if (isNaN(result)) {
|
||||
throw new NumberParseError('Invalid integer literal when parsing ' + text);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static parseInt(text: string, radix: number): number {
|
||||
if (radix == 10) {
|
||||
if (/^(\-|\+)?[0-9]+$/.test(text)) {
|
||||
return parseInt(text, radix);
|
||||
}
|
||||
} else if (radix == 16) {
|
||||
if (/^(\-|\+)?[0-9ABCDEFabcdef]+$/.test(text)) {
|
||||
return parseInt(text, radix);
|
||||
}
|
||||
} else {
|
||||
var result: number = parseInt(text, radix);
|
||||
if (!isNaN(result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new NumberParseError(
|
||||
'Invalid integer literal when parsing ' + text + ' in base ' + radix);
|
||||
}
|
||||
|
||||
// TODO: NaN is a valid literal but is returned by parseFloat to indicate an error.
|
||||
static parseFloat(text: string): number { return parseFloat(text); }
|
||||
|
||||
static get NaN(): number { return NaN; }
|
||||
|
||||
static isNaN(value: any): boolean { return isNaN(value); }
|
||||
|
||||
static isInteger(value: any): boolean { return Number.isInteger(value); }
|
||||
}
|
||||
|
||||
export var RegExp = _global.RegExp;
|
||||
|
||||
export class RegExpWrapper {
|
||||
static create(regExpStr: string, flags: string = ''): RegExp {
|
||||
flags = flags.replace(/g/g, '');
|
||||
return new _global.RegExp(regExpStr, flags + 'g');
|
||||
}
|
||||
static firstMatch(regExp: RegExp, input: string): RegExpExecArray {
|
||||
// Reset multimatch regex state
|
||||
regExp.lastIndex = 0;
|
||||
return regExp.exec(input);
|
||||
}
|
||||
static test(regExp: RegExp, input: string): boolean {
|
||||
regExp.lastIndex = 0;
|
||||
return regExp.test(input);
|
||||
}
|
||||
static matcher(regExp: RegExp, input: string): {re: RegExp; input: string} {
|
||||
// Reset regex state for the case
|
||||
// someone did not loop over all matches
|
||||
// last time.
|
||||
regExp.lastIndex = 0;
|
||||
return {re: regExp, input: input};
|
||||
}
|
||||
static replaceAll(regExp: RegExp, input: string, replace: Function): string {
|
||||
let c = regExp.exec(input);
|
||||
let res = '';
|
||||
regExp.lastIndex = 0;
|
||||
let prev = 0;
|
||||
while (c) {
|
||||
res += input.substring(prev, c.index);
|
||||
res += replace(c);
|
||||
prev = c.index + c[0].length;
|
||||
regExp.lastIndex = prev;
|
||||
c = regExp.exec(input);
|
||||
}
|
||||
res += input.substring(prev);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export class RegExpMatcherWrapper {
|
||||
static next(matcher: {re: RegExp; input: string}): RegExpExecArray {
|
||||
return matcher.re.exec(matcher.input);
|
||||
}
|
||||
}
|
||||
|
||||
export class FunctionWrapper {
|
||||
static apply(fn: Function, posArgs: any): any { return fn.apply(null, posArgs); }
|
||||
|
||||
static bind(fn: Function, scope: any): Function { return fn.bind(scope); }
|
||||
}
|
||||
|
||||
// JS has NaN !== NaN
|
||||
export function looseIdentical(a: any, b: any): boolean {
|
||||
return a === b || typeof a === 'number' && typeof b === 'number' && isNaN(a) && isNaN(b);
|
||||
}
|
||||
|
||||
// JS considers NaN is the same as NaN for map Key (while NaN !== NaN otherwise)
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||||
export function getMapKey<T>(value: T): T {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function normalizeBlank(obj: Object): any {
|
||||
return isBlank(obj) ? null : obj;
|
||||
}
|
||||
|
||||
export function normalizeBool(obj: boolean): boolean {
|
||||
return isBlank(obj) ? false : obj;
|
||||
}
|
||||
|
||||
export function isJsObject(o: any): boolean {
|
||||
return o !== null && (typeof o === 'function' || typeof o === 'object');
|
||||
}
|
||||
|
||||
export function print(obj: Error | Object) {
|
||||
console.log(obj);
|
||||
}
|
||||
|
||||
export function warn(obj: Error | Object) {
|
||||
console.warn(obj);
|
||||
}
|
||||
|
||||
// Can't be all uppercase as our transpiler would think it is a special directive...
|
||||
export class Json {
|
||||
static parse(s: string): Object { return _global.JSON.parse(s); }
|
||||
static stringify(data: Object): string {
|
||||
// Dart doesn't take 3 arguments
|
||||
return _global.JSON.stringify(data, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
export class DateWrapper {
|
||||
static create(
|
||||
year: number, month: number = 1, day: number = 1, hour: number = 0, minutes: number = 0,
|
||||
seconds: number = 0, milliseconds: number = 0): Date {
|
||||
return new Date(year, month - 1, day, hour, minutes, seconds, milliseconds);
|
||||
}
|
||||
static fromISOString(str: string): Date { return new Date(str); }
|
||||
static fromMillis(ms: number): Date { return new Date(ms); }
|
||||
static toMillis(date: Date): number { return date.getTime(); }
|
||||
static now(): Date { return new Date(); }
|
||||
static toJson(date: Date): string { return date.toJSON(); }
|
||||
}
|
||||
|
||||
export function setValueOnPath(global: any, path: string, value: any) {
|
||||
var parts = path.split('.');
|
||||
var obj: any = global;
|
||||
while (parts.length > 1) {
|
||||
var name = parts.shift();
|
||||
if (obj.hasOwnProperty(name) && isPresent(obj[name])) {
|
||||
obj = obj[name];
|
||||
} else {
|
||||
obj = obj[name] = {};
|
||||
}
|
||||
}
|
||||
if (obj === undefined || obj === null) {
|
||||
obj = {};
|
||||
}
|
||||
obj[parts.shift()] = value;
|
||||
}
|
||||
|
||||
// When Symbol.iterator doesn't exist, retrieves the key used in es6-shim
|
||||
declare var Symbol: any;
|
||||
var _symbolIterator: any = null;
|
||||
export function getSymbolIterator(): string|symbol {
|
||||
if (isBlank(_symbolIterator)) {
|
||||
if (isPresent((<any>globalScope).Symbol) && isPresent(Symbol.iterator)) {
|
||||
_symbolIterator = Symbol.iterator;
|
||||
} else {
|
||||
// es6-shim specific logic
|
||||
var keys = Object.getOwnPropertyNames(Map.prototype);
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
var key = keys[i];
|
||||
if (key !== 'entries' && key !== 'size' &&
|
||||
(Map as any).prototype[key] === Map.prototype['entries']) {
|
||||
_symbolIterator = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _symbolIterator;
|
||||
}
|
||||
|
||||
export function evalExpression(
|
||||
sourceUrl: string, expr: string, declarations: string, vars: {[key: string]: any}): any {
|
||||
var fnBody = `${declarations}\nreturn ${expr}\n//# sourceURL=${sourceUrl}`;
|
||||
var fnArgNames: string[] = [];
|
||||
var fnArgValues: any[] = [];
|
||||
for (var argName in vars) {
|
||||
fnArgNames.push(argName);
|
||||
fnArgValues.push(vars[argName]);
|
||||
}
|
||||
return new Function(...fnArgNames.concat(fnBody))(...fnArgValues);
|
||||
}
|
||||
|
||||
export function isPrimitive(obj: any): boolean {
|
||||
return !isJsObject(obj);
|
||||
}
|
||||
|
||||
export function hasConstructor(value: Object, type: Type): boolean {
|
||||
return value.constructor === type;
|
||||
}
|
||||
|
||||
export function bitWiseOr(values: number[]): number {
|
||||
return values.reduce((a, b) => { return a | b; });
|
||||
}
|
||||
|
||||
export function bitWiseAnd(values: number[]): number {
|
||||
return values.reduce((a, b) => { return a & b; });
|
||||
}
|
||||
|
||||
export function escape(s: string): string {
|
||||
return _global.encodeURI(s);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
library angular.core.facade.math;
|
||||
|
||||
import 'dart:core' show double, num;
|
||||
import 'dart:math' as math;
|
||||
|
||||
const NaN = double.NAN;
|
||||
|
||||
class Math {
|
||||
static num pow(num x, num exponent) {
|
||||
return math.pow(x, exponent);
|
||||
}
|
||||
|
||||
static num max(num a, num b) => math.max(a, b);
|
||||
|
||||
static num min(num a, num b) => math.min(a, b);
|
||||
|
||||
static num floor(num a) => a.floor();
|
||||
|
||||
static num ceil(num a) => a.ceil();
|
||||
|
||||
static num sqrt(num x) => math.sqrt(x);
|
||||
|
||||
static num round(num x) => x.round();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import {global} from './lang';
|
||||
|
||||
export var Math = global.Math;
|
||||
export var NaN: any /** TODO #???? */ = typeof NaN;
|
||||
@@ -0,0 +1,58 @@
|
||||
library angular2.core.facade.promise;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:async' as async;
|
||||
|
||||
class PromiseWrapper {
|
||||
static Future/*<T>*/ resolve/*<T>*/(dynamic /*=T*/ obj) => new Future.value(obj);
|
||||
|
||||
static Future/*<T>*/ reject/*<T>*/(dynamic /*=T*/ obj, Object stackTrace) => new Future.error(obj,
|
||||
stackTrace != null ? stackTrace : obj is Error ? (obj as Error).stackTrace : null);
|
||||
|
||||
static Future<List/*<T>*/> all/*<T>*/(List<dynamic> promises) {
|
||||
return Future
|
||||
.wait(promises.map((p) => p is Future ? p as Future/*<T>*/ : new Future/*<T>*/.value(p)));
|
||||
}
|
||||
static Future/*<R>*/ then/*<T, R>*/(Future/*<T>*/ promise, dynamic /*=R*/ success(dynamic /*=T*/ value), [Function onError]) {
|
||||
if (success == null) return promise.catchError(onError);
|
||||
return promise.then(success, onError: onError);
|
||||
}
|
||||
|
||||
static Future/*<T>*/ wrap/*<T>*/(dynamic /*=T*/ fn()) {
|
||||
return new Future(fn);
|
||||
}
|
||||
|
||||
// Note: We can't rename this method to `catch`, as this is not a valid
|
||||
// method name in Dart.
|
||||
static Future catchError(Future promise, Function onError) {
|
||||
return promise.catchError(onError);
|
||||
}
|
||||
|
||||
static void scheduleMicrotask(fn) {
|
||||
async.scheduleMicrotask(fn);
|
||||
}
|
||||
|
||||
static bool isPromise(obj) {
|
||||
return obj is Future;
|
||||
}
|
||||
|
||||
static PromiseCompleter/*<T>*/ completer/*<T>*/() =>
|
||||
new PromiseCompleter();
|
||||
}
|
||||
|
||||
class PromiseCompleter<T> {
|
||||
final Completer<T> c = new Completer();
|
||||
|
||||
Future<T> get promise => c.future;
|
||||
|
||||
void resolve(v) {
|
||||
c.complete(v);
|
||||
}
|
||||
|
||||
void reject(error, stack) {
|
||||
if (stack == null && error is Error) {
|
||||
stack = error.stackTrace;
|
||||
}
|
||||
c.completeError(error, stack);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
var PromiseCompleter = (function () {
|
||||
function PromiseCompleter() {
|
||||
var _this = this;
|
||||
this.promise = new Promise(function (res, rej) {
|
||||
_this.resolve = res;
|
||||
_this.reject = rej;
|
||||
});
|
||||
}
|
||||
return PromiseCompleter;
|
||||
}());
|
||||
exports.PromiseCompleter = PromiseCompleter;
|
||||
var PromiseWrapper = (function () {
|
||||
function PromiseWrapper() {
|
||||
}
|
||||
PromiseWrapper.resolve = function (obj) { return Promise.resolve(obj); };
|
||||
PromiseWrapper.reject = function (obj, _) { return Promise.reject(obj); };
|
||||
// Note: We can't rename this method into `catch`, as this is not a valid
|
||||
// method name in Dart.
|
||||
PromiseWrapper.catchError = function (promise, onError) {
|
||||
return promise.catch(onError);
|
||||
};
|
||||
PromiseWrapper.all = function (promises) {
|
||||
if (promises.length == 0)
|
||||
return Promise.resolve([]);
|
||||
return Promise.all(promises);
|
||||
};
|
||||
PromiseWrapper.then = function (promise, success, rejection) {
|
||||
return promise.then(success, rejection);
|
||||
};
|
||||
PromiseWrapper.wrap = function (computation) {
|
||||
return new Promise(function (res, rej) {
|
||||
try {
|
||||
res(computation());
|
||||
}
|
||||
catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
PromiseWrapper.scheduleMicrotask = function (computation) {
|
||||
PromiseWrapper.then(PromiseWrapper.resolve(null), computation, function (_) { });
|
||||
};
|
||||
PromiseWrapper.isPromise = function (obj) { return obj instanceof Promise; };
|
||||
PromiseWrapper.completer = function () { return new PromiseCompleter(); };
|
||||
return PromiseWrapper;
|
||||
}());
|
||||
exports.PromiseWrapper = PromiseWrapper;
|
||||
//# sourceMappingURL=promise.js.map
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
export class PromiseCompleter<R> {
|
||||
promise: Promise<R>;
|
||||
resolve: (value?: R|PromiseLike<R>) => void;
|
||||
reject: (error?: any, stackTrace?: string) => void;
|
||||
|
||||
constructor() {
|
||||
this.promise = new Promise((res, rej) => {
|
||||
this.resolve = res;
|
||||
this.reject = rej;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class PromiseWrapper {
|
||||
static resolve<T>(obj: T): Promise<T> { return Promise.resolve(obj); }
|
||||
|
||||
static reject(obj: any, _: any): Promise<any> { return Promise.reject(obj); }
|
||||
|
||||
// Note: We can't rename this method into `catch`, as this is not a valid
|
||||
// method name in Dart.
|
||||
static catchError<T>(promise: Promise<T>, onError: (error: any) => T | PromiseLike<T>):
|
||||
Promise<T> {
|
||||
return promise.catch(onError);
|
||||
}
|
||||
|
||||
static all<T>(promises: (T|Promise<T>)[]): Promise<T[]> {
|
||||
if (promises.length == 0) return Promise.resolve([]);
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
static then<T, U>(
|
||||
promise: Promise<T>, success: (value: T) => U | PromiseLike<U>,
|
||||
rejection?: (error: any, stack?: any) => U | PromiseLike<U>): Promise<U> {
|
||||
return promise.then(success, rejection);
|
||||
}
|
||||
|
||||
static wrap<T>(computation: () => T): Promise<T> {
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
res(computation());
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static scheduleMicrotask(computation: () => any): void {
|
||||
PromiseWrapper.then(PromiseWrapper.resolve(null), computation, (_) => {});
|
||||
}
|
||||
|
||||
static isPromise(obj: any): boolean { return obj instanceof Promise; }
|
||||
|
||||
static completer<T>(): PromiseCompleter<T> { return new PromiseCompleter<T>(); }
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {StringMapWrapper} from './facade/collection';
|
||||
import {isArray, isPresent} from './facade/lang';
|
||||
import * as modelModule from './model';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a form object from a user-specified configuration.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <form [formGroup]="loginForm">
|
||||
* <p>Login <input formControlName="login"></p>
|
||||
* <div formGroupName="passwordRetry">
|
||||
* <p>Password <input type="password" formControlName="password"></p>
|
||||
* <p>Confirm password <input type="password" formControlName="passwordConfirmation"></p>
|
||||
* </div>
|
||||
* </form>
|
||||
* <h3>Form value:</h3>
|
||||
* <pre>{{value}}</pre>
|
||||
* `,
|
||||
* directives: [REACTIVE_FORM_DIRECTIVES]
|
||||
* })
|
||||
* export class App {
|
||||
* loginForm: FormGroup;
|
||||
*
|
||||
* constructor(builder: FormBuilder) {
|
||||
* this.loginForm = builder.group({
|
||||
* login: ["", Validators.required],
|
||||
* passwordRetry: builder.group({
|
||||
* password: ["", Validators.required],
|
||||
* passwordConfirmation: ["", Validators.required, asyncValidator]
|
||||
* })
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* get value(): string {
|
||||
* return JSON.stringify(this.loginForm.value, null, 2);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Injectable()
|
||||
export class FormBuilder {
|
||||
/**
|
||||
* Construct a new {@link FormGroup} with the given map of configuration.
|
||||
* Valid keys for the `extra` parameter map are `optionals` and `validator`.
|
||||
*
|
||||
* See the {@link FormGroup} constructor for more details.
|
||||
*/
|
||||
group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null):
|
||||
modelModule.FormGroup {
|
||||
var controls = this._reduceControls(controlsConfig);
|
||||
var optionals = <{[key: string]: boolean}>(
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'optionals') : null);
|
||||
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, 'validator') : null;
|
||||
var asyncValidator: AsyncValidatorFn =
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'asyncValidator') : null;
|
||||
return new modelModule.FormGroup(controls, optionals, validator, asyncValidator);
|
||||
}
|
||||
/**
|
||||
* Construct a new {@link FormControl} with the given `value`,`validator`, and `asyncValidator`.
|
||||
*/
|
||||
control(
|
||||
value: Object, validator: ValidatorFn|ValidatorFn[] = null,
|
||||
asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): modelModule.FormControl {
|
||||
return new modelModule.FormControl(value, validator, asyncValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array of {@link FormControl}s from the given `controlsConfig` array of
|
||||
* configuration, with the given optional `validator` and `asyncValidator`.
|
||||
*/
|
||||
array(
|
||||
controlsConfig: any[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): modelModule.FormArray {
|
||||
var controls = controlsConfig.map(c => this._createControl(c));
|
||||
return new modelModule.FormArray(controls, validator, asyncValidator);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceControls(controlsConfig: {[k: string]: any}):
|
||||
{[key: string]: modelModule.AbstractControl} {
|
||||
var controls: {[key: string]: modelModule.AbstractControl} = {};
|
||||
StringMapWrapper.forEach(controlsConfig, (controlConfig: any, controlName: string) => {
|
||||
controls[controlName] = this._createControl(controlConfig);
|
||||
});
|
||||
return controls;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_createControl(controlConfig: any): modelModule.AbstractControl {
|
||||
if (controlConfig instanceof modelModule.FormControl ||
|
||||
controlConfig instanceof modelModule.FormGroup ||
|
||||
controlConfig instanceof modelModule.FormArray) {
|
||||
return controlConfig;
|
||||
|
||||
} else if (isArray(controlConfig)) {
|
||||
var value = controlConfig[0];
|
||||
var validator: ValidatorFn = controlConfig.length > 1 ? controlConfig[1] : null;
|
||||
var asyncValidator: AsyncValidatorFn = controlConfig.length > 2 ? controlConfig[2] : null;
|
||||
return this.control(value, validator, asyncValidator);
|
||||
|
||||
} else {
|
||||
return this.control(controlConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
import {COMMON_DIRECTIVES, FORM_DIRECTIVES as OLD_FORM_DIRECTIVES, FORM_PROVIDERS as OLD_FORM_PROVIDERS} from '@angular/common';
|
||||
import {CompilerConfig} from '@angular/compiler';
|
||||
import {PLATFORM_DIRECTIVES, PLATFORM_PIPES, Type} from '@angular/core';
|
||||
|
||||
import {FORM_DIRECTIVES as NEW_FORM_DIRECTIVES} from './directives';
|
||||
import {RadioControlRegistry as NewRadioControlRegistry} from './directives/radio_control_value_accessor';
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {FormBuilder as NewFormBuilder} from './form_builder';
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Shorthand set of providers used for building Angular forms.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* bootstrap(MyApp, [FORM_PROVIDERS]);
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const FORM_PROVIDERS: Type[] = /*@ts2dart_const*/[NewFormBuilder, NewRadioControlRegistry];
|
||||
|
||||
function flatten(platformDirectives: any[]): any[] {
|
||||
let flattenedDirectives: any[] = [];
|
||||
platformDirectives.forEach((directives) => {
|
||||
if (Array.isArray(directives)) {
|
||||
flattenedDirectives = flattenedDirectives.concat(directives);
|
||||
} else {
|
||||
flattenedDirectives.push(directives);
|
||||
}
|
||||
});
|
||||
return flattenedDirectives;
|
||||
}
|
||||
|
||||
export function disableDeprecatedForms(): any[] {
|
||||
return [{
|
||||
provide: CompilerConfig,
|
||||
useFactory: (platformDirectives: any[], platformPipes: any[]) => {
|
||||
const flattenedDirectives = flatten(platformDirectives);
|
||||
ListWrapper.remove(flattenedDirectives, OLD_FORM_DIRECTIVES);
|
||||
return new CompilerConfig({platformDirectives: flattenedDirectives, platformPipes});
|
||||
},
|
||||
deps: [PLATFORM_DIRECTIVES, PLATFORM_PIPES]
|
||||
}];
|
||||
}
|
||||
|
||||
export function provideForms(): any[] {
|
||||
return [
|
||||
{provide: PLATFORM_DIRECTIVES, useValue: NEW_FORM_DIRECTIVES, multi: true}, FORM_PROVIDERS
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* This module is used for handling user input, by defining and building a {@link FormGroup} that
|
||||
* consists of
|
||||
* {@link FormControl} objects, and mapping them onto the DOM. {@link FormControl} objects can then
|
||||
* be used
|
||||
* to read information
|
||||
* from the form DOM elements.
|
||||
*
|
||||
* Forms providers are not included in default providers; you must import these providers
|
||||
* explicitly.
|
||||
*/
|
||||
|
||||
|
||||
export {FORM_DIRECTIVES, REACTIVE_FORM_DIRECTIVES, RadioButtonState} from './directives';
|
||||
export {AbstractControlDirective} from './directives/abstract_control_directive';
|
||||
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
export {ControlContainer} from './directives/control_container';
|
||||
export {ControlValueAccessor, NG_VALUE_ACCESSOR} from './directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
export {Form} from './directives/form_interface';
|
||||
export {NgControl} from './directives/ng_control';
|
||||
export {NgControlStatus} from './directives/ng_control_status';
|
||||
export {NgForm} from './directives/ng_form';
|
||||
export {NgModel} from './directives/ng_model';
|
||||
export {NgModelGroup} from './directives/ng_model_group';
|
||||
export {FormControlDirective} from './directives/reactive_directives/form_control_directive';
|
||||
export {FormControlName} from './directives/reactive_directives/form_control_name';
|
||||
export {FormGroupDirective} from './directives/reactive_directives/form_group_directive';
|
||||
export {FormGroupName} from './directives/reactive_directives/form_group_name';
|
||||
export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator} from './directives/validators';
|
||||
export {FormBuilder} from './form_builder';
|
||||
export {AbstractControl, FormArray, FormControl, FormGroup} from './model';
|
||||
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators';
|
||||
export * from './form_providers';
|
||||
@@ -0,0 +1,540 @@
|
||||
import {composeAsyncValidators, composeValidators} from './directives/shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {EventEmitter, Observable, ObservableWrapper} from './facade/async';
|
||||
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, normalizeBool} from './facade/lang';
|
||||
import {PromiseWrapper} from './facade/promise';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that a FormControl is valid, i.e. that no errors exist in the input value.
|
||||
*/
|
||||
export const VALID = 'VALID';
|
||||
|
||||
/**
|
||||
* Indicates that a FormControl is invalid, i.e. that an error exists in the input value.
|
||||
*/
|
||||
export const INVALID = 'INVALID';
|
||||
|
||||
/**
|
||||
* Indicates that a FormControl is pending, i.e. that async validation is occurring and
|
||||
* errors are not yet available for the input value.
|
||||
*/
|
||||
export const PENDING = 'PENDING';
|
||||
|
||||
export function isControl(control: Object): boolean {
|
||||
return control instanceof AbstractControl;
|
||||
}
|
||||
|
||||
function _find(control: AbstractControl, path: Array<string|number>| string) {
|
||||
if (isBlank(path)) return null;
|
||||
|
||||
if (!(path instanceof Array)) {
|
||||
path = (<string>path).split('/');
|
||||
}
|
||||
if (path instanceof Array && ListWrapper.isEmpty(path)) return null;
|
||||
|
||||
return (<Array<string|number>>path).reduce((v, name) => {
|
||||
if (v instanceof FormGroup) {
|
||||
return isPresent(v.controls[name]) ? v.controls[name] : null;
|
||||
} else if (v instanceof FormArray) {
|
||||
var index = <number>name;
|
||||
return isPresent(v.at(index)) ? v.at(index) : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, control);
|
||||
}
|
||||
|
||||
function toObservable(r: any): Observable<any> {
|
||||
return PromiseWrapper.isPromise(r) ? ObservableWrapper.fromPromise(r) : r;
|
||||
}
|
||||
|
||||
function coerceToValidator(validator: ValidatorFn | ValidatorFn[]): ValidatorFn {
|
||||
return Array.isArray(validator) ? composeValidators(validator) : validator;
|
||||
}
|
||||
|
||||
function coerceToAsyncValidator(asyncValidator: AsyncValidatorFn | AsyncValidatorFn[]):
|
||||
AsyncValidatorFn {
|
||||
return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) : asyncValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class AbstractControl {
|
||||
/** @internal */
|
||||
_value: any;
|
||||
|
||||
private _valueChanges: EventEmitter<any>;
|
||||
private _statusChanges: EventEmitter<any>;
|
||||
private _status: string;
|
||||
private _errors: {[key: string]: any};
|
||||
private _pristine: boolean = true;
|
||||
private _touched: boolean = false;
|
||||
private _parent: FormGroup|FormArray;
|
||||
private _asyncValidationSubscription: any;
|
||||
|
||||
constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {}
|
||||
|
||||
get value(): any { return this._value; }
|
||||
|
||||
get status(): string { return this._status; }
|
||||
|
||||
get valid(): boolean { return this._status === VALID; }
|
||||
|
||||
/**
|
||||
* Returns the errors of this control.
|
||||
*/
|
||||
get errors(): {[key: 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<any> { return this._valueChanges; }
|
||||
|
||||
get statusChanges(): Observable<any> { return this._statusChanges; }
|
||||
|
||||
get pending(): boolean { return this._status == PENDING; }
|
||||
|
||||
markAsTouched(): void { this._touched = true; }
|
||||
|
||||
markAsDirty({onlySelf}: {onlySelf?: boolean} = {}): void {
|
||||
onlySelf = normalizeBool(onlySelf);
|
||||
this._pristine = false;
|
||||
|
||||
if (isPresent(this._parent) && !onlySelf) {
|
||||
this._parent.markAsDirty({onlySelf: onlySelf});
|
||||
}
|
||||
}
|
||||
|
||||
markAsPending({onlySelf}: {onlySelf?: boolean} = {}): void {
|
||||
onlySelf = normalizeBool(onlySelf);
|
||||
this._status = PENDING;
|
||||
|
||||
if (isPresent(this._parent) && !onlySelf) {
|
||||
this._parent.markAsPending({onlySelf: onlySelf});
|
||||
}
|
||||
}
|
||||
|
||||
setParent(parent: FormGroup|FormArray): void { this._parent = parent; }
|
||||
|
||||
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
onlySelf = normalizeBool(onlySelf);
|
||||
emitEvent = isPresent(emitEvent) ? emitEvent : true;
|
||||
|
||||
this._updateValue();
|
||||
|
||||
this._errors = this._runValidator();
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (this._status == VALID || this._status == PENDING) {
|
||||
this._runAsyncValidator(emitEvent);
|
||||
}
|
||||
|
||||
if (emitEvent) {
|
||||
ObservableWrapper.callEmit(this._valueChanges, this._value);
|
||||
ObservableWrapper.callEmit(this._statusChanges, this._status);
|
||||
}
|
||||
|
||||
if (isPresent(this._parent) && !onlySelf) {
|
||||
this._parent.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
|
||||
}
|
||||
}
|
||||
|
||||
private _runValidator(): {[key: string]: any} {
|
||||
return isPresent(this.validator) ? this.validator(this) : null;
|
||||
}
|
||||
|
||||
private _runAsyncValidator(emitEvent: boolean): void {
|
||||
if (isPresent(this.asyncValidator)) {
|
||||
this._status = PENDING;
|
||||
this._cancelExistingSubscription();
|
||||
var obs = toObservable(this.asyncValidator(this));
|
||||
this._asyncValidationSubscription = ObservableWrapper.subscribe(
|
||||
obs, (res: {[key: string]: any}) => this.setErrors(res, {emitEvent: emitEvent}));
|
||||
}
|
||||
}
|
||||
|
||||
private _cancelExistingSubscription(): void {
|
||||
if (isPresent(this._asyncValidationSubscription)) {
|
||||
ObservableWrapper.dispose(this._asyncValidationSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets errors on a form control.
|
||||
*
|
||||
* This is used when validations are run not automatically, but manually by the user.
|
||||
*
|
||||
* Calling `setErrors` will also update the validity of the parent control.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```
|
||||
* var login = new FormControl("someLogin");
|
||||
* login.setErrors({
|
||||
* "notUnique": true
|
||||
* });
|
||||
*
|
||||
* expect(login.valid).toEqual(false);
|
||||
* expect(login.errors).toEqual({"notUnique": true});
|
||||
*
|
||||
* login.updateValue("someOtherLogin");
|
||||
*
|
||||
* expect(login.valid).toEqual(true);
|
||||
* ```
|
||||
*/
|
||||
setErrors(errors: {[key: string]: any}, {emitEvent}: {emitEvent?: boolean} = {}): void {
|
||||
emitEvent = isPresent(emitEvent) ? emitEvent : true;
|
||||
|
||||
this._errors = errors;
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (emitEvent) {
|
||||
ObservableWrapper.callEmit(this._statusChanges, this._status);
|
||||
}
|
||||
|
||||
if (isPresent(this._parent)) {
|
||||
this._parent._updateControlsErrors();
|
||||
}
|
||||
}
|
||||
|
||||
find(path: Array<string|number>|string): AbstractControl { return _find(this, path); }
|
||||
|
||||
getError(errorCode: string, path: string[] = null): any {
|
||||
var control = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this;
|
||||
if (isPresent(control) && isPresent(control._errors)) {
|
||||
return StringMapWrapper.get(control._errors, errorCode);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
hasError(errorCode: string, path: string[] = null): boolean {
|
||||
return isPresent(this.getError(errorCode, path));
|
||||
}
|
||||
|
||||
get root(): AbstractControl {
|
||||
let x: AbstractControl = this;
|
||||
|
||||
while (isPresent(x._parent)) {
|
||||
x = x._parent;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateControlsErrors(): void {
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (isPresent(this._parent)) {
|
||||
this._parent._updateControlsErrors();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_initObservables() {
|
||||
this._valueChanges = new EventEmitter();
|
||||
this._statusChanges = new EventEmitter();
|
||||
}
|
||||
|
||||
|
||||
private _calculateStatus(): string {
|
||||
if (isPresent(this._errors)) return INVALID;
|
||||
if (this._anyControlsHaveStatus(PENDING)) return PENDING;
|
||||
if (this._anyControlsHaveStatus(INVALID)) return INVALID;
|
||||
return VALID;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
abstract _updateValue(): void;
|
||||
|
||||
/** @internal */
|
||||
abstract _anyControlsHaveStatus(status: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a part of a form that cannot be divided into other controls. `FormControl`s have values
|
||||
* and
|
||||
* validation state, which is determined by an optional validation function.
|
||||
*
|
||||
* `FormControl` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along
|
||||
* with {@link FormGroup} and {@link FormArray}.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* By default, a `FormControl` is created for every `<input>` or other form component.
|
||||
* With {@link FormControlDirective} or {@link FormGroupDirective} an existing {@link FormControl}
|
||||
* can be bound to a DOM element instead. This `FormControl` can be configured with a custom
|
||||
* validation function.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class FormControl extends AbstractControl {
|
||||
/** @internal */
|
||||
_onChange: Function;
|
||||
|
||||
constructor(
|
||||
value: any = null, validator: ValidatorFn|ValidatorFn[] = null,
|
||||
asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null) {
|
||||
super(coerceToValidator(validator), coerceToAsyncValidator(asyncValidator));
|
||||
this._value = value;
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
this._initObservables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the form control to `value`.
|
||||
*
|
||||
* If `onlySelf` is `true`, this change will only affect the validation of this `FormControl`
|
||||
* and not its parent component. If `emitEvent` is `true`, this change will cause a
|
||||
* `valueChanges` event on the `FormControl` to be emitted. Both of these options default to
|
||||
* `false`.
|
||||
*
|
||||
* If `emitModelToViewChange` is `true`, the view will be notified about the new value
|
||||
* via an `onChange` event. This is the default behavior if `emitModelToViewChange` is not
|
||||
* specified.
|
||||
*/
|
||||
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});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_updateValue() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_anyControlsHaveStatus(status: string): boolean { return false; }
|
||||
|
||||
/**
|
||||
* Register a listener for change events.
|
||||
*/
|
||||
registerOnChange(fn: Function): void { this._onChange = fn; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a part of a form, of fixed length, that can contain other controls.
|
||||
*
|
||||
* A `FormGroup` aggregates the values of each {@link FormControl} in the group.
|
||||
* The status of a `FormGroup` depends on the status of its children.
|
||||
* 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.
|
||||
*
|
||||
* `FormGroup` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along with {@link FormControl} and {@link FormArray}. {@link FormArray} can also contain other
|
||||
* controls, but is of variable length.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class FormGroup extends AbstractControl {
|
||||
private _optionals: {[key: string]: boolean};
|
||||
|
||||
constructor(
|
||||
public controls: {[key: string]: AbstractControl}, optionals: {[key: string]: boolean} = null,
|
||||
validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._optionals = isPresent(optionals) ? optionals : {};
|
||||
this._initObservables();
|
||||
this._setParentForControls();
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a control with the group's list of controls.
|
||||
*/
|
||||
registerControl(name: string, control: AbstractControl): void {
|
||||
this.controls[name] = control;
|
||||
control.setParent(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a control to this group.
|
||||
*/
|
||||
addControl(name: string, control: AbstractControl): void {
|
||||
this.registerControl(name, control);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a control from this group.
|
||||
*/
|
||||
removeControl(name: string): void {
|
||||
StringMapWrapper.delete(this.controls, name);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the named control as non-optional.
|
||||
*/
|
||||
include(controlName: string): void {
|
||||
StringMapWrapper.set(this._optionals, controlName, true);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the named control as optional.
|
||||
*/
|
||||
exclude(controlName: string): void {
|
||||
StringMapWrapper.set(this._optionals, controlName, false);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there is a control with the given name in the group.
|
||||
*/
|
||||
contains(controlName: string): boolean {
|
||||
var c = StringMapWrapper.contains(this.controls, controlName);
|
||||
return c && this._included(controlName);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setParentForControls() {
|
||||
StringMapWrapper.forEach(
|
||||
this.controls, (control: AbstractControl, name: string) => { control.setParent(this); });
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateValue() { this._value = this._reduceValue(); }
|
||||
|
||||
/** @internal */
|
||||
_anyControlsHaveStatus(status: string): boolean {
|
||||
var res = false;
|
||||
StringMapWrapper.forEach(this.controls, (control: AbstractControl, name: string) => {
|
||||
res = res || (this.contains(name) && control.status == status);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceValue() {
|
||||
return this._reduceChildren(
|
||||
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
||||
acc[name] = control.value;
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceChildren(initValue: any, fn: Function) {
|
||||
var res = initValue;
|
||||
StringMapWrapper.forEach(this.controls, (control: AbstractControl, name: string) => {
|
||||
if (this._included(name)) {
|
||||
res = fn(res, control, name);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_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 `FormArray` aggregates the values of each {@link FormControl} in the group.
|
||||
* The status of a `FormArray` depends on the status of its children.
|
||||
* If one of the controls in a group is invalid, the entire array is invalid.
|
||||
* Similarly, if a control changes its value, the entire array changes as well.
|
||||
*
|
||||
* `FormArray` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along with {@link FormControl} and {@link FormGroup}. {@link FormGroup} can also contain
|
||||
* other controls, but is of fixed length.
|
||||
*
|
||||
* ## Adding or removing controls
|
||||
*
|
||||
* To change the controls in the array, use the `push`, `insert`, or `removeAt` methods
|
||||
* in `FormArray` itself. These methods ensure the controls are properly tracked in the
|
||||
* form's hierarchy. Do not modify the array of `AbstractControl`s used to instantiate
|
||||
* the `FormArray` directly, as that will result in strange and unexpected behavior such
|
||||
* as broken change detection.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class FormArray extends AbstractControl {
|
||||
constructor(
|
||||
public controls: AbstractControl[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._initObservables();
|
||||
this._setParentForControls();
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link AbstractControl} at the given `index` in the array.
|
||||
*/
|
||||
at(index: number): AbstractControl { return this.controls[index]; }
|
||||
|
||||
/**
|
||||
* Insert a new {@link AbstractControl} at the end of the array.
|
||||
*/
|
||||
push(control: AbstractControl): void {
|
||||
this.controls.push(control);
|
||||
control.setParent(this);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new {@link AbstractControl} at the given `index` in the array.
|
||||
*/
|
||||
insert(index: number, control: AbstractControl): void {
|
||||
ListWrapper.insert(this.controls, index, control);
|
||||
control.setParent(this);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the control at the given `index` in the array.
|
||||
*/
|
||||
removeAt(index: number): void {
|
||||
ListWrapper.removeAt(this.controls, index);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the control array.
|
||||
*/
|
||||
get length(): number { return this.controls.length; }
|
||||
|
||||
/** @internal */
|
||||
_updateValue(): void { this._value = this.controls.map((control) => control.value); }
|
||||
|
||||
/** @internal */
|
||||
_anyControlsHaveStatus(status: string): boolean {
|
||||
return this.controls.some(c => c.status == status);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
_setParentForControls(): void {
|
||||
this.controls.forEach((control) => { control.setParent(this); });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {ObservableWrapper} from './facade/async';
|
||||
import {StringMapWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, isString} from './facade/lang';
|
||||
import {PromiseWrapper} from './facade/promise';
|
||||
import * as modelModule from './model';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Providers for validators to be used for {@link FormControl}s in a form.
|
||||
*
|
||||
* Provide this using `multi: true` to add validators.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/forms/ts/ng_validators/ng_validators.ts region='ng_validators'}
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_VALIDATORS: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken('NgValidators');
|
||||
|
||||
/**
|
||||
* Providers for asynchronous validators to be used for {@link FormControl}s
|
||||
* in a form.
|
||||
*
|
||||
* Provide this using `multi: true` to add validators.
|
||||
*
|
||||
* See {@link NG_VALIDATORS} for more details.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_ASYNC_VALIDATORS: OpaqueToken =
|
||||
/*@ts2dart_const*/ new OpaqueToken('NgAsyncValidators');
|
||||
|
||||
/**
|
||||
* Provides a set of validators used by form controls.
|
||||
*
|
||||
* A validator is a function that processes a {@link FormControl} or collection of
|
||||
* controls and returns a map of errors. A null map means that validation has passed.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* var loginControl = new FormControl("", Validators.required)
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a non-empty value.
|
||||
*/
|
||||
static required(control: modelModule.AbstractControl): {[key: string]: boolean} {
|
||||
return isBlank(control.value) || (isString(control.value) && control.value == '') ?
|
||||
{'required': true} :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires controls to have a value of a minimum length.
|
||||
*/
|
||||
static minLength(minLength: number): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length < minLength ?
|
||||
{'minlength': {'requiredLength': minLength, 'actualLength': v.length}} :
|
||||
null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires controls to have a value of a maximum length.
|
||||
*/
|
||||
static maxLength(maxLength: number): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length > maxLength ?
|
||||
{'maxlength': {'requiredLength': maxLength, 'actualLength': v.length}} :
|
||||
null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires a control to match a regex to its value.
|
||||
*/
|
||||
static pattern(pattern: string): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
let regex = new RegExp(`^${pattern}$`);
|
||||
let v: string = control.value;
|
||||
return regex.test(v) ? null :
|
||||
{'pattern': {'requiredPattern': `^${pattern}$`, 'actualValue': v}};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op validator.
|
||||
*/
|
||||
static nullValidator(c: modelModule.AbstractControl): {[key: string]: boolean} { return null; }
|
||||
|
||||
/**
|
||||
* Compose multiple validators into a single function that returns the union
|
||||
* of the individual error maps.
|
||||
*/
|
||||
static compose(validators: ValidatorFn[]): ValidatorFn {
|
||||
if (isBlank(validators)) return null;
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
|
||||
return function(control: modelModule.AbstractControl) {
|
||||
return _mergeErrors(_executeValidators(control, presentValidators));
|
||||
};
|
||||
}
|
||||
|
||||
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn {
|
||||
if (isBlank(validators)) return null;
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
|
||||
return function(control: modelModule.AbstractControl) {
|
||||
let promises = _executeAsyncValidators(control, presentValidators).map(_convertToPromise);
|
||||
return PromiseWrapper.all(promises).then(_mergeErrors);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function _convertToPromise(obj: any): any {
|
||||
return PromiseWrapper.isPromise(obj) ? obj : ObservableWrapper.toPromise(obj);
|
||||
}
|
||||
|
||||
function _executeValidators(
|
||||
control: modelModule.AbstractControl, validators: ValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _executeAsyncValidators(
|
||||
control: modelModule.AbstractControl, validators: AsyncValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _mergeErrors(arrayOfErrors: any[]): {[key: string]: any} {
|
||||
var res: {[key: string]: any} =
|
||||
arrayOfErrors.reduce((res: {[key: string]: any}, errors: {[key: string]: any}) => {
|
||||
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
|
||||
}, {});
|
||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {fakeAsync, flushMicrotasks, Log, tick,} from '@angular/core/testing';
|
||||
|
||||
import {SpyNgControl, SpyValueAccessor} from './spies';
|
||||
|
||||
import {FormGroup, FormControl, FormControlName, FormGroupName, NgModelGroup, FormGroupDirective, ControlValueAccessor, Validators, NgForm, NgModel, FormControlDirective, NgControl, DefaultValueAccessor, CheckboxControlValueAccessor, SelectControlValueAccessor, Validator} from '@angular/forms';
|
||||
|
||||
import {selectValueAccessor, composeValidators} from '@angular/forms/src/directives/shared';
|
||||
import {TimerWrapper} from '../src/facade/async';
|
||||
import {PromiseWrapper} from '../src/facade/promise';
|
||||
import {SimpleChange} from '@angular/core/src/change_detection';
|
||||
|
||||
class DummyControlValueAccessor implements ControlValueAccessor {
|
||||
writtenValue: any;
|
||||
|
||||
registerOnChange(fn: any /** TODO #9100 */) {}
|
||||
registerOnTouched(fn: any /** TODO #9100 */) {}
|
||||
|
||||
writeValue(obj: any): void { this.writtenValue = obj; }
|
||||
}
|
||||
|
||||
class CustomValidatorDirective implements Validator {
|
||||
validate(c: FormControl): {[key: string]: any} { return {'custom': true}; }
|
||||
}
|
||||
|
||||
function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
var completer = PromiseWrapper.completer();
|
||||
var res = c.value != expected ? {'async': true} : null;
|
||||
if (timeout == 0) {
|
||||
completer.resolve(res);
|
||||
} else {
|
||||
TimerWrapper.setTimeout(() => { completer.resolve(res); }, timeout);
|
||||
}
|
||||
return completer.promise;
|
||||
};
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('Form Directives', () => {
|
||||
var defaultAccessor: DefaultValueAccessor;
|
||||
|
||||
beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null); });
|
||||
|
||||
describe('shared', () => {
|
||||
describe('selectValueAccessor', () => {
|
||||
var dir: NgControl;
|
||||
|
||||
beforeEach(() => { dir = <any>new SpyNgControl(); });
|
||||
|
||||
it('should throw when given an empty array',
|
||||
() => { expect(() => selectValueAccessor(dir, [])).toThrowError(); });
|
||||
|
||||
it('should return the default value accessor when no other provided',
|
||||
() => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); });
|
||||
|
||||
it('should return checkbox accessor when provided', () => {
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, [
|
||||
defaultAccessor, checkboxAccessor
|
||||
])).toEqual(checkboxAccessor);
|
||||
});
|
||||
|
||||
it('should return select accessor when provided', () => {
|
||||
var selectAccessor = new SelectControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, [
|
||||
defaultAccessor, selectAccessor
|
||||
])).toEqual(selectAccessor);
|
||||
});
|
||||
|
||||
it('should throw when more than one build-in accessor is provided', () => {
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
var selectAccessor = new SelectControlValueAccessor(null, null);
|
||||
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
|
||||
});
|
||||
|
||||
it('should return custom accessor when provided', () => {
|
||||
var customAccessor = new SpyValueAccessor();
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, <any>[defaultAccessor, customAccessor, checkboxAccessor]))
|
||||
.toEqual(customAccessor);
|
||||
});
|
||||
|
||||
it('should throw when more than one custom accessor is provided', () => {
|
||||
var customAccessor: ControlValueAccessor = <any>new SpyValueAccessor();
|
||||
expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('composeValidators', () => {
|
||||
it('should compose functions', () => {
|
||||
var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
||||
var dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true});
|
||||
var v = composeValidators([dummy1, dummy2]);
|
||||
expect(v(new FormControl(''))).toEqual({'dummy1': true, 'dummy2': true});
|
||||
});
|
||||
|
||||
it('should compose validator directives', () => {
|
||||
var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
||||
var v = composeValidators([dummy1, new CustomValidatorDirective()]);
|
||||
expect(v(new FormControl(''))).toEqual({'dummy1': true, 'custom': true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('formGroup', () => {
|
||||
var form: any /** TODO #9100 */;
|
||||
var formModel: FormGroup;
|
||||
var loginControlDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new FormGroupDirective([], []);
|
||||
formModel = new FormGroup({
|
||||
'login': new FormControl(),
|
||||
'passwords': new FormGroup(
|
||||
{'password': new FormControl(), 'passwordConfirm': new FormControl()})
|
||||
});
|
||||
form.form = formModel;
|
||||
|
||||
loginControlDir = new FormControlName(
|
||||
form, [Validators.required], [asyncValidator('expected')], [defaultAccessor]);
|
||||
loginControlDir.name = 'login';
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(form.control).toBe(formModel);
|
||||
expect(form.value).toBe(formModel.value);
|
||||
expect(form.valid).toBe(formModel.valid);
|
||||
expect(form.errors).toBe(formModel.errors);
|
||||
expect(form.pristine).toBe(formModel.pristine);
|
||||
expect(form.dirty).toBe(formModel.dirty);
|
||||
expect(form.touched).toBe(formModel.touched);
|
||||
expect(form.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
|
||||
describe('addControl', () => {
|
||||
it('should throw when no control found', () => {
|
||||
var dir = new FormControlName(form, null, null, [defaultAccessor]);
|
||||
dir.name = 'invalidName';
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp('Cannot find control \'invalidName\''));
|
||||
});
|
||||
|
||||
it('should throw when no value accessor', () => {
|
||||
var dir = new FormControlName(form, null, null, null);
|
||||
dir.name = 'login';
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp('No value accessor for \'login\''));
|
||||
});
|
||||
|
||||
it('should set up validators', fakeAsync(() => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError('required', ['login'])).toBe(true);
|
||||
expect(formModel.hasError('async', ['login'])).toBe(false);
|
||||
|
||||
(<FormControl>formModel.find(['login'])).updateValue('invalid value');
|
||||
|
||||
// sync validator passes, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError('required', ['login'])).toBe(false);
|
||||
expect(formModel.hasError('async', ['login'])).toBe(true);
|
||||
}));
|
||||
|
||||
it('should write value to the DOM', () => {
|
||||
(<FormControl>formModel.find(['login'])).updateValue('initValue');
|
||||
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual('initValue');
|
||||
});
|
||||
|
||||
it('should add the directive to the list of directives included in the form', () => {
|
||||
form.addControl(loginControlDir);
|
||||
expect(form.directives).toEqual([loginControlDir]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addFormGroup', () => {
|
||||
var matchingPasswordsValidator = (g: any /** TODO #9100 */) => {
|
||||
if (g.controls['password'].value != g.controls['passwordConfirm'].value) {
|
||||
return {'differentPasswords': true};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
it('should set up validator', fakeAsync(() => {
|
||||
var group = new FormGroupName(
|
||||
form, [matchingPasswordsValidator], [asyncValidator('expected')]);
|
||||
group.name = 'passwords';
|
||||
form.addFormGroup(group);
|
||||
|
||||
(<FormControl>formModel.find(['passwords', 'password'])).updateValue('somePassword');
|
||||
(<FormControl>formModel.find([
|
||||
'passwords', 'passwordConfirm'
|
||||
])).updateValue('someOtherPassword');
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError('differentPasswords', ['passwords'])).toEqual(true);
|
||||
|
||||
(<FormControl>formModel.find([
|
||||
'passwords', 'passwordConfirm'
|
||||
])).updateValue('somePassword');
|
||||
|
||||
// sync validators pass, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError('async', ['passwords'])).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('removeControl', () => {
|
||||
it('should remove the directive to the list of directives included in the form', () => {
|
||||
form.addControl(loginControlDir);
|
||||
form.removeControl(loginControlDir);
|
||||
expect(form.directives).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngOnChanges', () => {
|
||||
it('should update dom values of all the directives', () => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
(<FormControl>formModel.find(['login'])).updateValue('new value');
|
||||
|
||||
form.ngOnChanges({});
|
||||
|
||||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual('new value');
|
||||
});
|
||||
|
||||
it('should set up a sync validator', () => {
|
||||
var formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
|
||||
var f = new FormGroupDirective([formValidator], []);
|
||||
f.form = formModel;
|
||||
f.ngOnChanges({'form': new SimpleChange(null, null)});
|
||||
|
||||
expect(formModel.errors).toEqual({'custom': true});
|
||||
});
|
||||
|
||||
it('should set up an async validator', fakeAsync(() => {
|
||||
var f = new FormGroupDirective([], [asyncValidator('expected')]);
|
||||
f.form = formModel;
|
||||
f.ngOnChanges({'form': new SimpleChange(null, null)});
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgForm', () => {
|
||||
var form: any /** TODO #9100 */;
|
||||
var formModel: FormGroup;
|
||||
var loginControlDir: any /** TODO #9100 */;
|
||||
var personControlGroupDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgForm([], []);
|
||||
formModel = form.form;
|
||||
|
||||
personControlGroupDir = new NgModelGroup(form, [], []);
|
||||
personControlGroupDir.name = 'person';
|
||||
|
||||
loginControlDir = new FormControlName(personControlGroupDir, null, null, [defaultAccessor]);
|
||||
loginControlDir.name = 'login';
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(form.control).toBe(formModel);
|
||||
expect(form.value).toBe(formModel.value);
|
||||
expect(form.valid).toBe(formModel.valid);
|
||||
expect(form.errors).toBe(formModel.errors);
|
||||
expect(form.pristine).toBe(formModel.pristine);
|
||||
expect(form.dirty).toBe(formModel.dirty);
|
||||
expect(form.touched).toBe(formModel.touched);
|
||||
expect(form.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
|
||||
describe('addControl & addFormGroup', () => {
|
||||
it('should create a control with the given name', fakeAsync(() => {
|
||||
form.addFormGroup(personControlGroupDir);
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(formModel.find(['person', 'login'])).not.toBeNull;
|
||||
}));
|
||||
|
||||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
describe('removeControl & removeFormGroup', () => {
|
||||
it('should remove control', fakeAsync(() => {
|
||||
form.addFormGroup(personControlGroupDir);
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
form.removeFormGroup(personControlGroupDir);
|
||||
form.removeControl(loginControlDir);
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(formModel.find(['person'])).toBeNull();
|
||||
expect(formModel.find(['person', 'login'])).toBeNull();
|
||||
}));
|
||||
|
||||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
it('should set up sync validator', fakeAsync(() => {
|
||||
var formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
|
||||
var f = new NgForm([formValidator], []);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({'custom': true});
|
||||
}));
|
||||
|
||||
it('should set up async validator', fakeAsync(() => {
|
||||
var f = new NgForm([], [asyncValidator('expected')]);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('FormGroupName', () => {
|
||||
var formModel: any /** TODO #9100 */;
|
||||
var controlGroupDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
formModel = new FormGroup({'login': new FormControl(null)});
|
||||
|
||||
var parent = new FormGroupDirective([], []);
|
||||
parent.form = new FormGroup({'group': formModel});
|
||||
controlGroupDir = new FormGroupName(parent, [], []);
|
||||
controlGroupDir.name = 'group';
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(controlGroupDir.control).toBe(formModel);
|
||||
expect(controlGroupDir.value).toBe(formModel.value);
|
||||
expect(controlGroupDir.valid).toBe(formModel.valid);
|
||||
expect(controlGroupDir.errors).toBe(formModel.errors);
|
||||
expect(controlGroupDir.pristine).toBe(formModel.pristine);
|
||||
expect(controlGroupDir.dirty).toBe(formModel.dirty);
|
||||
expect(controlGroupDir.touched).toBe(formModel.touched);
|
||||
expect(controlGroupDir.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FormControlDirective', () => {
|
||||
var controlDir: any /** TODO #9100 */;
|
||||
var control: any /** TODO #9100 */;
|
||||
var checkProperties = function(control: any /** TODO #9100 */) {
|
||||
expect(controlDir.control).toBe(control);
|
||||
expect(controlDir.value).toBe(control.value);
|
||||
expect(controlDir.valid).toBe(control.valid);
|
||||
expect(controlDir.errors).toBe(control.errors);
|
||||
expect(controlDir.pristine).toBe(control.pristine);
|
||||
expect(controlDir.dirty).toBe(control.dirty);
|
||||
expect(controlDir.touched).toBe(control.touched);
|
||||
expect(controlDir.untouched).toBe(control.untouched);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
controlDir = new FormControlDirective([Validators.required], [], [defaultAccessor]);
|
||||
controlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
|
||||
control = new FormControl(null);
|
||||
controlDir.form = control;
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => { checkProperties(control); });
|
||||
|
||||
it('should reexport new control properties', () => {
|
||||
var newControl = new FormControl(null);
|
||||
controlDir.form = newControl;
|
||||
controlDir.ngOnChanges({'form': new SimpleChange(control, newControl)});
|
||||
|
||||
checkProperties(newControl);
|
||||
});
|
||||
|
||||
it('should set up validator', () => {
|
||||
expect(control.valid).toBe(true);
|
||||
|
||||
// this will add the required validator and recalculate the validity
|
||||
controlDir.ngOnChanges({'form': new SimpleChange(null, control)});
|
||||
|
||||
expect(control.valid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgModel', () => {
|
||||
var ngModel: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
ngModel = new NgModel(
|
||||
null, [Validators.required], [asyncValidator('expected')], [defaultAccessor]);
|
||||
ngModel.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
var control = ngModel.control;
|
||||
expect(ngModel.control).toBe(control);
|
||||
expect(ngModel.value).toBe(control.value);
|
||||
expect(ngModel.valid).toBe(control.valid);
|
||||
expect(ngModel.errors).toBe(control.errors);
|
||||
expect(ngModel.pristine).toBe(control.pristine);
|
||||
expect(ngModel.dirty).toBe(control.dirty);
|
||||
expect(ngModel.touched).toBe(control.touched);
|
||||
expect(ngModel.untouched).toBe(control.untouched);
|
||||
});
|
||||
|
||||
it('should set up validator', fakeAsync(() => {
|
||||
// this will add the required validator and recalculate the validity
|
||||
ngModel.ngOnChanges({});
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({'required': true});
|
||||
|
||||
ngModel.control.updateValue('someValue');
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('FormControlName', () => {
|
||||
var formModel: any /** TODO #9100 */;
|
||||
var controlNameDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
formModel = new FormControl('name');
|
||||
|
||||
var parent = new FormGroupDirective([], []);
|
||||
parent.form = new FormGroup({'name': formModel});
|
||||
controlNameDir = new FormControlName(parent, [], [], [defaultAccessor]);
|
||||
controlNameDir.name = 'name';
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(controlNameDir.control).toBe(formModel);
|
||||
expect(controlNameDir.value).toBe(formModel.value);
|
||||
expect(controlNameDir.valid).toBe(formModel.valid);
|
||||
expect(controlNameDir.errors).toBe(formModel.errors);
|
||||
expect(controlNameDir.pristine).toBe(formModel.pristine);
|
||||
expect(controlNameDir.dirty).toBe(formModel.dirty);
|
||||
expect(controlNameDir.touched).toBe(formModel.touched);
|
||||
expect(controlNameDir.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {FormBuilder, FormControl} from '@angular/forms';
|
||||
|
||||
import {PromiseWrapper} from '../src/facade/promise';
|
||||
|
||||
export function main() {
|
||||
function syncValidator(_: any /** TODO #9100 */): any /** TODO #9100 */ { return null; }
|
||||
function asyncValidator(_: any /** TODO #9100 */) { return PromiseWrapper.resolve(null); }
|
||||
|
||||
describe('Form Builder', () => {
|
||||
var b: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => { b = new FormBuilder(); });
|
||||
|
||||
it('should create controls from a value', () => {
|
||||
var g = b.group({'login': 'some value'});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
});
|
||||
|
||||
it('should create controls from an array', () => {
|
||||
var g = b.group(
|
||||
{'login': ['some value'], 'password': ['some value', syncValidator, asyncValidator]});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
expect(g.controls['password'].value).toEqual('some value');
|
||||
expect(g.controls['password'].validator).toEqual(syncValidator);
|
||||
expect(g.controls['password'].asyncValidator).toEqual(asyncValidator);
|
||||
});
|
||||
|
||||
it('should use controls', () => {
|
||||
var g = b.group({'login': b.control('some value', syncValidator, asyncValidator)});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
expect(g.controls['login'].validator).toBe(syncValidator);
|
||||
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
|
||||
it('should create groups with optional controls', () => {
|
||||
var g = b.group({'login': 'some value'}, {'optionals': {'login': false}});
|
||||
|
||||
expect(g.contains('login')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should create groups with a custom validator', () => {
|
||||
var g = b.group(
|
||||
{'login': 'some value'}, {'validator': syncValidator, 'asyncValidator': asyncValidator});
|
||||
|
||||
expect(g.validator).toBe(syncValidator);
|
||||
expect(g.asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
|
||||
it('should create control arrays', () => {
|
||||
var c = b.control('three');
|
||||
var a = b.array(
|
||||
['one', ['two', syncValidator], c, b.array(['four'])], syncValidator, asyncValidator);
|
||||
|
||||
expect(a.value).toEqual(['one', 'two', 'three', ['four']]);
|
||||
expect(a.validator).toBe(syncValidator);
|
||||
expect(a.asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,853 @@
|
||||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, inject,} from '@angular/core/testing/testing_internal';
|
||||
import {fakeAsync, flushMicrotasks, Log, tick} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
import {FormGroup, FormControl, FormArray, Validators} from '@angular/forms';
|
||||
import {IS_DART, isPresent} from '../src/facade/lang';
|
||||
import {PromiseWrapper} from '../src/facade/promise';
|
||||
import {TimerWrapper, ObservableWrapper, EventEmitter} from '../src/facade/async';
|
||||
|
||||
export function main() {
|
||||
function asyncValidator(expected: any /** TODO #9100 */, timeouts = /*@ts2dart_const*/ {}) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
var completer = PromiseWrapper.completer();
|
||||
var t = isPresent((timeouts as any /** TODO #9100 */)[c.value]) ?
|
||||
(timeouts as any /** TODO #9100 */)[c.value] :
|
||||
0;
|
||||
var res = c.value != expected ? {'async': true} : null;
|
||||
|
||||
if (t == 0) {
|
||||
completer.resolve(res);
|
||||
} else {
|
||||
TimerWrapper.setTimeout(() => { completer.resolve(res); }, t);
|
||||
}
|
||||
|
||||
return completer.promise;
|
||||
};
|
||||
}
|
||||
|
||||
function asyncValidatorReturningObservable(c: any /** TODO #9100 */) {
|
||||
var e = new EventEmitter();
|
||||
PromiseWrapper.scheduleMicrotask(() => ObservableWrapper.callEmit(e, {'async': true}));
|
||||
return e;
|
||||
}
|
||||
|
||||
function otherAsyncValidator() { return PromiseWrapper.resolve({'other': true}); }
|
||||
|
||||
describe('Form Model', () => {
|
||||
describe('FormControl', () => {
|
||||
it('should default the value to null', () => {
|
||||
var c = new FormControl();
|
||||
expect(c.value).toBe(null);
|
||||
});
|
||||
|
||||
describe('validator', () => {
|
||||
it('should run validator with the initial value', () => {
|
||||
var c = new FormControl('value', Validators.required);
|
||||
expect(c.valid).toEqual(true);
|
||||
});
|
||||
|
||||
it('should rerun the validator when the value changes', () => {
|
||||
var c = new FormControl('value', Validators.required);
|
||||
c.updateValue(null);
|
||||
expect(c.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it('should support arrays of validator functions if passed', () => {
|
||||
const c = new FormControl('value', [Validators.required, Validators.minLength(3)]);
|
||||
c.updateValue('a');
|
||||
expect(c.valid).toEqual(false);
|
||||
|
||||
c.updateValue('aaa');
|
||||
expect(c.valid).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return errors', () => {
|
||||
var c = new FormControl(null, Validators.required);
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run validator with the initial value', fakeAsync(() => {
|
||||
var c = new FormControl('value', null, asyncValidator('expected'));
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should support validators returning observables', fakeAsync(() => {
|
||||
var c = new FormControl('value', null, asyncValidatorReturningObservable);
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should rerun the validator when the value changes', fakeAsync(() => {
|
||||
var c = new FormControl('value', null, asyncValidator('expected'));
|
||||
|
||||
c.updateValue('expected');
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should run the async validator only when the sync validator passes', fakeAsync(() => {
|
||||
var c = new FormControl('', Validators.required, asyncValidator('expected'));
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
|
||||
c.updateValue('some value');
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should mark the control as pending while running the async validation',
|
||||
fakeAsync(() => {
|
||||
var c = new FormControl('', null, asyncValidator('expected'));
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(c.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should only use the latest async validation run', fakeAsync(() => {
|
||||
var c = new FormControl(
|
||||
'', null, asyncValidator('expected', {'long': 200, 'expected': 100}));
|
||||
|
||||
c.updateValue('long');
|
||||
c.updateValue('expected');
|
||||
|
||||
tick(300);
|
||||
|
||||
expect(c.valid).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should support arrays of async validator functions if passed', fakeAsync(() => {
|
||||
const c =
|
||||
new FormControl('value', null, [asyncValidator('expected'), otherAsyncValidator]);
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({'async': true, 'other': true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('dirty', () => {
|
||||
it('should be false after creating a control', () => {
|
||||
var c = new FormControl('value');
|
||||
expect(c.dirty).toEqual(false);
|
||||
});
|
||||
|
||||
it('should be true after changing the value of the control', () => {
|
||||
var c = new FormControl('value');
|
||||
c.markAsDirty();
|
||||
expect(c.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateValue', () => {
|
||||
var g: any /** TODO #9100 */, c: any /** TODO #9100 */;
|
||||
beforeEach(() => {
|
||||
c = new FormControl('oldValue');
|
||||
g = new FormGroup({'one': c});
|
||||
});
|
||||
|
||||
it('should update the value of the control', () => {
|
||||
c.updateValue('newValue');
|
||||
expect(c.value).toEqual('newValue');
|
||||
});
|
||||
|
||||
it('should invoke ngOnChanges if it is present', () => {
|
||||
var ngOnChanges: any /** TODO #9100 */;
|
||||
c.registerOnChange((v: any /** TODO #9100 */) => ngOnChanges = ['invoked', v]);
|
||||
|
||||
c.updateValue('newValue');
|
||||
|
||||
expect(ngOnChanges).toEqual(['invoked', 'newValue']);
|
||||
});
|
||||
|
||||
it('should not invoke on change when explicitly specified', () => {
|
||||
var onChange: any /** TODO #9100 */ = null;
|
||||
c.registerOnChange((v: any /** TODO #9100 */) => onChange = ['invoked', v]);
|
||||
|
||||
c.updateValue('newValue', {emitModelToViewChange: false});
|
||||
|
||||
expect(onChange).toBeNull();
|
||||
});
|
||||
|
||||
it('should update the parent', () => {
|
||||
c.updateValue('newValue');
|
||||
expect(g.value).toEqual({'one': 'newValue'});
|
||||
});
|
||||
|
||||
it('should not update the parent when explicitly specified', () => {
|
||||
c.updateValue('newValue', {onlySelf: true});
|
||||
expect(g.value).toEqual({'one': 'oldValue'});
|
||||
});
|
||||
|
||||
it('should fire an event', fakeAsync(() => {
|
||||
ObservableWrapper.subscribe(
|
||||
c.valueChanges, (value) => { expect(value).toEqual('newValue'); });
|
||||
|
||||
c.updateValue('newValue');
|
||||
tick();
|
||||
}));
|
||||
|
||||
it('should not fire an event when explicitly specified', fakeAsync(() => {
|
||||
ObservableWrapper.subscribe(c.valueChanges, (value) => { throw 'Should not happen'; });
|
||||
|
||||
c.updateValue('newValue', {emitEvent: false});
|
||||
|
||||
tick();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('valueChanges & statusChanges', () => {
|
||||
var c: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => { c = new FormControl('old', Validators.required); });
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
ObservableWrapper.subscribe(c.valueChanges, (value) => {
|
||||
expect(c.value).toEqual('new');
|
||||
expect(value).toEqual('new');
|
||||
async.done();
|
||||
});
|
||||
c.updateValue('new');
|
||||
}));
|
||||
|
||||
it('should fire an event after the status has been updated to invalid', fakeAsync(() => {
|
||||
ObservableWrapper.subscribe(c.statusChanges, (status) => {
|
||||
expect(c.status).toEqual('INVALID');
|
||||
expect(status).toEqual('INVALID');
|
||||
});
|
||||
|
||||
c.updateValue('');
|
||||
tick();
|
||||
}));
|
||||
|
||||
it('should fire an event after the status has been updated to pending', fakeAsync(() => {
|
||||
var c = new FormControl('old', Validators.required, asyncValidator('expected'));
|
||||
|
||||
var log: any[] /** TODO #9100 */ = [];
|
||||
ObservableWrapper.subscribe(c.valueChanges, (value) => log.push(`value: '${value}'`));
|
||||
ObservableWrapper.subscribe(
|
||||
c.statusChanges, (status) => log.push(`status: '${status}'`));
|
||||
|
||||
c.updateValue('');
|
||||
tick();
|
||||
|
||||
c.updateValue('nonEmpty');
|
||||
tick();
|
||||
|
||||
c.updateValue('expected');
|
||||
tick();
|
||||
|
||||
expect(log).toEqual([
|
||||
'' +
|
||||
'value: \'\'',
|
||||
'status: \'INVALID\'',
|
||||
'value: \'nonEmpty\'',
|
||||
'status: \'PENDING\'',
|
||||
'status: \'INVALID\'',
|
||||
'value: \'expected\'',
|
||||
'status: \'PENDING\'',
|
||||
'status: \'VALID\'',
|
||||
]);
|
||||
}));
|
||||
|
||||
// TODO: remove the if statement after making observable delivery sync
|
||||
if (!IS_DART) {
|
||||
it('should update set errors and status before emitting an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
c.valueChanges.subscribe((value: any /** TODO #9100 */) => {
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
async.done();
|
||||
});
|
||||
c.updateValue('');
|
||||
}));
|
||||
}
|
||||
|
||||
it('should return a cold observable',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
c.updateValue('will be ignored');
|
||||
ObservableWrapper.subscribe(c.valueChanges, (value) => {
|
||||
expect(value).toEqual('new');
|
||||
async.done();
|
||||
});
|
||||
c.updateValue('new');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('setErrors', () => {
|
||||
it('should set errors on a control', () => {
|
||||
var c = new FormControl('someValue');
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'someError': true});
|
||||
});
|
||||
|
||||
it('should reset the errors and validity when the value changes', () => {
|
||||
var c = new FormControl('someValue', Validators.required);
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
c.updateValue('');
|
||||
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
});
|
||||
|
||||
it('should update the parent group\'s validity', () => {
|
||||
var c = new FormControl('someValue');
|
||||
var g = new FormGroup({'one': c});
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not reset parent\'s errors', () => {
|
||||
var c = new FormControl('someValue');
|
||||
var g = new FormGroup({'one': c});
|
||||
|
||||
g.setErrors({'someGroupError': true});
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(g.errors).toEqual({'someGroupError': true});
|
||||
});
|
||||
|
||||
it('should reset errors when updating a value', () => {
|
||||
var c = new FormControl('oldValue');
|
||||
var g = new FormGroup({'one': c});
|
||||
|
||||
g.setErrors({'someGroupError': true});
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
c.updateValue('newValue');
|
||||
|
||||
expect(c.errors).toEqual(null);
|
||||
expect(g.errors).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('FormGroup', () => {
|
||||
describe('value', () => {
|
||||
it('should be the reduced value of the child controls', () => {
|
||||
var g = new FormGroup({'one': new FormControl('111'), 'two': new FormControl('222')});
|
||||
expect(g.value).toEqual({'one': '111', 'two': '222'});
|
||||
});
|
||||
|
||||
it('should be empty when there are no child controls', () => {
|
||||
var g = new FormGroup({});
|
||||
expect(g.value).toEqual({});
|
||||
});
|
||||
|
||||
it('should support nested groups', () => {
|
||||
var g = new FormGroup({
|
||||
'one': new FormControl('111'),
|
||||
'nested': new FormGroup({'two': new FormControl('222')})
|
||||
});
|
||||
expect(g.value).toEqual({'one': '111', 'nested': {'two': '222'}});
|
||||
|
||||
(<FormControl>(g.controls['nested'].find('two'))).updateValue('333');
|
||||
|
||||
expect(g.value).toEqual({'one': '111', 'nested': {'two': '333'}});
|
||||
});
|
||||
});
|
||||
|
||||
describe('adding and removing controls', () => {
|
||||
it('should update value and validity when control is added', () => {
|
||||
var g = new FormGroup({'one': new FormControl('1')});
|
||||
expect(g.value).toEqual({'one': '1'});
|
||||
expect(g.valid).toBe(true);
|
||||
|
||||
g.addControl('two', new FormControl('2', Validators.minLength(10)));
|
||||
|
||||
expect(g.value).toEqual({'one': '1', 'two': '2'});
|
||||
expect(g.valid).toBe(false);
|
||||
});
|
||||
|
||||
it('should update value and validity when control is removed', () => {
|
||||
var g = new FormGroup(
|
||||
{'one': new FormControl('1'), 'two': new FormControl('2', Validators.minLength(10))});
|
||||
expect(g.value).toEqual({'one': '1', 'two': '2'});
|
||||
expect(g.valid).toBe(false);
|
||||
|
||||
g.removeControl('two');
|
||||
|
||||
expect(g.value).toEqual({'one': '1'});
|
||||
expect(g.valid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should run the validator when the value changes', () => {
|
||||
var simpleValidator = (c: any /** TODO #9100 */) =>
|
||||
c.controls['one'].value != 'correct' ? {'broken': true} : null;
|
||||
|
||||
var c = new FormControl(null);
|
||||
var g = new FormGroup({'one': c}, null, simpleValidator);
|
||||
|
||||
c.updateValue('correct');
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
expect(g.errors).toEqual(null);
|
||||
|
||||
c.updateValue('incorrect');
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
expect(g.errors).toEqual({'broken': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dirty', () => {
|
||||
var c: any /** TODO #9100 */, g: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new FormControl('value');
|
||||
g = new FormGroup({'one': c});
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => { expect(g.dirty).toEqual(false); });
|
||||
|
||||
it('should be false after changing the value of the control', () => {
|
||||
c.markAsDirty();
|
||||
|
||||
expect(g.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('optional components', () => {
|
||||
describe('contains', () => {
|
||||
var group: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
group = new FormGroup(
|
||||
{
|
||||
'required': new FormControl('requiredValue'),
|
||||
'optional': new FormControl('optionalValue')
|
||||
},
|
||||
{'optional': false});
|
||||
});
|
||||
|
||||
// rename contains into has
|
||||
it('should return false when the component is not included',
|
||||
() => { expect(group.contains('optional')).toEqual(false); })
|
||||
|
||||
it('should return false when there is no component with the given name',
|
||||
() => { expect(group.contains('something else')).toEqual(false); });
|
||||
|
||||
it('should return true when the component is included', () => {
|
||||
expect(group.contains('required')).toEqual(true);
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.contains('optional')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include an inactive component into the group value', () => {
|
||||
var group = new FormGroup(
|
||||
{
|
||||
'required': new FormControl('requiredValue'),
|
||||
'optional': new FormControl('optionalValue')
|
||||
},
|
||||
{'optional': false});
|
||||
|
||||
expect(group.value).toEqual({'required': 'requiredValue'});
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.value).toEqual({'required': 'requiredValue', 'optional': 'optionalValue'});
|
||||
});
|
||||
|
||||
it('should not run Validators on an inactive component', () => {
|
||||
var group = new FormGroup(
|
||||
{
|
||||
'required': new FormControl('requiredValue', Validators.required),
|
||||
'optional': new FormControl('', Validators.required)
|
||||
},
|
||||
{'optional': false});
|
||||
|
||||
expect(group.valid).toEqual(true);
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.valid).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valueChanges', () => {
|
||||
var g: any /** TODO #9100 */, c1: any /** TODO #9100 */, c2: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c1 = new FormControl('old1');
|
||||
c2 = new FormControl('old2');
|
||||
g = new FormGroup({'one': c1, 'two': c2}, {'two': true});
|
||||
});
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
expect(g.value).toEqual({'one': 'new1', 'two': 'old2'});
|
||||
expect(value).toEqual({'one': 'new1', 'two': 'old2'});
|
||||
async.done();
|
||||
});
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event after the control\'s observable fired an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var controlCallbackIsCalled = false;
|
||||
|
||||
ObservableWrapper.subscribe(
|
||||
c1.valueChanges, (value) => { controlCallbackIsCalled = true; });
|
||||
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
expect(controlCallbackIsCalled).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is excluded',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
expect(value).toEqual({'one': 'old1'});
|
||||
async.done();
|
||||
});
|
||||
|
||||
g.exclude('two');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is included',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
g.exclude('two');
|
||||
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
expect(value).toEqual({'one': 'old1', 'two': 'old2'});
|
||||
async.done();
|
||||
});
|
||||
|
||||
g.include('two');
|
||||
}));
|
||||
|
||||
it('should fire an event every time a control is updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var loggedValues: any[] /** TODO #9100 */ = [];
|
||||
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
loggedValues.push(value);
|
||||
|
||||
if (loggedValues.length == 2) {
|
||||
expect(loggedValues).toEqual([
|
||||
{'one': 'new1', 'two': 'old2'}, {'one': 'new1', 'two': 'new2'}
|
||||
]);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
c2.updateValue('new2');
|
||||
}));
|
||||
|
||||
xit('should not fire an event when an excluded control is updated',
|
||||
inject(
|
||||
[AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
// hard to test without hacking zones
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getError', () => {
|
||||
it('should return the error when it is present', () => {
|
||||
var c = new FormControl('', Validators.required);
|
||||
var g = new FormGroup({'one': c});
|
||||
expect(c.getError('required')).toEqual(true);
|
||||
expect(g.getError('required', ['one'])).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return null otherwise', () => {
|
||||
var c = new FormControl('not empty', Validators.required);
|
||||
var g = new FormGroup({'one': c});
|
||||
expect(c.getError('invalid')).toEqual(null);
|
||||
expect(g.getError('required', ['one'])).toEqual(null);
|
||||
expect(g.getError('required', ['invalid'])).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run the async validator', fakeAsync(() => {
|
||||
var c = new FormControl('value');
|
||||
var g = new FormGroup({'one': c}, null, null, asyncValidator('expected'));
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should set the parent group\'s status to pending', fakeAsync(() => {
|
||||
var c = new FormControl('value', null, asyncValidator('expected'));
|
||||
var g = new FormGroup({'one': c});
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should run the parent group\'s async validator when children are pending',
|
||||
fakeAsync(() => {
|
||||
var c = new FormControl('value', null, asyncValidator('expected'));
|
||||
var g = new FormGroup({'one': c}, null, null, asyncValidator('expected'));
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.find(['one']).errors).toEqual({'async': true});
|
||||
}));
|
||||
})
|
||||
});
|
||||
|
||||
describe('FormArray', () => {
|
||||
describe('adding/removing', () => {
|
||||
var a: FormArray;
|
||||
var c1: any /** TODO #9100 */, c2: any /** TODO #9100 */, c3: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
a = new FormArray([]);
|
||||
c1 = new FormControl(1);
|
||||
c2 = new FormControl(2);
|
||||
c3 = new FormControl(3);
|
||||
});
|
||||
|
||||
it('should support pushing', () => {
|
||||
a.push(c1);
|
||||
expect(a.length).toEqual(1);
|
||||
expect(a.controls).toEqual([c1]);
|
||||
});
|
||||
|
||||
it('should support removing', () => {
|
||||
a.push(c1);
|
||||
a.push(c2);
|
||||
a.push(c3);
|
||||
|
||||
a.removeAt(1);
|
||||
|
||||
expect(a.controls).toEqual([c1, c3]);
|
||||
});
|
||||
|
||||
it('should support inserting', () => {
|
||||
a.push(c1);
|
||||
a.push(c3);
|
||||
|
||||
a.insert(1, c2);
|
||||
|
||||
expect(a.controls).toEqual([c1, c2, c3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('value', () => {
|
||||
it('should be the reduced value of the child controls', () => {
|
||||
var a = new FormArray([new FormControl(1), new FormControl(2)]);
|
||||
expect(a.value).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('should be an empty array when there are no child controls', () => {
|
||||
var a = new FormArray([]);
|
||||
expect(a.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should run the validator when the value changes', () => {
|
||||
var simpleValidator = (c: any /** TODO #9100 */) =>
|
||||
c.controls[0].value != 'correct' ? {'broken': true} : null;
|
||||
|
||||
var c = new FormControl(null);
|
||||
var g = new FormArray([c], simpleValidator);
|
||||
|
||||
c.updateValue('correct');
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
expect(g.errors).toEqual(null);
|
||||
|
||||
c.updateValue('incorrect');
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
expect(g.errors).toEqual({'broken': true});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('dirty', () => {
|
||||
var c: FormControl;
|
||||
var a: FormArray;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new FormControl('value');
|
||||
a = new FormArray([c]);
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => { expect(a.dirty).toEqual(false); });
|
||||
|
||||
it('should be false after changing the value of the control', () => {
|
||||
c.markAsDirty();
|
||||
|
||||
expect(a.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pending', () => {
|
||||
var c: FormControl;
|
||||
var a: FormArray;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new FormControl('value');
|
||||
a = new FormArray([c]);
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => {
|
||||
expect(c.pending).toEqual(false);
|
||||
expect(a.pending).toEqual(false);
|
||||
});
|
||||
|
||||
it('should be true after changing the value of the control', () => {
|
||||
c.markAsPending();
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
expect(a.pending).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not update the parent when onlySelf = true', () => {
|
||||
c.markAsPending({onlySelf: true});
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
expect(a.pending).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valueChanges', () => {
|
||||
var a: FormArray;
|
||||
var c1: any /** TODO #9100 */, c2: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c1 = new FormControl('old1');
|
||||
c2 = new FormControl('old2');
|
||||
a = new FormArray([c1, c2]);
|
||||
});
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
ObservableWrapper.subscribe(a.valueChanges, (value) => {
|
||||
expect(a.value).toEqual(['new1', 'old2']);
|
||||
expect(value).toEqual(['new1', 'old2']);
|
||||
async.done();
|
||||
});
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event after the control\'s observable fired an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var controlCallbackIsCalled = false;
|
||||
|
||||
ObservableWrapper.subscribe(
|
||||
c1.valueChanges, (value) => { controlCallbackIsCalled = true; });
|
||||
|
||||
ObservableWrapper.subscribe(a.valueChanges, (value) => {
|
||||
expect(controlCallbackIsCalled).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is removed',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
ObservableWrapper.subscribe(a.valueChanges, (value) => {
|
||||
expect(value).toEqual(['old1']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
a.removeAt(1);
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is added',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
a.removeAt(1);
|
||||
|
||||
ObservableWrapper.subscribe(a.valueChanges, (value) => {
|
||||
expect(value).toEqual(['old1', 'old2']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
a.push(c2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('find', () => {
|
||||
it('should return null when path is null', () => {
|
||||
var g = new FormGroup({});
|
||||
expect(g.find(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null when path is empty', () => {
|
||||
var g = new FormGroup({});
|
||||
expect(g.find([])).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null when path is invalid', () => {
|
||||
var g = new FormGroup({});
|
||||
expect(g.find(['one', 'two'])).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return a child of a control group', () => {
|
||||
var g = new FormGroup({
|
||||
'one': new FormControl('111'),
|
||||
'nested': new FormGroup({'two': new FormControl('222')})
|
||||
});
|
||||
|
||||
expect(g.find(['nested', 'two']).value).toEqual('222');
|
||||
expect(g.find(['one']).value).toEqual('111');
|
||||
expect(g.find('nested/two').value).toEqual('222');
|
||||
expect(g.find('one').value).toEqual('111');
|
||||
});
|
||||
|
||||
it('should return an element of an array', () => {
|
||||
var g = new FormGroup({'array': new FormArray([new FormControl('111')])});
|
||||
|
||||
expect(g.find(['array', 0]).value).toEqual('111');
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run the async validator', fakeAsync(() => {
|
||||
var c = new FormControl('value');
|
||||
var g = new FormArray([c], null, asyncValidator('expected'));
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
library core.spies;
|
||||
|
||||
import 'package:angular2/common.dart';
|
||||
import 'package:angular2/src/core/change_detection/change_detection.dart';
|
||||
import 'package:angular2/testing_internal.dart';
|
||||
|
||||
@proxy
|
||||
class SpyNgControl extends SpyObject implements NgControl {}
|
||||
|
||||
@proxy
|
||||
class SpyValueAccessor extends SpyObject implements ControlValueAccessor {}
|
||||
|
||||
@proxy
|
||||
class SpyChangeDetectorRef extends SpyObject implements ChangeDetectorRef {}
|
||||
@@ -0,0 +1,13 @@
|
||||
import {ChangeDetectorRef} from '@angular/core/src/change_detection/change_detector_ref';
|
||||
import {SpyObject, proxy} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export class SpyChangeDetectorRef extends SpyObject {
|
||||
constructor() {
|
||||
super(ChangeDetectorRef);
|
||||
this.spy('markForCheck');
|
||||
}
|
||||
}
|
||||
|
||||
export class SpyNgControl extends SpyObject {}
|
||||
|
||||
export class SpyValueAccessor extends SpyObject { writeValue: any; }
|
||||
@@ -0,0 +1,165 @@
|
||||
import {Log, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {AbstractControl, FormControl, Validators} from '@angular/forms';
|
||||
|
||||
import {EventEmitter, ObservableWrapper, TimerWrapper} from '../src/facade/async';
|
||||
import {PromiseWrapper} from '../src/facade/promise';
|
||||
|
||||
export function main() {
|
||||
function validator(key: string, error: any) {
|
||||
return function(c: AbstractControl) {
|
||||
var r = {};
|
||||
(r as any /** TODO #9100 */)[key] = error;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
describe('Validators', () => {
|
||||
describe('required', () => {
|
||||
it('should error on an empty string',
|
||||
() => { expect(Validators.required(new FormControl(''))).toEqual({'required': true}); });
|
||||
|
||||
it('should error on null',
|
||||
() => { expect(Validators.required(new FormControl(null))).toEqual({'required': true}); });
|
||||
|
||||
it('should not error on a non-empty string',
|
||||
() => { expect(Validators.required(new FormControl('not empty'))).toEqual(null); });
|
||||
|
||||
it('should accept zero as valid',
|
||||
() => { expect(Validators.required(new FormControl(0))).toEqual(null); });
|
||||
});
|
||||
|
||||
describe('minLength', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.minLength(2)(new FormControl(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.minLength(2)(new FormControl(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toEqual(null); });
|
||||
|
||||
it('should error on short strings', () => {
|
||||
expect(Validators.minLength(2)(new FormControl('a'))).toEqual({
|
||||
'minlength': {'requiredLength': 2, 'actualLength': 1}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxLength', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toEqual(null); });
|
||||
|
||||
it('should error on long strings', () => {
|
||||
expect(Validators.maxLength(2)(new FormControl('aaa'))).toEqual({
|
||||
'maxlength': {'requiredLength': 2, 'actualLength': 3}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pattern', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new FormControl(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new FormControl(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings', () => {
|
||||
expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toEqual(null);
|
||||
});
|
||||
|
||||
it('should error on failure to match string', () => {
|
||||
expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaa0'))).toEqual({
|
||||
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('compose', () => {
|
||||
it('should return null when given null',
|
||||
() => { expect(Validators.compose(null)).toBe(null); });
|
||||
|
||||
it('should collect errors from all the validators', () => {
|
||||
var c = Validators.compose([validator('a', true), validator('b', true)]);
|
||||
expect(c(new FormControl(''))).toEqual({'a': true, 'b': true});
|
||||
});
|
||||
|
||||
it('should run validators left to right', () => {
|
||||
var c = Validators.compose([validator('a', 1), validator('a', 2)]);
|
||||
expect(c(new FormControl(''))).toEqual({'a': 2});
|
||||
});
|
||||
|
||||
it('should return null when no errors', () => {
|
||||
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
|
||||
expect(c(new FormControl(''))).toEqual(null);
|
||||
});
|
||||
|
||||
it('should ignore nulls', () => {
|
||||
var c = Validators.compose([null, Validators.required]);
|
||||
expect(c(new FormControl(''))).toEqual({'required': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('composeAsync', () => {
|
||||
function asyncValidator(expected: any /** TODO #9100 */, response: any /** TODO #9100 */) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
var emitter = new EventEmitter();
|
||||
var res = c.value != expected ? response : null;
|
||||
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
ObservableWrapper.callEmit(emitter, res);
|
||||
// this is required because of a bug in ObservableWrapper
|
||||
// where callComplete can fire before callEmit
|
||||
// remove this one the bug is fixed
|
||||
TimerWrapper.setTimeout(() => { ObservableWrapper.callComplete(emitter); }, 0);
|
||||
});
|
||||
return emitter;
|
||||
};
|
||||
}
|
||||
|
||||
it('should return null when given null',
|
||||
() => { expect(Validators.composeAsync(null)).toEqual(null); });
|
||||
|
||||
it('should collect errors from all the validators', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([
|
||||
asyncValidator('expected', {'one': true}), asyncValidator('expected', {'two': true})
|
||||
]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new FormControl('invalid'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({'one': true, 'two': true});
|
||||
}));
|
||||
|
||||
it('should return null when no errors', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([asyncValidator('expected', {'one': true})]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new FormControl('expected'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual(null);
|
||||
}));
|
||||
|
||||
it('should ignore nulls', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([asyncValidator('expected', {'one': true}), null]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new FormControl('invalid'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({'one': true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true
|
||||
},
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"stripInternal": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../../../dist/packages-dist/forms/esm",
|
||||
"paths": {
|
||||
"@angular/core": ["../../../dist/packages-dist/core"],
|
||||
"@angular/core/testing": ["../../../dist/packages-dist/core/testing"],
|
||||
"@angular/common": ["../../../dist/packages-dist/common"],
|
||||
"@angular/common/testing": ["../../../dist/packages-dist/common/testing"],
|
||||
"@angular/compiler": ["../../../dist/packages-dist/compiler"],
|
||||
"@angular/compiler/testing": ["../../../dist/packages-dist/compiler/testing"]
|
||||
},
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"target": "es2015"
|
||||
},
|
||||
"files": [
|
||||
"index.ts",
|
||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true
|
||||
},
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"stripInternal": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../../../dist/packages-dist/forms/",
|
||||
"paths": {
|
||||
"@angular/core": ["../../../dist/packages-dist/core/"],
|
||||
"@angular/core/testing": ["../../../dist/packages-dist/core/testing"],
|
||||
"@angular/common": ["../../../dist/packages-dist/common"],
|
||||
"@angular/common/testing": ["../../../dist/packages-dist/common/testing"],
|
||||
"@angular/compiler": ["../../../dist/packages-dist/compiler"],
|
||||
"@angular/compiler/testing": ["../../../dist/packages-dist/compiler/testing"]
|
||||
},
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"target": "es5"
|
||||
},
|
||||
"files": [
|
||||
"index.ts",
|
||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user