Compare commits

..

2 Commits

Author SHA1 Message Date
Géry Debongnie d83cfc12ee [IMP] qweb: the t-slot directive is now dynamic
It is useful for some kind of components to be able to use a completely
dynamic slot expression.
2020-10-30 15:31:03 +01:00
Géry Debongnie cb07c99d40 [IMP] owl: add a new mount method 2020-10-30 15:31:03 +01:00
21 changed files with 264 additions and 130 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:
```javascript
const { Component, useState } = owl;
const { Component, useState, mount } = owl;
const { xml } = owl.tags;
class Counter extends Component {
@@ -63,8 +63,7 @@ class App extends Component {
static components = { Counter };
}
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
```
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", () => {
test("component behaves as expected", async () => {
const props = {...}; // depends on the component
const comp = new SomeComponent(null, props);
await comp.mount(fixture);
const comp = await mount(SomeComponent, { target: fixture, props });
// do some assertions
expect(...).toBe(...);
+8 -11
View File
@@ -54,7 +54,7 @@ Now, `index.html` should contain the following:
And `app.js` should look like this:
```js
const { Component } = owl;
const { Component, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
@@ -65,8 +65,7 @@ class App extends Component {
// Setup code
function setup() {
const app = new App();
app.mount(document.body);
mount(App, target: { document.body })
}
whenReady(setup);
@@ -124,7 +123,7 @@ Here is the content of `app.js` and `main.js`:
```js
// app.js ----------------------------------------------------------------------
const { Component } = owl;
const { Component, mount } = owl;
const { xml } = owl.tags;
export class App extends Component {
@@ -135,8 +134,7 @@ export class App extends Component {
import { App } from "./app.js";
function setup() {
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
}
owl.utils.whenReady(setup);
@@ -240,12 +238,11 @@ export class App extends Component {
}
// src/main.js -----------------------------------------------------------------
import { utils } from "@odoo/owl";
import { utils, mount } from "@odoo/owl";
import { App } from "./components/App";
function setup() {
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
}
utils.whenReady(setup);
@@ -253,6 +250,7 @@ utils.whenReady(setup);
// tests/components/App.test.js ------------------------------------------------
import { App } from "../../src/components/App";
import { makeTestFixture, nextTick, click } from "../helpers";
import { mount } from "@odoo/owl";
let fixture;
@@ -266,8 +264,7 @@ afterEach(() => {
describe("App", () => {
test("Works as expected...", async () => {
const app = new App();
await app.mount(fixture);
await mount(App, { target: fixture });
expect(fixture.innerHTML).toBe("<div>Hello Owl</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:
```js
const { Component } = owl;
const { Component, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
@@ -94,8 +94,7 @@ class App extends Component {
// Setup code
function setup() {
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
}
whenReady(setup);
@@ -279,8 +278,7 @@ class App extends Component {
// -------------------------------------------------------------------------
function setup() {
owl.config.mode = "dev";
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
}
whenReady(setup);
@@ -639,8 +637,7 @@ function setup() {
owl.config.mode = "dev";
const store = new Store({ actions, state: initialState });
App.env.store = store;
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
}
whenReady(setup);
@@ -666,9 +663,8 @@ function makeStore() {
function setup() {
owl.config.mode = "dev";
App.env.store = makeStore();
const app = new App();
app.mount(document.body);
const env = {store = makeStore()};
mount(App, { target: document.body, env });
}
```
@@ -943,9 +939,8 @@ For reference, here is the final code:
function setup() {
owl.config.mode = "dev";
App.env.store = makeStore();
const app = new App();
app.mount(document.body);
const env = {store = makeStore()};
mount(App, { target: document.body, env });
}
whenReady(setup);
+1
View File
@@ -27,6 +27,7 @@ provided by Owl.
- [Event Handling](reference/event_handling.md)
- [Error Handling](reference/error_handling.md)
- [Hooks](reference/hooks.md)
- [Mounting a component](reference/mounting.md)
- [Miscellaneous Components](reference/misc.md)
- [Observer](reference/observer.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
```
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
method can be used. Most applications should not call `unmount`, this is more
useful to the underlying component system.
+5 -4
View File
@@ -10,10 +10,11 @@ exported as `owl.core.EventBus`.
Component misc
Context AsyncRoot
QWeb Portal
Store router
useState Link
config RouteComponent
mode Router
mount router
Store Link
useState RouteComponent
config Router
mode
core tags
EventBus css
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:
```js
App.env = {
const env = {
_t: myTranslateFunction,
user: {...},
services: {
...
},
};
const app = new App();
app.mount(document.body);
mount(App, { target: document.body, env });
```
It is also possible to simply share an environment between all root components,
@@ -121,9 +120,8 @@ async function myEnv() {
}
async function start() {
App.env = await myEnv();
const app = new App();
await app.mount(document.body);
const env = await myEnv();
mount(App, { target: document.body, env });
}
```
+2 -3
View File
@@ -43,7 +43,7 @@ workflow to help the user put in some data, which it could use later on.
JavaScript:
```js
const { Component } = owl;
const { Component, mount } = owl;
const { Portal } = owl.misc;
class TeleportedComponent extends Component {}
@@ -51,8 +51,7 @@ class App extends Component {
static components = { Portal, TeleportedComponent };
}
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
```
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.
+17 -2
View File
@@ -25,6 +25,8 @@ some sub template, but still be the owner. For example, a generic dialog compone
will need to render some content, some footer, but with the parent as the
rendering context.
Slots are inserted with the `t-slot` directive:
```xml
<div t-name="Dialog" class="modal">
<div class="modal-title"><t t-esc="props.title"/></div>
@@ -62,7 +64,9 @@ This is deprecated and should no longer be used in new code.
## Reference
Default slot: the first element inside the component which is not a named slot will
### Default Slot
The first element inside the component which is not a named slot will
be considered the `default` slot. For example:
```xml
@@ -77,7 +81,9 @@ be considered the `default` slot. For example:
</div>
```
Default content: slots can define a default content, in case the parent did not define them:
### Default content
Slots can define a default content, in case the parent did not define them:
```xml
<div t-name="Parent">
@@ -94,3 +100,12 @@ Rendering context: the content of the slots is actually rendered with the
rendering context corresponding to where it was defined, not where it is
positioned. This allows the user to define event handlers that will be bound
to the correct component (usually, the grandparent of the slot content).
### Dynamic Slots
The `t-slot` directive is actually able to use any expressions, using string
interplolation:
```xml
<t t-slot="{{current}}" />
```
+4 -4
View File
@@ -21,8 +21,8 @@ argument, it executes it as soon as the DOM ready (or directly).
```js
Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function ([templates]) {
const qweb = new owl.QWeb({ templates });
const app = new App({ qweb });
app.mount(document.body);
const env = { qweb };
await mount(App, { env, target: document.body });
});
```
@@ -31,8 +31,8 @@ or alternatively:
```js
owl.utils.whenReady(function () {
const qweb = new owl.QWeb();
const app = new App({ qweb });
app.mount(document.body);
const env = { qweb };
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 { Router } from "./router/router";
export { Component } from "./component/component";
export { Component, mount } from "./component/component";
export { QWeb };
export { config };
@@ -37,4 +37,5 @@ export const hooks = Object.assign({}, _hooks, {
useGetters: _store.useGetters,
useStore: _store.useStore,
});
export const __info__ = {};
+3 -1
View File
@@ -1,4 +1,5 @@
import { VNode } from "../vdom/index";
import { INTERP_REGEXP } from "./compilation_context";
import { QWeb } from "./qweb";
/**
@@ -228,8 +229,9 @@ QWeb.addDirective({
priority: 80,
atNodeEncounter({ ctx, value, node, qweb }): boolean {
const slotKey = ctx.generateID();
const valueExpr = value.match(INTERP_REGEXP) ? ctx.interpolate(value) : `'${value}'`;
ctx.addLine(
`const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + '${value}'];`
`const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + ${valueExpr}];`
);
ctx.addIf(`slot${slotKey}`);
let parentNode = `c${ctx.parentNode}`;
@@ -220,6 +220,25 @@ exports[`t-slot directive default slot work with text nodes 1`] = `
}"
`;
exports[`t-slot directive dynamic t-slot call 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c10 = [], p10 = {key:10,on:{}};
let vn10 = h('button', p10, c10);
extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['toggle'](e);};
p10.on['click'] = extra.handlers['click__11__'];
const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)];
if (slot12) {
slot12.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context}));
}
return vn10;
}"
`;
exports[`t-slot directive multiple roots are allowed in a default slot 1`] = `
"function anonymous(context, extra
) {
+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 { useRef, useState } from "../../src/hooks";
import { QWeb } from "../../src/qweb/qweb";
@@ -69,18 +69,23 @@ describe("basic widget properties", () => {
class SomeWidget extends Component {
static template = xml`<div>content</div>`;
}
const widget = new SomeWidget();
widget.mount(fixture);
await nextTick();
await mount(SomeWidget, { target: fixture });
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 () => {
class SomeWidget extends Component {
static template = xml`<div>content</div>`;
}
const widget = new SomeWidget();
await widget.mount(document.createDocumentFragment());
const widget = await mount(SomeWidget, { target: document.createDocumentFragment() });
expect(fixture.innerHTML).toBe("");
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>content</div>");
@@ -90,10 +95,9 @@ describe("basic widget properties", () => {
class SomeWidget extends Component {
static template = xml`<div>content</div>`;
}
const widget = new SomeWidget();
let error;
try {
await widget.mount(null as any);
await mount(SomeWidget, { target: null as any });
} catch (e) {
error = e;
}
@@ -107,10 +111,9 @@ describe("basic widget properties", () => {
class SomeWidget extends Component {
static template = xml`<t/>`;
}
const widget = new SomeWidget();
let error;
try {
await widget.mount(fixture);
await mount(SomeWidget, { target: fixture });
} catch (e) {
error = e;
}
@@ -137,8 +140,7 @@ describe("basic widget properties", () => {
});
}
const counter = new Counter();
counter.mount(fixture);
const counter = await mount(Counter, { target: fixture });
await nextTick();
expect(fixture.innerHTML).toBe("<div>0<button>Inc</button></div>");
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
@@ -156,8 +158,7 @@ describe("basic widget properties", () => {
static components = { Child };
}
const parent = new Parent();
await parent.mount(fixture);
await mount(Parent, { target: fixture });
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
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");
await counter.mount(target);
const counter = await mount(Counter, { target: target });
expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
button.click();
@@ -188,8 +188,7 @@ describe("basic widget properties", () => {
<div style="font-weight:bold;" class="some-class">world</div>
`;
}
const widget = new StyledWidget();
await widget.mount(fixture);
await mount(StyledWidget, { target: fixture });
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");
}
}
const widget = new TestW();
await widget.mount(fixture);
await mount(TestW, { target: fixture });
expect(steps).toEqual(["__render", "mounted"]);
});
+34 -1
View File
@@ -1,4 +1,4 @@
import { Component, Env } from "../../src/component/component";
import { Component, Env, mount } from "../../src/component/component";
import { QWeb } from "../../src/qweb/qweb";
import { xml } from "../../src/tags";
import { useState, useRef } from "../../src/hooks";
@@ -1085,4 +1085,37 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe("<div><div><p>Ablip</p><div><p>Bblip</p></div></div></div>");
});
test("dynamic t-slot call", async () => {
class Toggler extends Component {
static template = xml`<button t-on-click="toggle"><t t-slot="{{current.slot}}"/></button>`;
current = useState({ slot: "slot1" });
toggle() {
this.current.slot = this.current.slot === "slot1" ? "slot2" : "slot1";
}
}
class Parent extends Component {
static template = xml`
<div>
<Toggler>
<t t-set-slot="slot1"><p>slot1</p><span>content</span></t>
<t t-set-slot="slot2"><h1>slot2</h1></t>
</Toggler>
</div>`;
static components = { Toggler };
}
await mount(Parent, { target: fixture });
expect(fixture.innerHTML).toBe("<div><button><p>slot1</p><span>content</span></button></div>");
fixture.querySelector<HTMLElement>("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><button><h1>slot2</h1></button></div>");
fixture.querySelector<HTMLElement>("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><button><p>slot1</p><span>content</span></button></div>");
expect(env.qweb.templates[Toggler.template].fn.toString()).toMatchSnapshot();
});
});
+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 { xml } from "../../src/tags";
import { makeDeferred, makeTestEnv, makeTestFixture, nextTick, nextMicroTick } from "../helpers";
@@ -37,8 +37,7 @@ describe("mount targets", () => {
div.innerHTML = `<p>pre-existing</p>`;
fixture.appendChild(div);
const app = new App();
await app.mount(div, { position: "self" });
const app = await mount(App, { target: div, position: "self" });
expect(fixture.innerHTML).toBe(
`<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");
fixture.appendChild(div);
const app = new App();
let error;
try {
await app.mount(div, { position: "self" });
await mount(App, { target: div, position: "self" });
} catch (e) {
error = e;
}
@@ -84,8 +82,7 @@ describe("mount targets", () => {
const span = document.createElement("span");
fixture.appendChild(span);
const app = new App();
await app.mount(fixture, { position: "first-child" });
await mount(App, { target: fixture, position: "first-child" });
expect(fixture.innerHTML).toBe("<div>app</div><span></span>");
});
@@ -96,8 +93,7 @@ describe("mount targets", () => {
const span = document.createElement("span");
fixture.appendChild(span);
const app = new App();
await app.mount(fixture, { position: "last-child" });
await mount(App, { target: fixture, position: "last-child" });
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
});
@@ -108,8 +104,7 @@ describe("mount targets", () => {
const span = document.createElement("span");
fixture.appendChild(span);
const app = new App();
await app.mount(fixture);
await mount(App, { target: fixture });
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
});
});
@@ -133,8 +128,7 @@ describe("unmounting and remounting", () => {
}
}
const w = new MyWidget();
await w.mount(fixture);
const w = await mount(MyWidget, { target: fixture });
expect(fixture.innerHTML).toBe("<div>Hey</div>");
expect(steps).toEqual(["willstart", "mounted"]);
@@ -162,8 +156,7 @@ describe("unmounting and remounting", () => {
}
}
const w = new MyWidget();
await w.mount(fixture);
const w = await mount(MyWidget, { target: fixture });
await w.mount(fixture);
expect(fixture.innerHTML).toBe("<div>Hey</div>");
expect(steps).toEqual(["willstart", "mounted"]);
@@ -203,8 +196,7 @@ describe("unmounting and remounting", () => {
state = useState({ val: 1, flag: true });
}
const widget = new Parent();
await widget.mount(fixture);
const widget = await mount(Parent, { target: fixture });
expect(steps).toEqual(["render"]);
expect(fixture.innerHTML).toBe("<div><span>12</span></div>");
widget.state.flag = false;
+4 -4
View File
@@ -1,5 +1,6 @@
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
//------------------------------------------------------------------------------
@@ -419,9 +420,8 @@ async function start() {
owl.utils.whenReady()
]);
const qweb = new owl.QWeb({ templates });
owl.Component.env = { qweb };
const app = new App();
app.mount(document.body);
const env = { qweb };
await mount(App, {target: document.body, env});
}
start();
+27 -38
View File
@@ -1,5 +1,5 @@
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 {
constructor() {
@@ -22,8 +22,7 @@ class App extends Component {
App.components = { Greeter };
// Application setup
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
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
// used to generate simple transition effects.
const { Component, useState } = owl;
const { Component, useState, mount } = owl;
class Counter extends Component {
constructor() {
@@ -80,8 +79,7 @@ class App extends Component {
}
App.components = { Counter };
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
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
// clicking on the two main buttons, and look into the console to see what
// happens.
const { Component, useState } = owl;
const { Component, useState, mount } = owl;
class DemoComponent extends Component {
constructor() {
@@ -240,8 +238,7 @@ class App extends Component {
}
App.components = { DemoComponent };
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
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 {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
// position
@@ -312,8 +310,7 @@ class App extends owl.Component {
}
// Application setup
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
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'
// hook to share information between them.
const { Component, Context } = owl;
const { Component, Context, mount } = owl;
const { useContext } = owl.hooks;
class ToolbarButton extends Component {
@@ -367,8 +364,7 @@ const themeContext = new Context({
});
// Add the themeContext the environment to make it available to all components
App.env.themeContext = themeContext;
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
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
// is very similar to the VueX store.
const { Component, useState } = owl;
const { Component, useState, mount } = owl;
const { useRef, useStore, useDispatch, onPatched, onMounted } = owl.hooks;
//------------------------------------------------------------------------------
@@ -568,8 +564,7 @@ function makeStore() {
}
TodoApp.env.store = makeStore();
const app = new TodoApp();
app.mount(document.body);
mount(TodoApp, { target: document.body });
`;
const TODO_APP_STORE_XML = `<templates>
@@ -1060,8 +1055,7 @@ function setupResponsivePlugin(env) {
//------------------------------------------------------------------------------
setupResponsivePlugin(App.env);
const app = new App();
app.mount(document.body);
owl.mount(App, { target: document.body });
`;
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
// 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 {
constructor() {
@@ -1211,8 +1205,8 @@ class App extends Component {
App.components = {Card, Counter};
// Application setup
const app = new App();
app.mount(document.body);`;
mount(App, { target: document.body });
`;
const SLOTS_XML = `<templates>
<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
// because of the slow component. We use the AsyncRoot component for this
// purpose. Try removing it to see the difference.
const { Component, useState } = owl;
const { Component, useState, mount } = owl;
const { AsyncRoot } = owl.misc;
class SlowComponent extends Component {
@@ -1329,8 +1323,7 @@ class App extends Component {
}
App.components = {SlowComponent, NotificationList, AsyncRoot};
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
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.
// Note that there are two controls with t-model="color": they are totally
// synchronized.
const { Component, useState } = owl;
const { Component, useState, mount } = owl;
class Form extends Component {
constructor() {
@@ -1401,8 +1394,7 @@ class Form extends Component {
}
// Application setup
const form = new Form();
form.mount(document.body);
mount(Form, { target: document.body });
`;
const FORM_XML = `<templates>
@@ -1448,7 +1440,7 @@ const PORTAL_COMPONENTS = `
// This shows the expected use case of Portal
// which is to implement something similar
// to bootstrap modal
const { Component, useState } = owl;
const { Component, useState, mount } = owl;
const { Portal } = owl.misc;
class Modal extends Component {}
@@ -1470,8 +1462,7 @@ class App extends Component {
App.components = { Dialog , Interstellar };
// Application setup
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
const PORTAL_XML = `
@@ -1559,7 +1550,7 @@ const WMS = `// This example is slightly more complex than usual. We demonstrate
// - minimal width/height
// - better heuristic for initial window position
// - ...
const { Component, useState } = owl;
const { Component, useState, mount } = owl;
const { useRef } = owl.hooks;
class HelloWorld extends Component {}
@@ -1699,8 +1690,7 @@ const windows = [
];
App.env.windows = windows;
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
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
// contained in a single js file.
const { Component, useState, tags } = owl;
const { Component, useState, tags, mount } = owl;
const { xml, css } = tags;
// Counter component
@@ -1850,8 +1840,7 @@ App.template = APP_TEMPLATE;
App.components = { Counter };
// Application setup
const app = new App();
app.mount(document.body);
mount(App, { target: document.body });
`;
export const SAMPLES = [