Skip to content

Commit cdb1e82

Browse files
vsavkinalexeagle
authored andcommitted
feat(forms): initial implementation of forms
1 parent 4623e88 commit cdb1e82

File tree

13 files changed

+297
-9
lines changed

13 files changed

+297
-9
lines changed

karma-dart.conf.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ module.exports = function(config) {
4949
'/packages/di': 'http://localhost:9877/base/modules/di',
5050
'/packages/directives': 'http://localhost:9877/base/modules/directives',
5151
'/packages/facade': 'http://localhost:9877/base/modules/facade',
52-
'/packages/test_lib': 'http://localhost:9877/base/modules/test_lib',
52+
'/packages/forms': 'http://localhost:9877/base/modules/forms',
53+
'/packages/test_lib': 'http://localhost:9877/base/modules/test_lib'
5354
},
5455

5556
preprocessors: {

modules/core/core.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './src/annotations/annotations';
2+
export * from './src/annotations/visibility';
23
export * from './src/compiler/interfaces';
34
export * from './src/annotations/template_config';
45

modules/facade/src/dom.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class DOM {
4141
}
4242
static MouseEvent createMouseEvent(String eventType) =>
4343
new MouseEvent(eventType, canBubble: true);
44+
static createEvent(eventType) =>
45+
new Event(eventType, canBubble: true);
4446
static String getInnerHTML(Element el) => el.innerHtml;
4547
static String getOuterHTML(Element el) => el.outerHtml;
4648
static void setInnerHTML(Element el, String value) {

modules/facade/src/dom.es6

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export class DOM {
3232
evt.initEvent(eventType, true, true);
3333
return evt;
3434
}
35+
static createEvent(eventType) {
36+
return new Event(eventType, true);
37+
}
3538
static getInnerHTML(el) {
3639
return el.innerHTML;
3740
}

modules/forms/forms.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './src/model';
2+
export * from './src/decorators';

modules/forms/pubspec.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: forms
2+
environment:
3+
sdk: '>=1.4.0'
4+
dependencies:
5+
core:
6+
path: ../core
7+
di:
8+
path: ../di
9+
facade:
10+
path: ../facade
11+
dev_dependencies:
12+
test_lib:
13+
path: ../test_lib
14+
guinness: ">=0.1.16 <0.2.0"

modules/forms/src/decorators.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {Decorator, NgElement, Ancestor} from 'core/core';
2+
import {DOM} from 'facade/src/dom';
3+
import {isPresent} from 'facade/src/lang';
4+
import {ListWrapper} from 'facade/src/collection';
5+
import {ControlGroup, Control} from './model';
6+
7+
@Decorator({
8+
selector: '[control-name]',
9+
bind: {
10+
'control-name' : 'controlName'
11+
}
12+
})
13+
export class ControlDecorator {
14+
_groupDecorator:ControlGroupDecorator;
15+
_el:NgElement;
16+
_controlName:String;
17+
18+
constructor(@Ancestor() groupDecorator:ControlGroupDecorator, el:NgElement) {
19+
this._groupDecorator = groupDecorator;
20+
groupDecorator.addControlDecorator(this);
21+
22+
this._el = el;
23+
DOM.on(el.domElement, "change", (_) => this._updateControl());
24+
}
25+
26+
set controlName(name:string) {
27+
this._controlName = name;
28+
this._updateDOM();
29+
}
30+
31+
_updateDOM() {
32+
// remove it once all DOM write go throuh a queue
33+
if (isPresent(this._controlName)) {
34+
this._el.domElement.value = this._control().value;
35+
}
36+
}
37+
38+
_updateControl() {
39+
this._control().value = this._el.domElement.value;
40+
}
41+
42+
_control() {
43+
return this._groupDecorator.findControl(this._controlName);
44+
}
45+
}
46+
47+
@Decorator({
48+
selector: '[control-group]',
49+
bind: {
50+
'control-group' : 'controlGroup'
51+
}
52+
})
53+
export class ControlGroupDecorator {
54+
_controlGroup:ControlGroup;
55+
_controlDecorators:List<ControlDecorator>;
56+
57+
constructor() {
58+
this._controlDecorators = ListWrapper.create();
59+
}
60+
61+
set controlGroup(controlGroup:ControlGroup) {
62+
this._controlGroup = controlGroup;
63+
ListWrapper.forEach(this._controlDecorators, (cd) => cd._updateDOM());
64+
}
65+
66+
addControlDecorator(c:ControlDecorator) {
67+
ListWrapper.push(this._controlDecorators, c);
68+
}
69+
70+
findControl(name:string):Control {
71+
return this._controlGroup.controls[name];
72+
}
73+
}

modules/forms/src/model.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {StringMapWrapper} from 'facade/src/collection';
2+
3+
export class Control {
4+
value:any;
5+
6+
constructor(value:any) {
7+
this.value = value;
8+
}
9+
}
10+
11+
export class ControlGroup {
12+
controls;
13+
14+
constructor(controls) {
15+
this.controls = controls;
16+
}
17+
18+
get value() {
19+
var res = {};
20+
StringMapWrapper.forEach(this.controls, (control, name) => {
21+
res[name] = control.value;
22+
});
23+
return res;
24+
}
25+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach,
2+
el, queryView, dispatchEvent} from 'test_lib/test_lib';
3+
4+
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
5+
import {Compiler, CompilerCache} from 'core/src/compiler/compiler';
6+
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
7+
import {Injector} from 'di/di';
8+
import {DOM} from 'facade/src/dom';
9+
10+
import {Component, TemplateConfig} from 'core/core';
11+
import {ControlDecorator, ControlGroupDecorator, Control, ControlGroup} from 'forms/forms';
12+
13+
export function main() {
14+
function detectChanges(view) {
15+
view.changeDetector.detectChanges();
16+
}
17+
18+
function compile(componentType, template, context, callback) {
19+
var compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
20+
new Parser(new Lexer()), new CompilerCache());
21+
22+
compiler.compile(componentType, el(template)).then((pv) => {
23+
var view = pv.instantiate(null);
24+
view.hydrate(new Injector([]), null, context);
25+
detectChanges(view);
26+
callback(view);
27+
});
28+
}
29+
30+
var compiler;
31+
32+
beforeEach(() => {
33+
compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
34+
new Parser(new Lexer()), new CompilerCache());
35+
});
36+
37+
describe("integration tests", () => {
38+
it("should initialize DOM elements with the given form object", (done) => {
39+
var ctx = new MyComp(new ControlGroup({
40+
"login": new Control("loginValue")
41+
}));
42+
43+
var t = `<div [control-group]="form">
44+
<input [control-name]="'login'">
45+
</div>`;
46+
47+
compile(MyComp, t, ctx, (view) => {
48+
var input = queryView(view, "input")
49+
expect(input.value).toEqual("loginValue");
50+
done();
51+
});
52+
});
53+
54+
it("should update the control group values on DOM change", (done) => {
55+
var form = new ControlGroup({
56+
"login": new Control("oldValue")
57+
});
58+
var ctx = new MyComp(form);
59+
60+
var t = `<div [control-group]="form">
61+
<input [control-name]="'login'">
62+
</div>`;
63+
64+
compile(MyComp, t, ctx, (view) => {
65+
var input = queryView(view, "input")
66+
67+
input.value = "updatedValue";
68+
dispatchEvent(input, "change");
69+
70+
expect(form.value).toEqual({"login": "updatedValue"});
71+
done();
72+
});
73+
});
74+
75+
it("should update DOM elements when rebinding the control group", (done) => {
76+
var form = new ControlGroup({
77+
"login": new Control("oldValue")
78+
});
79+
var ctx = new MyComp(form);
80+
81+
var t = `<div [control-group]="form">
82+
<input [control-name]="'login'">
83+
</div>`;
84+
85+
compile(MyComp, t, ctx, (view) => {
86+
ctx.form = new ControlGroup({
87+
"login": new Control("newValue")
88+
});
89+
detectChanges(view);
90+
91+
var input = queryView(view, "input")
92+
expect(input.value).toEqual("newValue");
93+
done();
94+
});
95+
});
96+
97+
it("should update DOM element when rebinding the control name", (done) => {
98+
var ctx = new MyComp(new ControlGroup({
99+
"one": new Control("one"),
100+
"two": new Control("two")
101+
}), "one");
102+
103+
var t = `<div [control-group]="form">
104+
<input [control-name]="name">
105+
</div>`;
106+
107+
compile(MyComp, t, ctx, (view) => {
108+
var input = queryView(view, "input")
109+
expect(input.value).toEqual("one");
110+
111+
ctx.name = "two";
112+
detectChanges(view);
113+
114+
expect(input.value).toEqual("two");
115+
done();
116+
});
117+
});
118+
});
119+
}
120+
121+
@Component({
122+
template: new TemplateConfig({
123+
directives: [ControlGroupDecorator, ControlDecorator]
124+
})
125+
})
126+
class MyComp {
127+
form:ControlGroup;
128+
name:string;
129+
130+
constructor(form, name = null) {
131+
this.form = form;
132+
this.name = name;
133+
}
134+
}

modules/forms/test/model_spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'test_lib/test_lib';
2+
import {ControlGroup, Control} from 'forms/forms';
3+
4+
export function main() {
5+
describe("ControlGroup", () => {
6+
describe("value", () => {
7+
it("should be the reduced value of the child controls", () => {
8+
var g = new ControlGroup({
9+
"one": new Control("111"),
10+
"two": new Control("222")
11+
});
12+
expect(g.value).toEqual({"one": "111", "two": "222"})
13+
});
14+
15+
it("should be empty when there are no child controls", () => {
16+
var g = new ControlGroup({});
17+
expect(g.value).toEqual({})
18+
});
19+
});
20+
});
21+
}

0 commit comments

Comments
 (0)