feat(forms): initial implementation of forms

This commit is contained in:
vsavkin 2015-02-03 07:27:09 -08:00 committed by Alex Eagle
parent 4623e88509
commit cdb1e82216
13 changed files with 297 additions and 9 deletions

View File

@ -49,7 +49,8 @@ module.exports = function(config) {
'/packages/di': 'http://localhost:9877/base/modules/di',
'/packages/directives': 'http://localhost:9877/base/modules/directives',
'/packages/facade': 'http://localhost:9877/base/modules/facade',
'/packages/test_lib': 'http://localhost:9877/base/modules/test_lib',
'/packages/forms': 'http://localhost:9877/base/modules/forms',
'/packages/test_lib': 'http://localhost:9877/base/modules/test_lib'
},
preprocessors: {

View File

@ -1,4 +1,5 @@
export * from './src/annotations/annotations';
export * from './src/annotations/visibility';
export * from './src/compiler/interfaces';
export * from './src/annotations/template_config';

View File

@ -41,6 +41,8 @@ class DOM {
}
static MouseEvent createMouseEvent(String eventType) =>
new MouseEvent(eventType, canBubble: true);
static createEvent(eventType) =>
new Event(eventType, canBubble: true);
static String getInnerHTML(Element el) => el.innerHtml;
static String getOuterHTML(Element el) => el.outerHtml;
static void setInnerHTML(Element el, String value) {

View File

@ -32,6 +32,9 @@ export class DOM {
evt.initEvent(eventType, true, true);
return evt;
}
static createEvent(eventType) {
return new Event(eventType, true);
}
static getInnerHTML(el) {
return el.innerHTML;
}

2
modules/forms/forms.js Normal file
View File

@ -0,0 +1,2 @@
export * from './src/model';
export * from './src/decorators';

View File

@ -0,0 +1,14 @@
name: forms
environment:
sdk: '>=1.4.0'
dependencies:
core:
path: ../core
di:
path: ../di
facade:
path: ../facade
dev_dependencies:
test_lib:
path: ../test_lib
guinness: ">=0.1.16 <0.2.0"

View File

@ -0,0 +1,73 @@
import {Decorator, NgElement, Ancestor} from 'core/core';
import {DOM} from 'facade/src/dom';
import {isPresent} from 'facade/src/lang';
import {ListWrapper} from 'facade/src/collection';
import {ControlGroup, Control} from './model';
@Decorator({
selector: '[control-name]',
bind: {
'control-name' : 'controlName'
}
})
export class ControlDecorator {
_groupDecorator:ControlGroupDecorator;
_el:NgElement;
_controlName:String;
constructor(@Ancestor() groupDecorator:ControlGroupDecorator, el:NgElement) {
this._groupDecorator = groupDecorator;
groupDecorator.addControlDecorator(this);
this._el = el;
DOM.on(el.domElement, "change", (_) => this._updateControl());
}
set controlName(name:string) {
this._controlName = name;
this._updateDOM();
}
_updateDOM() {
// remove it once all DOM write go throuh a queue
if (isPresent(this._controlName)) {
this._el.domElement.value = this._control().value;
}
}
_updateControl() {
this._control().value = this._el.domElement.value;
}
_control() {
return this._groupDecorator.findControl(this._controlName);
}
}
@Decorator({
selector: '[control-group]',
bind: {
'control-group' : 'controlGroup'
}
})
export class ControlGroupDecorator {
_controlGroup:ControlGroup;
_controlDecorators:List<ControlDecorator>;
constructor() {
this._controlDecorators = ListWrapper.create();
}
set controlGroup(controlGroup:ControlGroup) {
this._controlGroup = controlGroup;
ListWrapper.forEach(this._controlDecorators, (cd) => cd._updateDOM());
}
addControlDecorator(c:ControlDecorator) {
ListWrapper.push(this._controlDecorators, c);
}
findControl(name:string):Control {
return this._controlGroup.controls[name];
}
}

View File

@ -0,0 +1,25 @@
import {StringMapWrapper} from 'facade/src/collection';
export class Control {
value:any;
constructor(value:any) {
this.value = value;
}
}
export class ControlGroup {
controls;
constructor(controls) {
this.controls = controls;
}
get value() {
var res = {};
StringMapWrapper.forEach(this.controls, (control, name) => {
res[name] = control.value;
});
return res;
}
}

View File

@ -0,0 +1,134 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach,
el, queryView, dispatchEvent} from 'test_lib/test_lib';
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/src/compiler/compiler';
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
import {Injector} from 'di/di';
import {DOM} from 'facade/src/dom';
import {Component, TemplateConfig} from 'core/core';
import {ControlDecorator, ControlGroupDecorator, Control, ControlGroup} from 'forms/forms';
export function main() {
function detectChanges(view) {
view.changeDetector.detectChanges();
}
function compile(componentType, template, context, callback) {
var compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
new Parser(new Lexer()), new CompilerCache());
compiler.compile(componentType, el(template)).then((pv) => {
var view = pv.instantiate(null);
view.hydrate(new Injector([]), null, context);
detectChanges(view);
callback(view);
});
}
var compiler;
beforeEach(() => {
compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
new Parser(new Lexer()), new CompilerCache());
});
describe("integration tests", () => {
it("should initialize DOM elements with the given form object", (done) => {
var ctx = new MyComp(new ControlGroup({
"login": new Control("loginValue")
}));
var t = `<div [control-group]="form">
<input [control-name]="'login'">
</div>`;
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("loginValue");
done();
});
});
it("should update the control group values on DOM change", (done) => {
var form = new ControlGroup({
"login": new Control("oldValue")
});
var ctx = new MyComp(form);
var t = `<div [control-group]="form">
<input [control-name]="'login'">
</div>`;
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
input.value = "updatedValue";
dispatchEvent(input, "change");
expect(form.value).toEqual({"login": "updatedValue"});
done();
});
});
it("should update DOM elements when rebinding the control group", (done) => {
var form = new ControlGroup({
"login": new Control("oldValue")
});
var ctx = new MyComp(form);
var t = `<div [control-group]="form">
<input [control-name]="'login'">
</div>`;
compile(MyComp, t, ctx, (view) => {
ctx.form = new ControlGroup({
"login": new Control("newValue")
});
detectChanges(view);
var input = queryView(view, "input")
expect(input.value).toEqual("newValue");
done();
});
});
it("should update DOM element when rebinding the control name", (done) => {
var ctx = new MyComp(new ControlGroup({
"one": new Control("one"),
"two": new Control("two")
}), "one");
var t = `<div [control-group]="form">
<input [control-name]="name">
</div>`;
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("one");
ctx.name = "two";
detectChanges(view);
expect(input.value).toEqual("two");
done();
});
});
});
}
@Component({
template: new TemplateConfig({
directives: [ControlGroupDecorator, ControlDecorator]
})
})
class MyComp {
form:ControlGroup;
name:string;
constructor(form, name = null) {
this.form = form;
this.name = name;
}
}

