feat(forms): initial implementation of forms declared in html

This commit is contained in:
vsavkin 2015-02-04 11:45:33 -08:00
parent fa7cbf9bb1
commit 640134dee1
2 changed files with 183 additions and 41 deletions

View File

@ -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<ControlDecorator>;
_directives:List<ControlNameDirective>;
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: '<content>'
})
})
export class NewControlGroupDirective extends ControlGroupDirectiveBase {
_initData:any;
_controlGroup:ControlGroup;
_directives:List<ControlNameDirective>;
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;
}
}

View File

@ -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 = `<div [new-control-group]="{'login': 'loginValue', 'password':'passValue'}">
<input id="login" [control]="'login'">
<input id="password" [control]="'password'">
</div>`;
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 = `<div [new-control-group]="{'login': 'loginValue'}">
<input [control]="'login'">
</div>`;
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;
}