From 14db513f3be80d2e18206c2521fe7f8cc90be8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 22 Nov 2019 14:49:37 +0100 Subject: [PATCH] [FIX] component: allow fragments in method mount It is now possible to mount components in fragments closes #494 --- doc/reference/component.md | 16 +++++++++++++++- src/component/component.ts | 4 ++-- tests/component/component.test.ts | 11 +++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/doc/reference/component.md b/doc/reference/component.md index 85b52712..a4bf7562 100644 --- a/doc/reference/component.md +++ b/doc/reference/component.md @@ -239,7 +239,7 @@ We explain here all the public methods of the `Component` class. - **`mount(target)`** (async): this is the main way a component is added to the DOM: the root component is mounted to a target - HTMLElement. Obviously, this is asynchronous, since each children need to be + HTMLElement (or document fragment). Obviously, this is asynchronous, since each children need to be created as well. Most applications will need to call `mount` exactly once, on the root component. @@ -247,6 +247,20 @@ We explain here all the public methods of the `Component` class. automatically re-rendered to ensure that changes in its state (or something in the environment, or in the store, or ...) will be taken into account. + If a component is mounted inside an element or a fragment which is not in the + DOM, then it will be rendered fully, but not active: the `mounted` hooks will + not be called. This is sometimes useful if we want to load an application in + memory. In that case, we need to mount the root component again in an element + which is in the DOM: + + ```js + const app = new App(); + await app.mount(document.createDocumentFragment()); + // app is rendered in memory, but not active + await app.mount(document.body); + // app is now visible + ``` + - **`unmount()`**: in case a component needs 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 4ee69634..77a38bd8 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -281,12 +281,12 @@ export class Component { * * Note that a component can be mounted an unmounted several times */ - async mount(target: HTMLElement): Promise { + async mount(target: HTMLElement | DocumentFragment): Promise { const __owl__ = this.__owl__; if (__owl__.isMounted) { return Promise.resolve(); } - if (!(target instanceof HTMLElement)) { + if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) { let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`; message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`; throw new Error(message); diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index d75a4cd6..87aa49c1 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -91,6 +91,17 @@ describe("basic widget properties", () => { expect(fixture.innerHTML).toBe("
content
"); }); + test("can be mounted on a documentFragment", async () => { + class SomeWidget extends Component { + static template = xml`
content
`; + } + const widget = new SomeWidget(); + await widget.mount(document.createDocumentFragment()); + expect(fixture.innerHTML).toBe(""); + await widget.mount(fixture); + expect(fixture.innerHTML).toBe("
content
"); + }); + test("display a nice message if mounted on a non existing node", async () => { class SomeWidget extends Component { static template = xml`
content
`;