[IMP] owl: add a new mount method

This commit is contained in:
Géry Debongnie
2020-10-27 14:41:09 +01:00
committed by aab-odoo
parent d615ffd81b
commit cb07c99d40
17 changed files with 191 additions and 126 deletions
+2 -3
View File
@@ -41,7 +41,7 @@ find some more additional information [here](doc/miscellaneous/comparison.md).
Here is a short example to illustrate interactive components: Here is a short example to illustrate interactive components:
```javascript ```javascript
const { Component, useState } = owl; const { Component, useState, mount } = owl;
const { xml } = owl.tags; const { xml } = owl.tags;
class Counter extends Component { class Counter extends Component {
@@ -63,8 +63,7 @@ class App extends Component {
static components = { Counter }; static components = { Counter };
} }
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
``` ```
Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate). Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
+1 -2
View File
@@ -85,8 +85,7 @@ afterEach(() => {
describe("SomeComponent", () => { describe("SomeComponent", () => {
test("component behaves as expected", async () => { test("component behaves as expected", async () => {
const props = {...}; // depends on the component const props = {...}; // depends on the component
const comp = new SomeComponent(null, props); const comp = await mount(SomeComponent, { target: fixture, props });
await comp.mount(fixture);
// do some assertions // do some assertions
expect(...).toBe(...); expect(...).toBe(...);
+8 -11
View File
@@ -54,7 +54,7 @@ Now, `index.html` should contain the following:
And `app.js` should look like this: And `app.js` should look like this:
```js ```js
const { Component } = owl; const { Component, mount } = owl;
const { xml } = owl.tags; const { xml } = owl.tags;
const { whenReady } = owl.utils; const { whenReady } = owl.utils;
@@ -65,8 +65,7 @@ class App extends Component {
// Setup code // Setup code
function setup() { function setup() {
const app = new App(); mount(App, target: { document.body })
app.mount(document.body);
} }
whenReady(setup); whenReady(setup);
@@ -124,7 +123,7 @@ Here is the content of `app.js` and `main.js`:
```js ```js
// app.js ---------------------------------------------------------------------- // app.js ----------------------------------------------------------------------
const { Component } = owl; const { Component, mount } = owl;
const { xml } = owl.tags; const { xml } = owl.tags;
export class App extends Component { export class App extends Component {
@@ -135,8 +134,7 @@ export class App extends Component {
import { App } from "./app.js"; import { App } from "./app.js";
function setup() { function setup() {
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
} }
owl.utils.whenReady(setup); owl.utils.whenReady(setup);
@@ -240,12 +238,11 @@ export class App extends Component {
} }
// src/main.js ----------------------------------------------------------------- // src/main.js -----------------------------------------------------------------
import { utils } from "@odoo/owl"; import { utils, mount } from "@odoo/owl";
import { App } from "./components/App"; import { App } from "./components/App";
function setup() { function setup() {
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
} }
utils.whenReady(setup); utils.whenReady(setup);
@@ -253,6 +250,7 @@ utils.whenReady(setup);
// tests/components/App.test.js ------------------------------------------------ // tests/components/App.test.js ------------------------------------------------
import { App } from "../../src/components/App"; import { App } from "../../src/components/App";
import { makeTestFixture, nextTick, click } from "../helpers"; import { makeTestFixture, nextTick, click } from "../helpers";
import { mount } from "@odoo/owl";
let fixture; let fixture;
@@ -266,8 +264,7 @@ afterEach(() => {
describe("App", () => { describe("App", () => {
test("Works as expected...", async () => { test("Works as expected...", async () => {
const app = new App(); await mount(App, { target: fixture });
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div>Hello Owl</div>"); expect(fixture.innerHTML).toBe("<div>Hello Owl</div>");
click(fixture, "div"); click(fixture, "div");
+8 -13
View File
@@ -83,7 +83,7 @@ a single root component. Let us start by defining an `App` component. Replace th
content of the function in `app.js` by the following code: content of the function in `app.js` by the following code:
```js ```js
const { Component } = owl; const { Component, mount } = owl;
const { xml } = owl.tags; const { xml } = owl.tags;
const { whenReady } = owl.utils; const { whenReady } = owl.utils;
@@ -94,8 +94,7 @@ class App extends Component {
// Setup code // Setup code
function setup() { function setup() {
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
} }
whenReady(setup); whenReady(setup);
@@ -279,8 +278,7 @@ class App extends Component {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
function setup() { function setup() {
owl.config.mode = "dev"; owl.config.mode = "dev";
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
} }
whenReady(setup); whenReady(setup);
@@ -639,8 +637,7 @@ function setup() {
owl.config.mode = "dev"; owl.config.mode = "dev";
const store = new Store({ actions, state: initialState }); const store = new Store({ actions, state: initialState });
App.env.store = store; App.env.store = store;
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
} }
whenReady(setup); whenReady(setup);
@@ -666,9 +663,8 @@ function makeStore() {
function setup() { function setup() {
owl.config.mode = "dev"; owl.config.mode = "dev";
App.env.store = makeStore(); const env = {store = makeStore()};
const app = new App(); mount(App, { target: document.body, env });
app.mount(document.body);
} }
``` ```
@@ -943,9 +939,8 @@ For reference, here is the final code:
function setup() { function setup() {
owl.config.mode = "dev"; owl.config.mode = "dev";
App.env.store = makeStore(); const env = {store = makeStore()};
const app = new App(); mount(App, { target: document.body, env });
app.mount(document.body);
} }
whenReady(setup); whenReady(setup);
+1
View File
@@ -27,6 +27,7 @@ provided by Owl.
- [Event Handling](reference/event_handling.md) - [Event Handling](reference/event_handling.md)
- [Error Handling](reference/error_handling.md) - [Error Handling](reference/error_handling.md)
- [Hooks](reference/hooks.md) - [Hooks](reference/hooks.md)
- [Mounting a component](reference/mounting.md)
- [Miscellaneous Components](reference/misc.md) - [Miscellaneous Components](reference/misc.md)
- [Observer](reference/observer.md) - [Observer](reference/observer.md)
- [Props](reference/props.md) - [Props](reference/props.md)
+4
View File
@@ -270,6 +270,10 @@ We explain here all the public methods of the `Component` class.
// app is now visible // app is now visible
``` ```
Note that the normal way of mounting an application is by using the `mount`
method on a component class, not by creating the instance by hand. See the
documentation on [mounting applications](mounting.md).
* **`unmount()`**: in case a component needs to be detached/removed from the DOM, this * **`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 method can be used. Most applications should not call `unmount`, this is more
useful to the underlying component system. useful to the underlying component system.
+5 -4
View File
@@ -10,10 +10,11 @@ exported as `owl.core.EventBus`.
Component misc Component misc
Context AsyncRoot Context AsyncRoot
QWeb Portal QWeb Portal
Store router mount router
useState Link Store Link
config RouteComponent useState RouteComponent
mode Router config Router
mode
core tags core tags
EventBus css EventBus css
Observer xml Observer xml
+4 -6
View File
@@ -49,15 +49,14 @@ The correct way to customize an environment is to simply set it up on the root
component class, before the first component is created: component class, before the first component is created:
```js ```js
App.env = { const env = {
_t: myTranslateFunction, _t: myTranslateFunction,
user: {...}, user: {...},
services: { services: {
... ...
}, },
}; };
const app = new App(); mount(App, { target: document.body, env });
app.mount(document.body);
``` ```
It is also possible to simply share an environment between all root components, It is also possible to simply share an environment between all root components,
@@ -121,9 +120,8 @@ async function myEnv() {
} }
async function start() { async function start() {
App.env = await myEnv(); const env = await myEnv();
const app = new App(); mount(App, { target: document.body, env });
await app.mount(document.body);
} }
``` ```
+2 -3
View File
@@ -43,7 +43,7 @@ workflow to help the user put in some data, which it could use later on.
JavaScript: JavaScript:
```js ```js
const { Component } = owl; const { Component, mount } = owl;
const { Portal } = owl.misc; const { Portal } = owl.misc;
class TeleportedComponent extends Component {} class TeleportedComponent extends Component {}
@@ -51,8 +51,7 @@ class App extends Component {
static components = { Portal, TeleportedComponent }; static components = { Portal, TeleportedComponent };
} }
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
``` ```
XML: XML:
+60
View File
@@ -0,0 +1,60 @@
# 🦉 Mounting an application 🦉
## Content
- [Overview](#overview)
- [API](#api)
## Overview
Mounting an Owl application is done by using the `mount` method (available in
`owl.mount` if you are using the iife build, or it can be directly imported
from `owl` if you are using a module system):
```js
const mount = { owl }; // if owl is available as an object
const env = { ... };
const app = await mount(MyComponent, { target: document.body, env });
```
Another example:
```js
const config = {
env: ...,
props: ...,
target: document.body,
position: "self",
};
const app = await mount(App, config);
```
A common way to initialize an application is to first setup an environment,
then to call the `mount` method.
## API
Mount takes two parameters:
- `C`, which should be a component class (NOT instance),
- `params`, which is an object with the following keys:
- `target (HTMLElement | DocumentFragment)`: the target of the mount operation
- `env (optional, Env)` an environment
- `position (optional, "first-child" | "last-child" | "self")` the position
where it should be mounted (see below for more informations)
- `props (optional, any)`: some initial values that are given as props. Useful
when the root component is configurable, or when testing sub components
Here are the various positions supported by Owl:
- `first-child`: with this option, the component will be prepended inside the target,
- `last-child` (default value): with this option, the component will be
appended in the target element,
- `self`: the target will be used as the root element for the component. This
means that the target has to be an HTMLElement (and not a document fragment).
In this situation, it is possible that the component cannot be unmounted. For
example, if its target is `document.body`.
The `mount` method returns a promise that resolves to the instance of the created
component.
+4 -4
View File
@@ -21,8 +21,8 @@ argument, it executes it as soon as the DOM ready (or directly).
```js ```js
Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function ([templates]) { Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function ([templates]) {
const qweb = new owl.QWeb({ templates }); const qweb = new owl.QWeb({ templates });
const app = new App({ qweb }); const env = { qweb };
app.mount(document.body); await mount(App, { env, target: document.body });
}); });
``` ```
@@ -31,8 +31,8 @@ or alternatively:
```js ```js
owl.utils.whenReady(function () { owl.utils.whenReady(function () {
const qweb = new owl.QWeb(); const qweb = new owl.QWeb();
const app = new App({ qweb }); const env = { qweb };
app.mount(document.body); await mount(App, { env, target: document.body });
}); });
``` ```
+31
View File
@@ -729,3 +729,34 @@ export class Component<Props extends {} = any, T extends Env = Env> {
} }
} }
} }
interface MountParameters {
env?: Env;
target: HTMLElement | DocumentFragment;
props?: any;
position?: MountOptions["position"];
}
interface Type<T> extends Function {
new (...args: any[]): T;
}
export async function mount<T extends Type<Component>>(
C: T,
params: MountParameters
): Promise<InstanceType<T>> {
const { env, props, target } = params;
let origEnv = C.hasOwnProperty("env") ? (C as any).env : null;
if (env) {
((C as any) as typeof Component).env = env;
}
const component: Component = new C(null, props);
if (origEnv) {
(C as any).env = origEnv;
} else {
delete (C as any).env;
}
const position = params.position || "last-child";
await component.mount(target, { position });
return component as any;
}
+2 -1
View File
@@ -19,7 +19,7 @@ import { Link } from "./router/link";
import { RouteComponent } from "./router/route_component"; import { RouteComponent } from "./router/route_component";
import { Router } from "./router/router"; import { Router } from "./router/router";
export { Component } from "./component/component"; export { Component, mount } from "./component/component";
export { QWeb }; export { QWeb };
export { config }; export { config };
@@ -37,4 +37,5 @@ export const hooks = Object.assign({}, _hooks, {
useGetters: _store.useGetters, useGetters: _store.useGetters,
useStore: _store.useStore, useStore: _store.useStore,
}); });
export const __info__ = {}; export const __info__ = {};
+19 -20
View File
@@ -1,4 +1,4 @@
import { Component, Env } from "../../src/component/component"; import { Component, Env, mount } from "../../src/component/component";
import { EventBus } from "../../src/core/event_bus"; import { EventBus } from "../../src/core/event_bus";
import { useRef, useState } from "../../src/hooks"; import { useRef, useState } from "../../src/hooks";
import { QWeb } from "../../src/qweb/qweb"; import { QWeb } from "../../src/qweb/qweb";
@@ -69,18 +69,23 @@ describe("basic widget properties", () => {
class SomeWidget extends Component { class SomeWidget extends Component {
static template = xml`<div>content</div>`; static template = xml`<div>content</div>`;
} }
const widget = new SomeWidget(); await mount(SomeWidget, { target: fixture });
widget.mount(fixture);
await nextTick();
expect(fixture.innerHTML).toBe("<div>content</div>"); expect(fixture.innerHTML).toBe("<div>content</div>");
}); });
test("can be mounted with props", async () => {
class SomeWidget extends Component {
static template = xml`<div><t t-esc="props.content"/></div>`;
}
await mount(SomeWidget, { target: fixture, props: { content: "foo" } });
expect(fixture.innerHTML).toBe("<div>foo</div>");
});
test("can be mounted on a documentFragment", async () => { test("can be mounted on a documentFragment", async () => {
class SomeWidget extends Component { class SomeWidget extends Component {
static template = xml`<div>content</div>`; static template = xml`<div>content</div>`;
} }
const widget = new SomeWidget(); const widget = await mount(SomeWidget, { target: document.createDocumentFragment() });
await widget.mount(document.createDocumentFragment());
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
await widget.mount(fixture); await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>content</div>"); expect(fixture.innerHTML).toBe("<div>content</div>");
@@ -90,10 +95,9 @@ describe("basic widget properties", () => {
class SomeWidget extends Component { class SomeWidget extends Component {
static template = xml`<div>content</div>`; static template = xml`<div>content</div>`;
} }
const widget = new SomeWidget();
let error; let error;
try { try {
await widget.mount(null as any); await mount(SomeWidget, { target: null as any });
} catch (e) { } catch (e) {
error = e; error = e;
} }
@@ -107,10 +111,9 @@ describe("basic widget properties", () => {
class SomeWidget extends Component { class SomeWidget extends Component {
static template = xml`<t/>`; static template = xml`<t/>`;
} }
const widget = new SomeWidget();
let error; let error;
try { try {
await widget.mount(fixture); await mount(SomeWidget, { target: fixture });
} catch (e) { } catch (e) {
error = e; error = e;
} }
@@ -137,8 +140,7 @@ describe("basic widget properties", () => {
}); });
} }
const counter = new Counter(); const counter = await mount(Counter, { target: fixture });
counter.mount(fixture);
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div>0<button>Inc</button></div>"); expect(fixture.innerHTML).toBe("<div>0<button>Inc</button></div>");
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0]; const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
@@ -156,8 +158,7 @@ describe("basic widget properties", () => {
static components = { Child }; static components = { Child };
} }
const parent = new Parent(); await mount(Parent, { target: fixture });
await parent.mount(fixture);
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot(); expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
expect(fixture.innerHTML).toBe("<div><span></span></div>"); expect(fixture.innerHTML).toBe("<div><span></span></div>");
}); });
@@ -171,9 +172,8 @@ describe("basic widget properties", () => {
}); });
} }
const counter = new Counter();
const target = document.createElement("div"); const target = document.createElement("div");
await counter.mount(target); const counter = await mount(Counter, { target: target });
expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>"); expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0]; const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
button.click(); button.click();
@@ -188,8 +188,7 @@ describe("basic widget properties", () => {
<div style="font-weight:bold;" class="some-class">world</div> <div style="font-weight:bold;" class="some-class">world</div>
`; `;
} }
const widget = new StyledWidget(); await mount(StyledWidget, { target: fixture });
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(`<div style="font-weight:bold;" class="some-class">world</div>`); expect(fixture.innerHTML).toBe(`<div style="font-weight:bold;" class="some-class">world</div>`);
}); });
@@ -212,8 +211,8 @@ describe("basic widget properties", () => {
steps.push("patched"); steps.push("patched");
} }
} }
const widget = new TestW(); await mount(TestW, { target: fixture });
await widget.mount(fixture);
expect(steps).toEqual(["__render", "mounted"]); expect(steps).toEqual(["__render", "mounted"]);
}); });
+9 -17
View File
@@ -1,4 +1,4 @@
import { Component, Env } from "../../src/component/component"; import { Component, Env, mount } from "../../src/component/component";
import { useState } from "../../src/hooks"; import { useState } from "../../src/hooks";
import { xml } from "../../src/tags"; import { xml } from "../../src/tags";
import { makeDeferred, makeTestEnv, makeTestFixture, nextTick, nextMicroTick } from "../helpers"; import { makeDeferred, makeTestEnv, makeTestFixture, nextTick, nextMicroTick } from "../helpers";
@@ -37,8 +37,7 @@ describe("mount targets", () => {
div.innerHTML = `<p>pre-existing</p>`; div.innerHTML = `<p>pre-existing</p>`;
fixture.appendChild(div); fixture.appendChild(div);
const app = new App(); const app = await mount(App, { target: div, position: "self" });
await app.mount(div, { position: "self" });
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
`<div class="arbitrary custom"><p>pre-existing</p>app<p>another tag</p></div>` `<div class="arbitrary custom"><p>pre-existing</p>app<p>another tag</p></div>`
@@ -66,10 +65,9 @@ describe("mount targets", () => {
const div = document.createElement("div"); const div = document.createElement("div");
fixture.appendChild(div); fixture.appendChild(div);
const app = new App();
let error; let error;
try { try {
await app.mount(div, { position: "self" }); await mount(App, { target: div, position: "self" });
} catch (e) { } catch (e) {
error = e; error = e;
} }
@@ -84,8 +82,7 @@ describe("mount targets", () => {
const span = document.createElement("span"); const span = document.createElement("span");
fixture.appendChild(span); fixture.appendChild(span);
const app = new App(); await mount(App, { target: fixture, position: "first-child" });
await app.mount(fixture, { position: "first-child" });
expect(fixture.innerHTML).toBe("<div>app</div><span></span>"); expect(fixture.innerHTML).toBe("<div>app</div><span></span>");
}); });
@@ -96,8 +93,7 @@ describe("mount targets", () => {
const span = document.createElement("span"); const span = document.createElement("span");
fixture.appendChild(span); fixture.appendChild(span);
const app = new App(); await mount(App, { target: fixture, position: "last-child" });
await app.mount(fixture, { position: "last-child" });
expect(fixture.innerHTML).toBe("<span></span><div>app</div>"); expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
}); });
@@ -108,8 +104,7 @@ describe("mount targets", () => {
const span = document.createElement("span"); const span = document.createElement("span");
fixture.appendChild(span); fixture.appendChild(span);
const app = new App(); await mount(App, { target: fixture });
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<span></span><div>app</div>"); expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
}); });
}); });
@@ -133,8 +128,7 @@ describe("unmounting and remounting", () => {
} }
} }
const w = new MyWidget(); const w = await mount(MyWidget, { target: fixture });
await w.mount(fixture);
expect(fixture.innerHTML).toBe("<div>Hey</div>"); expect(fixture.innerHTML).toBe("<div>Hey</div>");
expect(steps).toEqual(["willstart", "mounted"]); expect(steps).toEqual(["willstart", "mounted"]);
@@ -162,8 +156,7 @@ describe("unmounting and remounting", () => {
} }
} }
const w = new MyWidget(); const w = await mount(MyWidget, { target: fixture });
await w.mount(fixture);
await w.mount(fixture); await w.mount(fixture);
expect(fixture.innerHTML).toBe("<div>Hey</div>"); expect(fixture.innerHTML).toBe("<div>Hey</div>");
expect(steps).toEqual(["willstart", "mounted"]); expect(steps).toEqual(["willstart", "mounted"]);
@@ -203,8 +196,7 @@ describe("unmounting and remounting", () => {
state = useState({ val: 1, flag: true }); state = useState({ val: 1, flag: true });
} }
const widget = new Parent(); const widget = await mount(Parent, { target: fixture });
await widget.mount(fixture);
expect(steps).toEqual(["render"]); expect(steps).toEqual(["render"]);
expect(fixture.innerHTML).toBe("<div><span>12</span></div>"); expect(fixture.innerHTML).toBe("<div><span>12</span></div>");
widget.state.flag = false; widget.state.flag = false;
+4 -4
View File
@@ -1,5 +1,6 @@
import { SAMPLES } from "./samples.js"; import { SAMPLES } from "./samples.js";
const { useState, useRef, onMounted, onWillUnmount } = owl.hooks; const { mount, hooks } = owl;
const { useState, useRef, onMounted, onWillUnmount } = hooks;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Constants, helpers, utils // Constants, helpers, utils
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -419,9 +420,8 @@ async function start() {
owl.utils.whenReady() owl.utils.whenReady()
]); ]);
const qweb = new owl.QWeb({ templates }); const qweb = new owl.QWeb({ templates });
owl.Component.env = { qweb }; const env = { qweb };
const app = new App(); await mount(App, {target: document.body, env});
app.mount(document.body);
} }
start(); start();
+27 -38
View File
@@ -1,5 +1,5 @@
const COMPONENTS = `// In this example, we show how components can be defined and created. const COMPONENTS = `// In this example, we show how components can be defined and created.
const { Component, useState } = owl; const { Component, useState, mount } = owl;
class Greeter extends Component { class Greeter extends Component {
constructor() { constructor() {
@@ -22,8 +22,7 @@ class App extends Component {
App.components = { Greeter }; App.components = { Greeter };
// Application setup // Application setup
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
const COMPONENTS_XML = `<templates> const COMPONENTS_XML = `<templates>
@@ -50,7 +49,7 @@ const COMPONENTS_CSS = `.greeter {
const ANIMATION = `// The goal of this component is to see how the t-transition directive can be const ANIMATION = `// The goal of this component is to see how the t-transition directive can be
// used to generate simple transition effects. // used to generate simple transition effects.
const { Component, useState } = owl; const { Component, useState, mount } = owl;
class Counter extends Component { class Counter extends Component {
constructor() { constructor() {
@@ -80,8 +79,7 @@ class App extends Component {
} }
App.components = { Counter }; App.components = { Counter };
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
const ANIMATION_XML = `<templates> const ANIMATION_XML = `<templates>
@@ -193,7 +191,7 @@ const LIFECYCLE_DEMO = `// This example shows all the possible lifecycle hooks
// methods in the console. Try modifying its state by clicking on it, or by // methods in the console. Try modifying its state by clicking on it, or by
// clicking on the two main buttons, and look into the console to see what // clicking on the two main buttons, and look into the console to see what
// happens. // happens.
const { Component, useState } = owl; const { Component, useState, mount } = owl;
class DemoComponent extends Component { class DemoComponent extends Component {
constructor() { constructor() {
@@ -240,8 +238,7 @@ class App extends Component {
} }
App.components = { DemoComponent }; App.components = { DemoComponent };
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
const LIFECYCLE_DEMO_XML = `<templates> const LIFECYCLE_DEMO_XML = `<templates>
@@ -273,7 +270,8 @@ const LIFECYCLE_CSS = `button {
}`; }`;
const HOOKS_DEMO = `// In this example, we show how hooks can be used or defined. const HOOKS_DEMO = `// In this example, we show how hooks can be used or defined.
const {useState, onMounted, onWillUnmount} = owl.hooks; const { hooks, mount } = owl;
const {useState, onMounted, onWillUnmount} = hooks;
// We define here a custom behaviour: this hook tracks the state of the mouse // We define here a custom behaviour: this hook tracks the state of the mouse
// position // position
@@ -312,8 +310,7 @@ class App extends owl.Component {
} }
// Application setup // Application setup
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
const HOOKS_DEMO_XML = `<templates> const HOOKS_DEMO_XML = `<templates>
@@ -332,7 +329,7 @@ const HOOKS_CSS = `button {
const CONTEXT_JS = `// In this example, we show how components can use the Context and 'useContext' const CONTEXT_JS = `// In this example, we show how components can use the Context and 'useContext'
// hook to share information between them. // hook to share information between them.
const { Component, Context } = owl; const { Component, Context, mount } = owl;
const { useContext } = owl.hooks; const { useContext } = owl.hooks;
class ToolbarButton extends Component { class ToolbarButton extends Component {
@@ -367,8 +364,7 @@ const themeContext = new Context({
}); });
// Add the themeContext the environment to make it available to all components // Add the themeContext the environment to make it available to all components
App.env.themeContext = themeContext; App.env.themeContext = themeContext;
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
const CONTEXT_XML = `<templates> const CONTEXT_XML = `<templates>
@@ -395,7 +391,7 @@ const TODO_APP_STORE = `// This example is an implementation of the TodoList app
// //
// In this implementation, we use the owl Store class to manage the state. It // In this implementation, we use the owl Store class to manage the state. It
// is very similar to the VueX store. // is very similar to the VueX store.
const { Component, useState } = owl; const { Component, useState, mount } = owl;
const { useRef, useStore, useDispatch, onPatched, onMounted } = owl.hooks; const { useRef, useStore, useDispatch, onPatched, onMounted } = owl.hooks;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -568,8 +564,7 @@ function makeStore() {
} }
TodoApp.env.store = makeStore(); TodoApp.env.store = makeStore();
const app = new TodoApp(); mount(TodoApp, { target: document.body });
app.mount(document.body);
`; `;
const TODO_APP_STORE_XML = `<templates> const TODO_APP_STORE_XML = `<templates>
@@ -1060,8 +1055,7 @@ function setupResponsivePlugin(env) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
setupResponsivePlugin(App.env); setupResponsivePlugin(App.env);
const app = new App(); owl.mount(App, { target: document.body });
app.mount(document.body);
`; `;
const RESPONSIVE_XML = `<templates> const RESPONSIVE_XML = `<templates>
@@ -1173,7 +1167,7 @@ const SLOTS = `// We show here how slots can be used to create generic component
// //
// Note that the t-on-click event, defined in the App template, is executed in // Note that the t-on-click event, defined in the App template, is executed in
// the context of the App component, even though it is inside the Card component // the context of the App component, even though it is inside the Card component
const { Component, useState } = owl; const { Component, useState, mount } = owl;
class Card extends Component { class Card extends Component {
constructor() { constructor() {
@@ -1211,8 +1205,8 @@ class App extends Component {
App.components = {Card, Counter}; App.components = {Card, Counter};
// Application setup // Application setup
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);`; `;
const SLOTS_XML = `<templates> const SLOTS_XML = `<templates>
<div t-name="Card" class="card" t-att-class="state.showContent ? 'full' : 'small'"> <div t-name="Card" class="card" t-att-class="state.showContent ? 'full' : 'small'">
@@ -1298,7 +1292,7 @@ const ASYNC_COMPONENTS = `// This example will not work if your browser does not
// However, we don't want renderings of the other sub component to be delayed // However, we don't want renderings of the other sub component to be delayed
// because of the slow component. We use the AsyncRoot component for this // because of the slow component. We use the AsyncRoot component for this
// purpose. Try removing it to see the difference. // purpose. Try removing it to see the difference.
const { Component, useState } = owl; const { Component, useState, mount } = owl;
const { AsyncRoot } = owl.misc; const { AsyncRoot } = owl.misc;
class SlowComponent extends Component { class SlowComponent extends Component {
@@ -1329,8 +1323,7 @@ class App extends Component {
} }
App.components = {SlowComponent, NotificationList, AsyncRoot}; App.components = {SlowComponent, NotificationList, AsyncRoot};
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
const ASYNC_COMPONENTS_XML = `<templates> const ASYNC_COMPONENTS_XML = `<templates>
@@ -1385,7 +1378,7 @@ const FORM = `// This example illustrate how the t-model directive can be used t
// data between html inputs (and select/textareas) and the state of a component. // data between html inputs (and select/textareas) and the state of a component.
// Note that there are two controls with t-model="color": they are totally // Note that there are two controls with t-model="color": they are totally
// synchronized. // synchronized.
const { Component, useState } = owl; const { Component, useState, mount } = owl;
class Form extends Component { class Form extends Component {
constructor() { constructor() {
@@ -1401,8 +1394,7 @@ class Form extends Component {
} }
// Application setup // Application setup
const form = new Form(); mount(Form, { target: document.body });
form.mount(document.body);
`; `;
const FORM_XML = `<templates> const FORM_XML = `<templates>
@@ -1448,7 +1440,7 @@ const PORTAL_COMPONENTS = `
// This shows the expected use case of Portal // This shows the expected use case of Portal
// which is to implement something similar // which is to implement something similar
// to bootstrap modal // to bootstrap modal
const { Component, useState } = owl; const { Component, useState, mount } = owl;
const { Portal } = owl.misc; const { Portal } = owl.misc;
class Modal extends Component {} class Modal extends Component {}
@@ -1470,8 +1462,7 @@ class App extends Component {
App.components = { Dialog , Interstellar }; App.components = { Dialog , Interstellar };
// Application setup // Application setup
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
const PORTAL_XML = ` const PORTAL_XML = `
@@ -1559,7 +1550,7 @@ const WMS = `// This example is slightly more complex than usual. We demonstrate
// - minimal width/height // - minimal width/height
// - better heuristic for initial window position // - better heuristic for initial window position
// - ... // - ...
const { Component, useState } = owl; const { Component, useState, mount } = owl;
const { useRef } = owl.hooks; const { useRef } = owl.hooks;
class HelloWorld extends Component {} class HelloWorld extends Component {}
@@ -1699,8 +1690,7 @@ const windows = [
]; ];
App.env.windows = windows; App.env.windows = windows;
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
const WMS_XML = `<templates> const WMS_XML = `<templates>
@@ -1818,7 +1808,7 @@ const SFC = `// This example illustrates how Owl enables single file components,
// Note that this example has no external xml or css file, everything is // Note that this example has no external xml or css file, everything is
// contained in a single js file. // contained in a single js file.
const { Component, useState, tags } = owl; const { Component, useState, tags, mount } = owl;
const { xml, css } = tags; const { xml, css } = tags;
// Counter component // Counter component
@@ -1850,8 +1840,7 @@ App.template = APP_TEMPLATE;
App.components = { Counter }; App.components = { Counter };
// Application setup // Application setup
const app = new App(); mount(App, { target: document.body });
app.mount(document.body);
`; `;
export const SAMPLES = [ export const SAMPLES = [