View File

@ -0,0 +1,21 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'test_lib/test_lib';
import {ControlGroup, Control} from 'forms/forms';
export function main() {
describe("ControlGroup", () => {
describe("value", () => {
it("should be the reduced value of the child controls", () => {
var g = new ControlGroup({
"one": new Control("111"),
"two": new Control("222")
});
expect(g.value).toEqual({"one": "111", "two": "222"})
});
it("should be empty when there are no child controls", () => {
var g = new ControlGroup({});
expect(g.value).toEqual({})
});
});
});
}

View File

@ -70,7 +70,3 @@ _handleAsync(fn) {
return fn;
}
el(String html) {
return DOM.createTemplate(html).content.firstChild;
}

View File

@ -162,7 +162,3 @@ function elementText(n) {
return n.textContent;
}
export function el(html) {
return DOM.createTemplate(html).content.firstChild;
}

View File

@ -1,4 +1,6 @@
import {List, ListWrapper} from 'facade/src/collection';
import {DOM} from 'facade/src/dom';
import {isPresent} from 'facade/src/lang';
export class Log {
_result:List;
@ -21,3 +23,21 @@ export class Log {
return ListWrapper.join(this._result, "; ");
}
}
export function queryView(view, selector) {
for (var i = 0; i < view.nodes.length; ++i) {
var res = DOM.querySelector(view.nodes[i], selector);
if (isPresent(res)) {
return res;
}
}
return null;
}
export function dispatchEvent(element, eventType) {
DOM.dispatchEvent(element, DOM.createEvent(eventType));
}
export function el(html) {
return DOM.firstChild(DOM.createTemplate(html).content);
}