From e5b1ba24d84c536c17e9823d3886db91a0866e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 22 Aug 2019 13:32:53 +0200 Subject: [PATCH] [IMP] component: allow multiple mount/unmount closes #258 --- doc/component.md | 2 + src/component/component.ts | 17 +++++++-- tests/component/component.test.ts | 61 +++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/doc/component.md b/doc/component.md index 151e38c8..5d5affa3 100644 --- a/doc/component.md +++ b/doc/component.md @@ -149,6 +149,8 @@ We explain here all the public methods of the `Component` class. is asynchronous, since each children need to be created as well. Most applications will need to call `mount` exactly once, on the root component. + Note that a component can be mounted and unmounted multiple times if needed. + - **`unmount()`**: in case a component need to be detached/removed from the DOM, this method can be used. Most applications should not call `unmount`, this is more useful to the underlying component system. diff --git a/src/component/component.ts b/src/component/component.ts index 224f643f..3f7a07d1 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -265,14 +265,23 @@ export class Component { * * This should only be done if the component was created manually. Components * created declaratively in templates are managed by the Owl system. + * + * Note that a component can be mounted an unmounted several times */ async mount(target: HTMLElement): Promise { - const vnode = await this.__prepare(); - if (this.__owl__.isDestroyed) { - // component was destroyed before we get here... + if (this.__owl__.isMounted) { return; } - this.__patch(vnode); + if (this.__owl__.renderId === 1) { + // we use the fact that renderId === 1 as a way to determine that the + // component is mounted for the first time + const vnode = await this.__prepare(); + if (this.__owl__.isDestroyed) { + // component was destroyed before we get here... + return; + } + this.__patch(vnode); + } target.appendChild(this.el!); if (document.body.contains(target)) { diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index cb932cde..15e51d7e 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -3950,3 +3950,64 @@ describe("top level sub widgets", () => { expect(fixture.innerHTML).toBe("
CHILD 2
"); }); }); + +describe("unmounting and remounting", () => { + test("widget can be unmounted and remounted", async () => { + env.qweb.addTemplates(` + +
Hey
+
`); + + const steps: string[] = []; + class MyWidget extends Widget { + async willStart() { + steps.push("willstart"); + } + mounted() { + steps.push("mounted"); + } + willUnmount() { + steps.push("willunmount"); + } + } + + const w = new MyWidget(env); + await w.mount(fixture); + expect(fixture.innerHTML).toBe("
Hey
"); + expect(steps).toEqual(["willstart", "mounted"]); + + w.unmount(); + expect(fixture.innerHTML).toBe(""); + expect(steps).toEqual(["willstart", "mounted", "willunmount"]); + + await w.mount(fixture); + expect(fixture.innerHTML).toBe("
Hey
"); + expect(steps).toEqual(["willstart", "mounted", "willunmount", "mounted"]); + }); + + test("widget can be mounted twice without ill effect", async () => { + env.qweb.addTemplates(` + +
Hey
+
`); + + const steps: string[] = []; + class MyWidget extends Widget { + async willStart() { + steps.push("willstart"); + } + mounted() { + steps.push("mounted"); + } + willUnmount() { + steps.push("willunmount"); + } + } + + const w = new MyWidget(env); + await w.mount(fixture); + await w.mount(fixture); + expect(fixture.innerHTML).toBe("
Hey
"); + expect(steps).toEqual(["willstart", "mounted"]); + }); +});