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