t-widget directive

This commit is contained in:
Géry Debongnie
2019-01-20 14:46:36 +01:00
parent d0bdf0ed8b
commit e40c9ee4e5
6 changed files with 67 additions and 35 deletions
+7
View File
@@ -5,6 +5,7 @@ const template = `
<div t-debug="1"> <div t-debug="1">
<span>Root Widget</span> <span>Root Widget</span>
<button t-on-click="resetCounter">Reset</button> <button t-on-click="resetCounter">Reset</button>
<button t-on-click="resetCounterAsync">Reset in 3s</button>
<button t-on-click="toggle">Toggle Counter</button> <button t-on-click="toggle">Toggle Counter</button>
<input/> <input/>
<t t-if="state.validcounter"> <t t-if="state.validcounter">
@@ -23,6 +24,12 @@ export default class RootWidget extends Widget {
this.refs.counter.updateState({counter: 3}) this.refs.counter.updateState({counter: 3})
} }
resetCounterAsync(ev: MouseEvent) {
setTimeout(() => {
this.refs.counter.updateState({counter: 3})
}, 3000);
}
toggle() { toggle() {
this.updateState({validcounter: !this.state.validcounter}); this.updateState({validcounter: !this.state.validcounter});
} }
+3 -3
View File
@@ -11,12 +11,12 @@
// (<any>window).h = h; // (<any>window).h = h;
// (<any>window).patch = patch; // (<any>window).patch = patch;
// (<any>window).QWeb = QWeb; // (<any>window).QWeb = QWeb;
import Counter from "./Counter"; // import Counter from "./Counter";
// import RootWidget from "./RootWidget"; import RootWidget from "./RootWidget";
import env from "./env"; import env from "./env";
document.addEventListener("DOMContentLoaded", async function() { document.addEventListener("DOMContentLoaded", async function() {
const rootWidget = new Counter(null, {}); const rootWidget = new RootWidget(null);
rootWidget.setEnvironment(env); rootWidget.setEnvironment(env);
const mainDiv = document.getElementById("app")!; const mainDiv = document.getElementById("app")!;
await rootWidget.mount(mainDiv); await rootWidget.mount(mainDiv);
+24 -19
View File
@@ -96,7 +96,8 @@ export default class QWeb {
elifDirective, elifDirective,
ifDirective, ifDirective,
callDirective, callDirective,
onDirective onDirective,
widgetDirective
].forEach(d => this.addDirective(d)); ].forEach(d => this.addDirective(d));
} }
@@ -641,21 +642,25 @@ const onDirective: Directive = {
} }
}; };
// const widgetDirective: Directive = { const widgetDirective: Directive = {
// name: "widget", name: "widget",
// priority: 100, priority: 100,
// atNodeEncounter({ ctx, fullName, value, node, qweb}): boolean { atNodeEncounter({ ctx, fullName, value, node, qweb}): boolean {
// let spanID = ctx.generateID(); let dummyID = ctx.generateID();
// ctx.addLine(`let ${spanID} = document.createElement('span')`); let defID = ctx.generateID();
// ctx.addNode(spanID) ctx.addLine(`let _${dummyID} = {} // DUMMY`);
// let props = node.getAttribute('t-props'); ctx.addLine(`c${ctx.parentNode}.push(_${dummyID})`);
// let widgetID = ctx.generateID(); let props = node.getAttribute('t-props');
// ctx.addLine(`let ${widgetID} = new context.widgets['${value}'](context, ${props})`); let widgetID = ctx.generateID();
// ctx.addLine(`${widgetID}.mount(${spanID}).then(()=>${ctx.parentNode}.replaceChild(${widgetID}.el, ${spanID}))`); ctx.addLine(`let _${widgetID} = new context.widgets['${value}'](context, ${props})`);
// let ref = node.getAttribute('t-ref'); ctx.addLine(`let def${defID} = _${widgetID}.mount().then(vnode=>Object.assign(_${dummyID}, vnode))`);
// if (ref) { ctx.addLine(`context._TEMP.push(def${defID})`);
// ctx.addLine(`context.refs['${ref}'] = ${widgetID}`);
// } // split into extra directive?
// return true; let ref = node.getAttribute('t-ref');
// } if (ref) {
// }; ctx.addLine(`context.refs['${ref}'] = _${widgetID}`);
}
return true;
}
};
+12 -12
View File
@@ -17,6 +17,7 @@ export default class Widget {
name: string = "widget"; name: string = "widget";
template: string = "<div></div>"; template: string = "<div></div>";
vnode: VNode | null = null; vnode: VNode | null = null;
_TEMP: Promise<any>[] | null = null;
parent: Widget | null; parent: Widget | null;
children: Widget[] = []; children: Widget[] = [];
@@ -50,13 +51,16 @@ export default class Widget {
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
async mount(target: HTMLElement) { async mount(target?: HTMLElement): Promise<VNode> {
await this.willStart(); await this.willStart();
this.env!.qweb.addTemplate(this.name, this.template); this.env!.qweb.addTemplate(this.name, this.template);
delete this.template; delete this.template;
await this.render(); const vnode = await this.render();
target.appendChild(this.el!); if (target) {
target.appendChild(this.el!);
}
return vnode;
} }
destroy() { destroy() {
@@ -84,19 +88,15 @@ export default class Widget {
// Private // Private
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
async render() { async render(): Promise<VNode> {
let vnode = await this.env!.qweb.render(this.name, this); this._TEMP = [];
let vnode = this.env!.qweb.render(this.name, this);
await Promise.all(this._TEMP);
if (!this.el) { if (!this.el) {
this.el = document.createElement(vnode.sel!); this.el = document.createElement(vnode.sel!);
} }
patch(this.vnode || this.el, vnode); patch(this.vnode || this.el, vnode);
this.vnode = vnode; this.vnode = vnode;
return vnode;
} }
// private _setElement(el: ChildNode) {
// if (this.el) {
// this.el.replaceWith(el);
// }
// this.el = el;
// }
} }
+1 -1
View File
@@ -311,7 +311,7 @@ describe("attributes", () => {
); );
}); });
xit("various escapes", () => { test.skip("various escapes", () => {
// need to think about this... This one does not pass, but I am not sure it is // need to think about this... This one does not pass, but I am not sure it is
// a correct test // a correct test
const template = ` const template = `
+20
View File
@@ -93,3 +93,23 @@ describe("destroy method", () => {
expect(document.contains(widget.el)).toBe(false); expect(document.contains(widget.el)).toBe(false);
}); });
}); });
describe("composition", () => {
test("a widget with a sub widget", async () => {
class WidgetB extends Widget {
template= `<div>world</div>`;
}
class WidgetA extends Widget {
name="a";
template= `<div>Hello<t t-widget="b"/></div>`;
widgets = {b: WidgetB}
}
const widget = makeWidget(WidgetA);
const target = document.createElement("div");
await widget.mount(target);
expect(target.innerHTML).toBe("<div>Hello<div>world</div></div>");
});
});