[FIX] component: call mounted hooks in subcomponent properly

order was not correct in some cases.  Also, this commit is a pretty
significant optimization.
This commit is contained in:
Géry Debongnie
2019-04-26 17:32:02 +02:00
parent 005b727328
commit b08e876136
2 changed files with 63 additions and 37 deletions
+15 -37
View File
@@ -191,24 +191,6 @@ export class Component<
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
/**
* Attach a child widget to a given html element
*
* This is most of the time not necessary, since widgets should primarily be
* created/managed with the t-widget directive in a qweb template. However,
* for the cases where we need more control, this method will do what is
* necessary to make sure all the proper hooks are called (for example,
* mounted/willUnmount)
*
* Note that this method makes a few assumptions:
* - the child widget is indeed a child of the current widget
* - the target is inside the dom of the current widget (typically a ref)
*/
attachChild(child: Component<T, any, any>, target: HTMLElement) {
target.appendChild(child.el!);
child.__mount();
}
async mount(target: HTMLElement): Promise<void> { async mount(target: HTMLElement): Promise<void> {
const vnode = await this._prepare(); const vnode = await this._prepare();
if (this.__owl__.isDestroyed) { if (this.__owl__.isDestroyed) {
@@ -271,14 +253,14 @@ export class Component<
destroy() { destroy() {
if (!this.__owl__.isDestroyed) { if (!this.__owl__.isDestroyed) {
const el = this.el; const el = this.el;
this._destroy(); this._destroy(this.__owl__.parent);
if (el) { if (el) {
el.remove(); el.remove();
} }
} }
} }
_destroy() { _destroy(parent) {
const isMounted = this.__owl__.isMounted; const isMounted = this.__owl__.isMounted;
if (isMounted) { if (isMounted) {
this.willUnmount(); this.willUnmount();
@@ -286,11 +268,11 @@ export class Component<
} }
const children = Object.values(this.__owl__.children); const children = Object.values(this.__owl__.children);
for (let child of children) { for (let child of children) {
child._destroy(); child._destroy(this);
} }
if (this.__owl__.parent) { if (parent) {
let id = this.__owl__.id; let id = this.__owl__.id;
delete this.__owl__.parent.__owl__.children[id]; delete parent.__owl__.children[id];
this.__owl__.parent = null; this.__owl__.parent = null;
} }
this.clear(); this.clear();
@@ -408,24 +390,20 @@ export class Component<
*/ */
_mount(vnode: VNode, elm: HTMLElement): VNode { _mount(vnode: VNode, elm: HTMLElement): VNode {
this.__owl__.vnode = patch(elm, vnode); this.__owl__.vnode = patch(elm, vnode);
this.__mount(); if (
this.__owl__.parent &&
this.__owl__.parent.__owl__.isMounted &&
!this.__owl__.isMounted
) {
this._callMounted();
}
return this.__owl__.vnode; return this.__owl__.vnode;
} }
__mount() { __mount() {
if (this.__owl__.isMounted) { if (!this.__owl__.isMounted) {
return; this.__owl__.isMounted = true;
} this.mounted();
this._observeState();
if (this.__owl__.parent) {
if (this.__owl__.parent!.__owl__.isMounted) {
this.__owl__.isMounted = true;
this.mounted();
const children = this.__owl__.children;
for (let id in children) {
children[id].__mount();
}
}
} }
} }
+48
View File
@@ -216,6 +216,54 @@ describe("lifecycle hooks", () => {
expect(steps).toEqual(["child:mounted", "parent:mounted"]); expect(steps).toEqual(["child:mounted", "parent:mounted"]);
}); });
test("mounted hook is called on subsubwidgets, in proper order", async () => {
const steps: any[] = [];
class ParentWidget extends Widget {
inlineTemplate = `<div><t t-if="state.flag"><t t-widget="child"/></t></div>`;
widgets = { child: ChildWidget };
state = { flag: false };
mounted() {
steps.push("parent:mounted");
}
willUnmount() {
steps.push("parent:willUnmount");
}
}
class ChildWidget extends Widget {
inlineTemplate = `<div><t t-widget="childchild"/></div>`;
widgets = { childchild: ChildChildWidget };
mounted() {
steps.push("child:mounted");
}
willUnmount() {
steps.push("child:willUnmount");
}
}
class ChildChildWidget extends Widget {
mounted() {
steps.push("childchild:mounted");
}
willUnmount() {
steps.push("childchild:willUnmount");
}
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
expect(steps).toEqual(["parent:mounted"]);
widget.state.flag = true;
await nextTick();
widget.destroy();
expect(steps).toEqual([
"parent:mounted",
"childchild:mounted",
"child:mounted",
"parent:willUnmount",
"child:willUnmount",
"childchild:willUnmount"
]);
});
test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => { test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => {
const steps: string[] = []; const steps: string[] = [];