feat(forms): initial implementation of forms declared in html
This commit is contained in:
parent
fa7cbf9bb1
commit
640134dee1
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue