Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
10 changes: 9 additions & 1 deletion modules/angular2/src/core/forms/directives/control_container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
27 changes: 27 additions & 0 deletions modules/angular2/src/core/forms/directives/form_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 2 additions & 4 deletions modules/angular2/src/core/forms/directives/ng_control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
79 changes: 49 additions & 30 deletions modules/angular2/src/core/forms/directives/ng_control_group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,60 @@ 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)}));

/**
* Creates and binds a control group to a DOM element.
*
* 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 &amp; 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'
})
Expand All @@ -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); }
Expand Down
10 changes: 10 additions & 0 deletions modules/angular2/src/core/forms/directives/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
62 changes: 29 additions & 33 deletions modules/angular2/src/core/forms/form_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
* })
* });
* }
* }
*
* 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}:
*
* get value(): string {
* return JSON.stringify(this.loginForm.value, null, 2);
* }
* }
* ```
* 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);
Expand All @@ -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);
Expand All @@ -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)) {
Expand Down
Loading