mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] owl: add a new mount method
This commit is contained in:
@@ -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).
|
||||||
|
|||||||
@@ -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(...);
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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 });
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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__ = {};
|
||||||
|
|||||||
@@ -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"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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 = [
|
||||||
|
|||||||
Reference in New Issue
Block a user