parent
993b3d62de
commit
ef23fe66a0
|
@ -2,6 +2,11 @@ import {AbstractControl} from '../model';
|
|||
import {isPresent} from 'angular2/src/core/facade/lang';
|
||||
import {unimplemented} from 'angular2/src/core/facade/exceptions';
|
||||
|
||||
/**
|
||||
* Base class for control directives.
|
||||
*
|
||||
* Only used internally in the forms module.
|
||||
*/
|
||||
export abstract class AbstractControlDirective {
|
||||
get control(): AbstractControl { return unimplemented(); }
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ const CHECKBOX_VALUE_ACCESSOR = CONST_EXPR(new Provider(
|
|||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* <input type="checkbox" [ng-control]="rememberLogin">
|
||||
* <input type="checkbox" ng-control="rememberLogin">
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
|
|
|
@ -2,12 +2,20 @@ import {Form} from './form_interface';
|
|||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
|
||||
/**
|
||||
* A directive that contains multiple {@link NgControl}.
|
||||
* A directive that contains multiple {@link NgControl}s.
|
||||
*
|
||||
* Only used by the forms module.
|
||||
*/
|
||||
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; }
|
||||
}
|
||||
|
|
|
@ -4,11 +4,25 @@ import {OpaqueToken} from 'angular2/src/core/di';
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ const DEFAULT_VALUE_ACCESSOR = CONST_EXPR(new Provider(
|
|||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* <input type="text" [(ng-model)]="searchQuery">
|
||||
* <input type="text" ng-control="searchQuery">
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
|
|
|
@ -8,11 +8,38 @@ import {Control, ControlGroup} from '../model';
|
|||
* Only used by the forms module.
|
||||
*/
|
||||
export interface Form {
|
||||
/**
|
||||
* Add a control to this form.
|
||||
*/
|
||||
addControl(dir: NgControl): void;
|
||||
|
||||
/**
|
||||
* Remove a control from this form.
|
||||
*/
|
||||
removeControl(dir: NgControl): void;
|
||||
|
||||
/**
|
||||
* Look up the {@link Control} associated with a particular {@link NgControl}.
|
||||
*/
|
||||
getControl(dir: NgControl): Control;
|
||||
|
||||
/**
|
||||
* Add a group of controls to this form.
|
||||
*/
|
||||
addControlGroup(dir: NgControlGroup): void;
|
||||
|
||||
/**
|
||||
* Remove a group of controls from this form.
|
||||
*/
|
||||
removeControlGroup(dir: NgControlGroup): void;
|
||||
|
||||
/**
|
||||
* Look up the {@link ControlGroup} associated with a particular {@link NgControlGroup}.
|
||||
*/
|
||||
getControlGroup(dir: NgControlGroup): ControlGroup;
|
||||
|
||||
/**
|
||||
* Update the model for a particular control with a new value.
|
||||
*/
|
||||
updateModel(dir: NgControl, value: any): void;
|
||||
}
|
|
@ -5,11 +5,9 @@ import {unimplemented} from 'angular2/src/core/facade/exceptions';
|
|||
/**
|
||||
* A base class that all control directive extend.
|
||||
* It binds a {@link Control} object to a DOM element.
|
||||
*
|
||||
* Used internally by Angular forms.
|
||||
*/
|
||||
// Cannot currently be abstract because it would contain
|
||||
// an abstract method in the public API, and we cannot reflect
|
||||
// on that in Dart due to https://github.com/dart-lang/sdk/issues/18721
|
||||
// Also we don't have abstract setters, see https://github.com/Microsoft/TypeScript/issues/4669
|
||||
export abstract class NgControl extends AbstractControlDirective {
|
||||
name: string = null;
|
||||
valueAccessor: ControlValueAccessor = null;
|
||||
|
|
|
@ -10,7 +10,7 @@ import {ControlGroup} from '../model';
|
|||
import {Form} from './form_interface';
|
||||
import {Validators, NG_VALIDATORS} from '../validators';
|
||||
|
||||
const controlGroupBinding =
|
||||
const controlGroupProvider =
|
||||
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgControlGroup)}));
|
||||
|
||||
/**
|
||||
|
@ -18,42 +18,52 @@ const controlGroupBinding =
|
|||
*
|
||||
* This directive can only be used as a child of {@link NgForm} or {@link NgFormModel}.
|
||||
*
|
||||
* ### Example
|
||||
* # Example ([live demo](http://plnkr.co/edit/7EJ11uGeaggViYM6T5nq?p=preview))
|
||||
*
|
||||
* In this example, we create the credentials and personal control groups.
|
||||
* We can work with each group separately: check its validity, get its value, listen to its changes.
|
||||
*
|
||||
* ```
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "signup-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <form #f="form" (submit)='onSignUp(f.value)'>
|
||||
* <div ng-control-group='credentials' #credentials="form">
|
||||
* Login <input type='text' ng-control='login'>
|
||||
* Password <input type='password' ng-control='password'>
|
||||
* </div>
|
||||
* <div *ng-if="!credentials.valid">Credentials are invalid</div>
|
||||
*
|
||||
* <div ng-control-group='personal'>
|
||||
* Name <input type='text' ng-control='name'>
|
||||
* </div>
|
||||
* <button type='submit'>Sign Up!</button>
|
||||
* </form>
|
||||
* `})
|
||||
* class SignupComp {
|
||||
* onSignUp(value) {
|
||||
* // value === {
|
||||
* // personal: {name: 'some name'},
|
||||
* // credentials: {login: 'some login', password: 'some password'}}
|
||||
* }
|
||||
* selector: 'my-app',
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* })
|
||||
* @View({
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Angular2 Control & ControlGroup Example</h2>
|
||||
* <form #f="form">
|
||||
* <div ng-control-group="name" #cg-name="form">
|
||||
* <h3>Enter your name:</h3>
|
||||
* <p>First: <input ng-control="first" required></p>
|
||||
* <p>Middle: <input ng-control="middle"></p>
|
||||
* <p>Last: <input ng-control="last" required></p>
|
||||
* </div>
|
||||
* <h3>Name value:</h3>
|
||||
* <pre>{{valueOf(cgName)}}</pre>
|
||||
* <p>Name is {{cgName?.control?.valid ? "valid" : "invalid"}}</p>
|
||||
* <h3>What's your favorite food?</h3>
|
||||
* <p><input ng-control="food"></p>
|
||||
* <h3>Form value</h3>
|
||||
* <pre>{{valueOf(f)}}</pre>
|
||||
* </form>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: [FORM_DIRECTIVES]
|
||||
* })
|
||||
* export class App {
|
||||
* valueOf(cg: NgControlGroup): string {
|
||||
* if (cg.control == null) {
|
||||
* return null;
|
||||
* }
|
||||
* return JSON.stringify(cg.control.value, null, 2);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* This example declares a control group for a user's name. The value and validation state of
|
||||
* this group can be accessed separately from the overall form.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ng-control-group]',
|
||||
bindings: [controlGroupBinding],
|
||||
providers: [controlGroupProvider],
|
||||
inputs: ['name: ng-control-group'],
|
||||
exportAs: 'form'
|
||||
})
|
||||
|
@ -75,10 +85,19 @@ export class NgControlGroup extends ControlContainer implements OnInit,
|
|||
|
||||
onDestroy(): void { this.formDirective.removeControlGroup(this); }
|
||||
|
||||
/**
|
||||
* Get the {@link ControlGroup} backing this binding.
|
||||
*/
|
||||
get control(): ControlGroup { return this.formDirective.getControlGroup(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(): Function { return Validators.compose(this._validators); }
|
||||
|
|
|
@ -29,6 +29,16 @@ export interface Validator { validate(c: modelModule.Control): {[key: string]: a
|
|||
const REQUIRED_VALIDATOR =
|
||||
CONST_EXPR(new Provider(NG_VALIDATORS, {useValue: Validators.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 ng-control="fullName" required>
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]',
|
||||
providers: [REQUIRED_VALIDATOR]
|
||||
|
|
|
@ -7,63 +7,52 @@ import * as modelModule from './model';
|
|||
/**
|
||||
* Creates a form object from a user-specified configuration.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* import {Component, bootstrap} from 'angular2/angular2';
|
||||
* import {FormBuilder, Validators, FORM_DIRECTIVES, ControlGroup} from 'angular2/core';
|
||||
* ### Example ([live demo](http://plnkr.co/edit/ENgZo8EuIECZNensZCVr?p=preview))
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'login-comp',
|
||||
* viewProviders: [FormBuilder],
|
||||
* selector: 'my-app',
|
||||
* viewBindings: [FORM_BINDINGS]
|
||||
* template: `
|
||||
* <form [control-group]="loginForm">
|
||||
* Login <input control="login">
|
||||
*
|
||||
* <div control-group="passwordRetry">
|
||||
* Password <input type="password" control="password">
|
||||
* Confirm password <input type="password" control="passwordConfirmation">
|
||||
* <form [ng-form-model]="loginForm">
|
||||
* <p>Login <input ng-control="login"></p>
|
||||
* <div ng-control-group="passwordRetry">
|
||||
* <p>Password <input type="password" ng-control="password"></p>
|
||||
* <p>Confirm password <input type="password" ng-control="passwordConfirmation"></p>
|
||||
* </div>
|
||||
* </form>
|
||||
* <h3>Form value:</h3>
|
||||
* <pre>{{value}}</pre>
|
||||
* `,
|
||||
* directives: [FORM_DIRECTIVES]
|
||||
* })
|
||||
* class LoginComp {
|
||||
* export class App {
|
||||
* loginForm: ControlGroup;
|
||||
*
|
||||
* constructor(builder: FormBuilder) {
|
||||
* this.loginForm = builder.group({
|
||||
* login: ["", Validators.required],
|
||||
*
|
||||
* passwordRetry: builder.group({
|
||||
* password: ["", Validators.required],
|
||||
* passwordConfirmation: ["", Validators.required]
|
||||
* })
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* get value(): string {
|
||||
* return JSON.stringify(this.loginForm.value, null, 2);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* bootstrap(LoginComp);
|
||||
* ```
|
||||
*
|
||||
* This example creates a {@link ControlGroup} that consists of a `login` {@link Control}, and a
|
||||
* nested {@link ControlGroup} that defines a `password` and a `passwordConfirmation`
|
||||
* {@link Control}:
|
||||
*
|
||||
* ```
|
||||
* var loginForm = builder.group({
|
||||
* login: ["", Validators.required],
|
||||
*
|
||||
* passwordRetry: builder.group({
|
||||
* password: ["", Validators.required],
|
||||
* passwordConfirmation: ["", Validators.required]
|
||||
* })
|
||||
* });
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Injectable()
|
||||
export class FormBuilder {
|
||||
/**
|
||||
* Construct a new {@link ControlGroup} with the given map of configuration.
|
||||
* Valid keys for the `extra` parameter map are `optionals` and `validator`.
|
||||
*
|
||||
* See the {@link ControlGroup} constructor for more details.
|
||||
*/
|
||||
group(controlsConfig: {[key: string]: any},
|
||||
extra: {[key: string]: any} = null): modelModule.ControlGroup {
|
||||
var controls = this._reduceControls(controlsConfig);
|
||||
|
@ -77,6 +66,9 @@ export class FormBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link Control} with the given `value` and `validator`.
|
||||
*/
|
||||
control(value: Object, validator: Function = null): modelModule.Control {
|
||||
if (isPresent(validator)) {
|
||||
return new modelModule.Control(value, validator);
|
||||
|
@ -85,6 +77,10 @@ export class FormBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array of {@link Control}s from the given `controlsConfig` array of
|
||||
* configuration, with the given optional `validator`.
|
||||
*/
|
||||
array(controlsConfig: any[], validator: Function = null): modelModule.ControlArray {
|
||||
var controls = controlsConfig.map(c => this._createControl(c));
|
||||
if (isPresent(validator)) {
|
||||
|
|
|
@ -291,23 +291,38 @@ export class ControlGroup extends AbstractControl {
|
|||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a control to this group.
|
||||
*/
|
||||
addControl(name: string, control: AbstractControl): void {
|
||||
this.controls[name] = control;
|
||||
control.setParent(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a control from this group.
|
||||
*/
|
||||
removeControl(name: string): void { StringMapWrapper.delete(this.controls, name); }
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
@ -421,7 +436,7 @@ export class ControlArray extends AbstractControl {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the length of the control array.
|
||||
* Length of the control array.
|
||||
*/
|
||||
get length(): number { return this.controls.length; }
|
||||
|
||||
|
|
|
@ -5,22 +5,45 @@ import {OpaqueToken} from 'angular2/src/core/di';
|
|||
|
||||
import * as modelModule from './model';
|
||||
|
||||
/**
|
||||
* Providers for validators to be used for {@link Control}s in a form.
|
||||
*
|
||||
* Provide this using `multi: true` to add validators.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* var providers = [
|
||||
* new Provider(NG_VALIDATORS, {useValue: myValidator, multi: true})
|
||||
* ];
|
||||
* ```
|
||||
*/
|
||||
export const NG_VALIDATORS: OpaqueToken = CONST_EXPR(new OpaqueToken("NgValidators"));
|
||||
|
||||
/**
|
||||
* Provides a set of validators used by form controls.
|
||||
*
|
||||
* ### Example
|
||||
* A validator is a function that processes a {@link Control} or collection of
|
||||
* controls and returns a {@link StringMap} of errors. A null map means that
|
||||
* validation has passed.
|
||||
*
|
||||
* ```
|
||||
* # Example
|
||||
*
|
||||
* ```typescript
|
||||
* var loginControl = new Control("", Validators.required)
|
||||
* ```
|
||||
*/
|
||||
export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a non-empty value.
|
||||
*/
|
||||
static required(control: modelModule.Control): {[key: string]: boolean} {
|
||||
return isBlank(control.value) || control.value == "" ? {"required": true} : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires controls to have a value of a minimum length.
|
||||
*/
|
||||
static minLength(minLength: number): Function {
|
||||
return (control: modelModule.Control): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
|
@ -31,6 +54,9 @@ export class Validators {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires controls to have a value of a maximum length.
|
||||
*/
|
||||
static maxLength(maxLength: number): Function {
|
||||
return (control: modelModule.Control): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
|
@ -41,8 +67,15 @@ export class Validators {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op validator.
|
||||
*/
|
||||
static nullValidator(c: any): {[key: string]: boolean} { return null; }
|
||||
|
||||
/**
|
||||
* Compose multiple validators into a single function that returns the union
|
||||
* of the individual error maps.
|
||||
*/
|
||||
static compose(validators: Function[]): Function {
|
||||
if (isBlank(validators)) return Validators.nullValidator;
|
||||
|
||||
|
|
Loading…
Reference in New Issue