feat(forms): initial implementation of forms
This commit is contained in:
parent
4623e88509
commit
cdb1e82216
|
@ -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: {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from './src/model';
|
||||
export * from './src/decorators';
|
|
@ -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"
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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({})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -69,8 +69,4 @@ _handleAsync(fn) {
|
|||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
el(String html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -161,8 +161,4 @@ function elementText(n) {
|
|||
if (hasNodes(n)) return elementText(DOM.childNodesAsList(n));
|
||||
|
||||
return n.textContent;
|
||||
}
|
||||
|
||||
export function el(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue