work on mounted hook, make sure widgets are uniquely defined...

This commit is contained in:
Géry Debongnie
2019-01-25 11:12:20 +01:00
parent 9c0b63cd29
commit dbada21890
6 changed files with 87 additions and 17 deletions
+27 -8
View File
@@ -16,6 +16,7 @@ export class Widget<T extends WEnv> {
vnode: VNode | null = null; vnode: VNode | null = null;
isStarted: boolean = false; isStarted: boolean = false;
isMounted: boolean = false;
parent: Widget<T> | null = null; parent: Widget<T> | null = null;
children: Widget<T>[] = []; children: Widget<T>[] = [];
env: T; env: T;
@@ -48,17 +49,20 @@ export class Widget<T extends WEnv> {
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
async mount(target?: HTMLElement): Promise<VNode> { async mount(target: HTMLElement): Promise<VNode> {
await this.willStart(); await this._start();
this.isStarted = true;
const vnode = await this.render(); const vnode = await this.render();
target.appendChild(this.el!);
if (target) { if (document.body.contains(target)) {
target.appendChild(this.el!); this.visitSubTree(w => {
if (document.body.contains(target)) { if (!w.isMounted) {
this.visitSubTree(w => w.mounted()); w.isMounted = true;
} w.mounted();
}
});
} }
// }
return vnode; return vnode;
} }
@@ -93,6 +97,11 @@ export class Widget<T extends WEnv> {
return vnode; return vnode;
} }
private async _start(): Promise<void> {
await this.willStart();
this.isStarted = true;
}
private async _render(): Promise<VNode> { private async _render(): Promise<VNode> {
if (this.template) { if (this.template) {
this.env.qweb.addTemplate(this.name, this.template); this.env.qweb.addTemplate(this.name, this.template);
@@ -103,6 +112,16 @@ export class Widget<T extends WEnv> {
return Promise.all(promises).then(() => vnode); return Promise.all(promises).then(() => vnode);
} }
_mount(el: HTMLElement) {
this.el = el;
if (this.parent) {
if (this.parent.isMounted) {
this.isMounted = true;
this.mounted();
}
}
}
private visitSubTree(callback: (w: Widget<T>) => void) { private visitSubTree(callback: (w: Widget<T>) => void) {
callback(this); callback(this);
for (let child of this.children) { for (let child of this.children) {
+1 -1
View File
@@ -684,7 +684,7 @@ const widgetDirective: Directive = {
`let _${widgetID} = new context.widgets['${value}'](context, ${props})` `let _${widgetID} = new context.widgets['${value}'](context, ${props})`
); );
ctx.addLine( ctx.addLine(
`let def${defID} = _${widgetID}._render().then(vnode=>{Object.assign(_${dummyID}, vnode);_${dummyID}.data.hook = {create(_,vn){_${widgetID}.el=vn.elm}}})` `let def${defID} = _${widgetID}._start().then(() => _${widgetID}._render()).then(vnode=>{Object.assign(_${dummyID}, vnode);_${dummyID}.key="${dummyID}";_${dummyID}.data.hook = {create(_,vn){_${widgetID}._mount(vn.elm)}}})`
); );
ctx.addLine(`extra.promises.push(def${defID})`); ctx.addLine(`extra.promises.push(def${defID})`);
-3
View File
@@ -21,9 +21,6 @@ export class Counter extends Widget<Env> {
this.state.counter = props.initialState || 0; this.state.counter = props.initialState || 0;
} }
mounted() {
console.log("counter mounter", this.el);
}
increment(delta: number) { increment(delta: number) {
this.updateState({ counter: this.state.counter + delta }); this.updateState({ counter: this.state.counter + delta });
} }
-3
View File
@@ -18,9 +18,6 @@ export class Navbar extends Widget<Env> {
name = "navbar"; name = "navbar";
template = template; template = template;
mounted() {
console.log("navbar mounted", this.el);
}
getUrl(menu: Menu) { getUrl(menu: Menu) {
const action_id = String(menu.actionID); const action_id = String(menu.actionID);
return this.env.router.formatURL("", { action_id }); return this.env.router.formatURL("", { action_id });
-2
View File
@@ -15,12 +15,10 @@ export class Clock extends Widget<Env> {
} }
mounted() { mounted() {
console.log("clock mounter", this.el);
setInterval(this.updateTime.bind(this), 500); setInterval(this.updateTime.bind(this), 500);
} }
updateTime() { updateTime() {
debugger;
this.updateState({ this.updateState({
currentTime: new Date().toLocaleTimeString() currentTime: new Date().toLocaleTimeString()
}); });
+59
View File
@@ -130,6 +130,27 @@ describe("lifecycle hooks", () => {
target.remove(); target.remove();
}); });
test("willStart hook is called on subwidget", async () => {
expect.assertions(1);
let ok = false;
class ParentWidget extends Widget<TestEnv> {
name = "a";
template = `<div><t t-widget="child"/></div>`;
widgets = { child: ChildWidget };
}
class ChildWidget extends Widget<TestEnv> {
async willStart() {
ok = true;
}
}
const widget = makeWidget(ParentWidget);
const target = document.createElement("div");
document.body.appendChild(target);
await widget.mount(target);
expect(ok).toBe(true);
target.remove();
});
test("mounted hook is called on subwidgets, in proper order", async () => { test("mounted hook is called on subwidgets, in proper order", async () => {
expect.assertions(4); expect.assertions(4);
let parentMounted = false; let parentMounted = false;
@@ -157,6 +178,44 @@ describe("lifecycle hooks", () => {
expect(childMounted).toBe(true); expect(childMounted).toBe(true);
target.remove(); target.remove();
}); });
test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => {
expect.assertions(3);
let hookCounter = 0;
class ParentWidget extends Widget<TestEnv> {
name = "a";
state = { ok: false };
template = `
<div>
<t t-if="state.ok">
<t t-widget="child"/>
</t>
<t t-else="1">
<div/>
</t>
</div>`; // the t-else part in this template is important. This is
// necessary to have a situation that could confuse the vdom
// patching algorithm
widgets = { child: ChildWidget };
}
class ChildWidget extends Widget<TestEnv> {
async willStart() {
hookCounter++;
}
mounted() {
expect(hookCounter).toBe(1);
hookCounter++;
}
}
const widget = makeWidget(ParentWidget);
const target = document.createElement("div");
document.body.appendChild(target);
await widget.mount(target);
expect(hookCounter).toBe(0); // sub widget not created yet
await widget.updateState({ ok: true });
expect(hookCounter).toBe(2);
target.remove();
});
}); });
describe("destroy method", () => { describe("destroy method", () => {