Skip to content

Commit 640134d

Browse files
committed
feat(forms): initial implementation of forms declared in html
1 parent fa7cbf9 commit 640134d

File tree

2 files changed

+173
-31
lines changed

2 files changed

+173
-31
lines changed
Lines changed: 116 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,165 @@
1-
import {Decorator, NgElement, Ancestor} from 'angular2/core';
1+
import {TemplateConfig, Component, Decorator, NgElement, Ancestor} from 'angular2/core';
22
import {DOM} from 'angular2/src/facade/dom';
3-
import {isPresent} from 'angular2/src/facade/lang';
3+
import {isBlank, isPresent} from 'angular2/src/facade/lang';
44
import {ListWrapper} from 'angular2/src/facade/collection';
55
import {ControlGroup, Control} from './model';
66

7-
@Decorator({
8-
selector: '[control-name]',
9-
bind: {
10-
'control-name' : 'controlName'
11-
}
12-
})
13-
export class ControlDecorator {
14-
_groupDecorator:ControlGroupDecorator;
7+
export class ControlDirectiveBase {
8+
_groupDecorator:ControlGroupDirectiveBase;
159
_el:NgElement;
16-
_controlName:String;
10+
_controlName:string;
1711

18-
constructor(@Ancestor() groupDecorator:ControlGroupDecorator, el:NgElement) {
12+
constructor(groupDecorator, el:NgElement) {
1913
this._groupDecorator = groupDecorator;
20-
groupDecorator.addControlDecorator(this);
21-
2214
this._el = el;
2315
DOM.on(el.domElement, "change", (_) => this._updateControl());
2416
}
2517

2618
set controlName(name:string) {
2719
this._controlName = name;
20+
this._groupDecorator.addDirective(this);
2821
this._updateDOM();
2922
}
3023

24+
get controlName() {
25+
return this._controlName;
26+
}
27+
28+
//TODO:vsavkin: Remove it once change detection lifecycle callbacks are available
29+
isInitialized():boolean {
30+
return isPresent(this._controlName);
31+
}
32+
3133
_updateDOM() {
32-
// remove it once all DOM write go throuh a queue
33-
if (isPresent(this._controlName)) {
34-
var inputElem: any = this._el.domElement;
35-
inputElem.value = this._control().value;
34+
// remove it once all DOM write go through a queue
35+
if (this.isInitialized()) {
36+
var inputElement:any = this._el.domElement;
37+
inputElement.value = this._control().value;
3638
}
3739
}
3840

3941
_updateControl() {
40-
var inputElem: any = this._el.domElement;
41-
this._control().value = inputElem.value;
42+
var inputElement:any = this._el.domElement;
43+
this._control().value = inputElement.value;
4244
}
4345

4446
_control() {
4547
return this._groupDecorator.findControl(this._controlName);
4648
}
4749
}
4850

51+
class ControlGroupDirectiveBase {
52+
addDirective(c:ControlNameDirective):void {}
53+
findControl(name:string):Control {}
54+
}
55+
56+
57+
@Decorator({
58+
selector: '[control-name]',
59+
bind: {
60+
'control-name' : 'controlName'
61+
}
62+
})
63+
export class ControlNameDirective extends ControlDirectiveBase {
64+
_groupDecorator:ControlGroupDirective;
65+
_el:NgElement;
66+
_controlName:String;
67+
68+
constructor(@Ancestor() groupDecorator:ControlGroupDirective, el:NgElement) {
69+
super(groupDecorator, el);
70+
}
71+
}
72+
73+
@Decorator({
74+
selector: '[control]',
75+
bind: {
76+
'control' : 'controlName'
77+
}
78+
})
79+
export class ControlDirective extends ControlDirectiveBase {
80+
_groupDecorator:ControlGroupDirective;
81+
_el:NgElement;
82+
_controlName:String;
83+
84+
constructor(@Ancestor() groupDecorator:NewControlGroupDirective, el:NgElement) {
85+
super(groupDecorator, el);
86+
}
87+
}
88+
4989
@Decorator({
5090
selector: '[control-group]',
5191
bind: {
5292
'control-group' : 'controlGroup'
5393
}
5494
})
55-
export class ControlGroupDecorator {
95+
export class ControlGroupDirective extends ControlGroupDirectiveBase {
5696
_controlGroup:ControlGroup;
57-
_controlDecorators:List<ControlDecorator>;
97+
_directives:List<ControlNameDirective>;
5898

5999
constructor() {
60-
this._controlDecorators = ListWrapper.create();
100+
this._directives = ListWrapper.create();
61101
}
62102

63103
set controlGroup(controlGroup:ControlGroup) {
64104
this._controlGroup = controlGroup;
65-
ListWrapper.forEach(this._controlDecorators, (cd) => cd._updateDOM());
105+
ListWrapper.forEach(this._directives, (cd) => cd._updateDOM());
66106
}
67107

68-
addControlDecorator(c:ControlDecorator) {
69-
ListWrapper.push(this._controlDecorators, c);
108+
addDirective(c:ControlNameDirective) {
109+
ListWrapper.push(this._directives, c);
70110
}
71111

72112
findControl(name:string):Control {
73113
return this._controlGroup.controls[name];
74114
}
75115
}
116+
117+
@Component({
118+
selector: '[new-control-group]',
119+
bind: {
120+
'new-control-group' : 'initData'
121+
},
122+
template: new TemplateConfig({
123+
inline: '<content>'
124+
})
125+
})
126+
export class NewControlGroupDirective extends ControlGroupDirectiveBase {
127+
_initData:any;
128+
_controlGroup:ControlGroup;
129+
_directives:List<ControlNameDirective>;
130+
131+
constructor() {
132+
this._directives = ListWrapper.create();
133+
}
134+
135+
set initData(value) {
136+
this._initData = value;
137+
}
138+
139+
addDirective(c:ControlDirective) {
140+
ListWrapper.push(this._directives, c);
141+
this._controlGroup = null;
142+
}
143+
144+
findControl(name:string):Control {
145+
if (isBlank(this._controlGroup)) {
146+
this._controlGroup = this._createControlGroup();
147+
}
148+
return this._controlGroup.controls[name];
149+
}
150+
151+
_createControlGroup():ControlGroup {
152+
var controls = ListWrapper.reduce(this._directives, (memo, cd) => {
153+
if (cd.isInitialized()) {
154+
var initControlValue = this._initData[cd.controlName];
155+
memo[cd.controlName] = new Control(initControlValue);
156+
}
157+
return memo;
158+
}, {});
159+
return new ControlGroup(controls);
160+
}
161+
162+
get value() {
163+
return this._controlGroup.value;
164+
}
165+
}

modules/angular2/test/forms/integration_spec.js

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@ import {Injector} from 'angular2/di';
99
import {DOM} from 'angular2/src/facade/dom';
1010

1111
import {Component, TemplateConfig} from 'angular2/core';
12-
import {ControlDecorator, ControlGroupDecorator, Control, ControlGroup} from 'angular2/forms';
12+
import {ControlDirective, ControlNameDirective, ControlGroupDirective, NewControlGroupDirective,
13+
Control, ControlGroup} from 'angular2/forms';
14+
15+
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
16+
import {XHRMock} from 'angular2/src/mock/xhr_mock';
1317

1418
export function main() {
1519
function detectChanges(view) {
1620
view.changeDetector.detectChanges();
1721
}
1822

1923
function compile(componentType, template, context, callback) {
20-
var compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
21-
new Parser(new Lexer()), new CompilerCache(), new NativeShadowDomStrategy());
24+
var compiler = new Compiler(dynamicChangeDetection,
25+
new TemplateLoader(new XHRMock()),
26+
new DirectiveMetadataReader(),
27+
new Parser(new Lexer()),
28+
new CompilerCache(),
29+
new NativeShadowDomStrategy());
2230

2331
compiler.compile(componentType, el(template)).then((pv) => {
2432
var view = pv.instantiate(null);
@@ -28,6 +36,11 @@ export function main() {
2836
});
2937
}
3038

39+
function formComponent(view) {
40+
// TODO: vsavkin remove when view variables work
41+
return view.elementInjectors[0].getComponent();
42+
}
43+
3144
describe("integration tests", () => {
3245
it("should initialize DOM elements with the given form object", (done) => {
3346
var ctx = new MyComp(new ControlGroup({
@@ -109,19 +122,58 @@ export function main() {
109122
done();
110123
});
111124
});
125+
126+
describe("declarative forms", () => {
127+
it("should initialize dom elements", (done) => {
128+
var t = `<div [new-control-group]="{'login': 'loginValue', 'password':'passValue'}">
129+
<input id="login" [control]="'login'">
130+
<input id="password" [control]="'password'">
131+
</div>`;
132+
133+
compile(MyComp, t, new MyComp(), (view) => {
134+
var loginInput = queryView(view, "#login")
135+
expect(loginInput.value).toEqual("loginValue");
136+
137+
var passInput = queryView(view, "#password")
138+
expect(passInput.value).toEqual("passValue");
139+
140+
done();
141+
});
142+
});
143+
144+
it("should update the control group values on DOM change", (done) => {
145+
var t = `<div [new-control-group]="{'login': 'loginValue'}">
146+
<input [control]="'login'">
147+
</div>`;
148+
149+
compile(MyComp, t, new MyComp(), (view) => {
150+
var input = queryView(view, "input")
151+
152+
input.value = "updatedValue";
153+
dispatchEvent(input, "change");
154+
155+
expect(formComponent(view).value).toEqual({'login': 'updatedValue'});
156+
done();
157+
});
158+
});
159+
160+
});
112161
});
113162
}
114163

115164
@Component({
165+
selector: "my-comp",
116166
template: new TemplateConfig({
117-
directives: [ControlGroupDecorator, ControlDecorator]
167+
inline: "",
168+
directives: [ControlGroupDirective, ControlNameDirective,
169+
ControlDirective, NewControlGroupDirective]
118170
})
119171
})
120172
class MyComp {
121173
form:ControlGroup;
122174
name:string;
123175

124-
constructor(form, name = null) {
176+
constructor(form = null, name = null) {
125177
this.form = form;
126178
this.name = name;
127179
}

0 commit comments

Comments
 (0)