From 640134dee1c85b541c06f6c806f05bdd87920f8d Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 4 Feb 2015 11:45:33 -0800 Subject: [PATCH] feat(forms): initial implementation of forms declared in html --- modules/angular2/src/forms/decorators.js | 162 ++++++++++++++---- .../angular2/test/forms/integration_spec.js | 62 ++++++- 2 files changed, 183 insertions(+), 41 deletions(-) diff --git a/modules/angular2/src/forms/decorators.js b/modules/angular2/src/forms/decorators.js index 87a0b8f187..348e4aab27 100644 --- a/modules/angular2/src/forms/decorators.js +++ b/modules/angular2/src/forms/decorators.js @@ -1,49 +1,89 @@ -import {Decorator, NgElement, Ancestor} from 'angular2/core'; +import {TemplateConfig, Component, Decorator, NgElement, Ancestor} from 'angular2/core'; import {DOM} from 'angular2/src/facade/dom'; -import {isPresent} from 'angular2/src/facade/lang'; +import {isBlank, isPresent} from 'angular2/src/facade/lang'; import {ListWrapper} from 'angular2/src/facade/collection'; import {ControlGroup, Control} from './model'; +export class ControlDirectiveBase { + _groupDecorator:ControlGroupDirectiveBase; + _el:NgElement; + _controlName:string; + + constructor(groupDecorator, el:NgElement) { + this._groupDecorator = groupDecorator; + this._el = el; + DOM.on(el.domElement, "change", (_) => this._updateControl()); + } + + set controlName(name:string) { + this._controlName = name; + this._groupDecorator.addDirective(this); + this._updateDOM(); + } + + get controlName() { + return this._controlName; + } + + //TODO:vsavkin: Remove it once change detection lifecycle callbacks are available + isInitialized():boolean { + return isPresent(this._controlName); + } + + _updateDOM() { + // remove it once all DOM write go through a queue + if (this.isInitialized()) { + var inputElement:any = this._el.domElement; + inputElement.value = this._control().value; + } + } + + _updateControl() { + var inputElement:any = this._el.domElement; + this._control().value = inputElement.value; + } + + _control() { + return this._groupDecorator.findControl(this._controlName); + } +} + +class ControlGroupDirectiveBase { + addDirective(c:ControlNameDirective):void {} + findControl(name:string):Control {} +} + + @Decorator({ selector: '[control-name]', bind: { 'control-name' : 'controlName' } }) -export class ControlDecorator { - _groupDecorator:ControlGroupDecorator; +export class ControlNameDirective extends ControlDirectiveBase { + _groupDecorator:ControlGroupDirective; _el:NgElement; _controlName:String; - constructor(@Ancestor() groupDecorator:ControlGroupDecorator, el:NgElement) { - this._groupDecorator = groupDecorator; - groupDecorator.addControlDecorator(this); + constructor(@Ancestor() groupDecorator:ControlGroupDirective, el:NgElement) { + super(groupDecorator, el); +} +} - this._el = el; - DOM.on(el.domElement, "change", (_) => this._updateControl()); +@Decorator({ + selector: '[control]', + bind: { + 'control' : 'controlName' } +}) +export class ControlDirective extends ControlDirectiveBase { + _groupDecorator:ControlGroupDirective; + _el:NgElement; + _controlName:String; - set controlName(name:string) { - this._controlName = name; - this._updateDOM(); - } - - _updateDOM() { - // remove it once all DOM write go throuh a queue - if (isPresent(this._controlName)) { - var inputElem: any = this._el.domElement; - inputElem.value = this._control().value; - } - } - - _updateControl() { - var inputElem: any = this._el.domElement; - this._control().value = inputElem.value; - } - - _control() { - return this._groupDecorator.findControl(this._controlName); - } + constructor(@Ancestor() groupDecorator:NewControlGroupDirective, el:NgElement) { + super(groupDecorator, el); +} } @Decorator({ @@ -52,24 +92,74 @@ export class ControlDecorator { 'control-group' : 'controlGroup' } }) -export class ControlGroupDecorator { +export class ControlGroupDirective extends ControlGroupDirectiveBase { _controlGroup:ControlGroup; - _controlDecorators:List; + _directives:List; constructor() { - this._controlDecorators = ListWrapper.create(); + this._directives = ListWrapper.create(); } set controlGroup(controlGroup:ControlGroup) { this._controlGroup = controlGroup; - ListWrapper.forEach(this._controlDecorators, (cd) => cd._updateDOM()); + ListWrapper.forEach(this._directives, (cd) => cd._updateDOM()); } - addControlDecorator(c:ControlDecorator) { - ListWrapper.push(this._controlDecorators, c); + addDirective(c:ControlNameDirective) { + ListWrapper.push(this._directives, c); } findControl(name:string):Control { return this._controlGroup.controls[name]; } } + +@Component({ + selector: '[new-control-group]', + bind: { + 'new-control-group' : 'initData' + }, + template: new TemplateConfig({ + inline: '' + }) +}) +export class NewControlGroupDirective extends ControlGroupDirectiveBase { + _initData:any; + _controlGroup:ControlGroup; + _directives:List; + + constructor() { + this._directives = ListWrapper.create(); + } + + set initData(value) { + this._initData = value; + } + + addDirective(c:ControlDirective) { + ListWrapper.push(this._directives, c); + this._controlGroup = null; + } + + findControl(name:string):Control { + if (isBlank(this._controlGroup)) { + this._controlGroup = this._createControlGroup(); + } + return this._controlGroup.controls[name]; + } + + _createControlGroup():ControlGroup { + var controls = ListWrapper.reduce(this._directives, (memo, cd) => { + if (cd.isInitialized()) { + var initControlValue = this._initData[cd.controlName]; + memo[cd.controlName] = new Control(initControlValue); + } + return memo; + }, {}); + return new ControlGroup(controls); + } + + get value() { + return this._controlGroup.value; + } +} diff --git a/modules/angular2/test/forms/integration_spec.js b/modules/angular2/test/forms/integration_spec.js index dea9195dad..1884e3bdaf 100644 --- a/modules/angular2/test/forms/integration_spec.js +++ b/modules/angular2/test/forms/integration_spec.js @@ -9,7 +9,11 @@ import {Injector} from 'angular2/di'; import {DOM} from 'angular2/src/facade/dom'; import {Component, TemplateConfig} from 'angular2/core'; -import {ControlDecorator, ControlGroupDecorator, Control, ControlGroup} from 'angular2/forms'; +import {ControlDirective, ControlNameDirective, ControlGroupDirective, NewControlGroupDirective, + Control, ControlGroup} from 'angular2/forms'; + +import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; +import {XHRMock} from 'angular2/src/mock/xhr_mock'; export function main() { function detectChanges(view) { @@ -17,8 +21,12 @@ export function main() { } function compile(componentType, template, context, callback) { - var compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(), - new Parser(new Lexer()), new CompilerCache(), new NativeShadowDomStrategy()); + var compiler = new Compiler(dynamicChangeDetection, + new TemplateLoader(new XHRMock()), + new DirectiveMetadataReader(), + new Parser(new Lexer()), + new CompilerCache(), + new NativeShadowDomStrategy()); compiler.compile(componentType, el(template)).then((pv) => { var view = pv.instantiate(null); @@ -28,6 +36,11 @@ export function main() { }); } + function formComponent(view) { + // TODO: vsavkin remove when view variables work + return view.elementInjectors[0].getComponent(); + } + describe("integration tests", () => { it("should initialize DOM elements with the given form object", (done) => { var ctx = new MyComp(new ControlGroup({ @@ -109,19 +122,58 @@ export function main() { done(); }); }); + + describe("declarative forms", () => { + it("should initialize dom elements", (done) => { + var t = `
+ + +
`; + + compile(MyComp, t, new MyComp(), (view) => { + var loginInput = queryView(view, "#login") + expect(loginInput.value).toEqual("loginValue"); + + var passInput = queryView(view, "#password") + expect(passInput.value).toEqual("passValue"); + + done(); + }); + }); + + it("should update the control group values on DOM change", (done) => { + var t = `
+ +
`; + + compile(MyComp, t, new MyComp(), (view) => { + var input = queryView(view, "input") + + input.value = "updatedValue"; + dispatchEvent(input, "change"); + + expect(formComponent(view).value).toEqual({'login': 'updatedValue'}); + done(); + }); + }); + + }); }); } @Component({ + selector: "my-comp", template: new TemplateConfig({ - directives: [ControlGroupDecorator, ControlDecorator] + inline: "", + directives: [ControlGroupDirective, ControlNameDirective, + ControlDirective, NewControlGroupDirective] }) }) class MyComp { form:ControlGroup; name:string; - constructor(form, name = null) { + constructor(form = null, name = null) { this.form = form; this.name = name; }