mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
@@ -48,7 +48,7 @@ const app = new App({ qweb: new QWeb() });
|
||||
app.mount(document.body);
|
||||
```
|
||||
|
||||
Note that the counter component is made reactive with the `useState` hook.
|
||||
Note that the counter component is made reactive with the [`useState` hook](doc/hooks.md#usestate).
|
||||
|
||||
More interesting examples can be found on the
|
||||
[playground](https://odoo.github.io/owl/playground) application.
|
||||
|
||||
+50
-39
@@ -47,7 +47,6 @@ exclusively done by a [QWeb](qweb.md) template (which needs to be preloaded in Q
|
||||
Rendering a component generates a virtual dom representation
|
||||
of the component, which is then patched to the DOM, in order to apply the changes in an efficient way.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
Let us have a look at a simple component:
|
||||
@@ -79,7 +78,6 @@ Owl will use the component's name as template name. Here,
|
||||
a state object is defined, by using the `useState` hook. It is not mandatory to use the state object, but it is certainly encouraged. The result of the `useState` call is
|
||||
[observed](observer.md), and any change to it will cause a rerendering.
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
An Owl component is a small class which represent a component or some UI element.
|
||||
@@ -122,9 +120,6 @@ constructor.
|
||||
in some case. Note that `props` are owned by the parent, not by the component.
|
||||
As such, it should not ever be modified by the component!!
|
||||
|
||||
- **`refs`** (Object): the `refs` object contains all references to sub DOM nodes
|
||||
or sub components defined by a `t-ref` directive in the component's template.
|
||||
|
||||
### Static Properties
|
||||
|
||||
- **`template`** (string, optional): if given, this is the name of the QWeb template that will render the component. Note that there is a helper `xml` to
|
||||
@@ -517,24 +512,24 @@ class ParentComponent {
|
||||
```
|
||||
|
||||
There is an even more dynamic way to use `t-component`: its value can be an
|
||||
expression evaluating to an actual component class. In that case, this is the
|
||||
expression evaluating to an actual component class. In that case, this is the
|
||||
class that will be used to create the component:
|
||||
|
||||
```js
|
||||
class A extends Component<any, any, any> {
|
||||
static template = xml`<span>child a</span>`;
|
||||
static template = xml`<span>child a</span>`;
|
||||
}
|
||||
class B extends Component<any, any, any> {
|
||||
static template = xml`<span>child b</span>`;
|
||||
static template = xml`<span>child b</span>`;
|
||||
}
|
||||
class App extends Component<any, any, any> {
|
||||
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
||||
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
||||
|
||||
state = { child: "a" };
|
||||
state = { child: "a" };
|
||||
|
||||
get myComponent() {
|
||||
return this.state.child === "a" ? A : B;
|
||||
}
|
||||
get myComponent() {
|
||||
return this.state.child === "a" ? A : B;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -967,44 +962,60 @@ Examples:
|
||||
|
||||
### References
|
||||
|
||||
The `t-ref` directive helps a component keep reference to some inside part of it.
|
||||
Like the `t-on` directive, it can work either on a DOM node, or on a component:
|
||||
The `useRef` hook is useful when we need a way to interact with some inside part
|
||||
of a component, rendered by Owl. It can work either on a DOM node, or on a component,
|
||||
tagged by the `t-ref` directive. See the [hooks section](hooks.md#useref) for
|
||||
more detail.
|
||||
|
||||
As a short example, here is how we could set the focus on a given input:
|
||||
|
||||
```xml
|
||||
<div>
|
||||
<div t-ref="someDiv"/>
|
||||
<SubComponent t-ref="someComponent"/>
|
||||
<input t-ref="input"/>
|
||||
<button t-on-click="focusInput">Click</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
In this example, the component will be able to access the `div` and the component
|
||||
inside the special `refs` variable:
|
||||
|
||||
```js
|
||||
this.refs.someDiv;
|
||||
this.refs.someComponent;
|
||||
import { useRef } from "owl/hooks";
|
||||
|
||||
class SomeComponent extends Component {
|
||||
inputRef = useRef("input");
|
||||
|
||||
focusInput() {
|
||||
this.inputRef.el.focus();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is useful for various usecases: for example, integrating with an external
|
||||
library that needs to render itself inside an actual DOM node. Or for calling
|
||||
some method on a sub component.
|
||||
|
||||
Note: if used on a component, the reference will be set in the `refs`
|
||||
variable between `willPatch` and `patched`.
|
||||
|
||||
The `t-ref` directive also accepts dynamic values with string interpolation
|
||||
(like the [`t-attf-`](qweb.md#dynamic-attributes) and
|
||||
`t-component` directives). For example, if we have
|
||||
`id` set to 44 in the rendering context,
|
||||
The `useRef` hook can also be used to get a reference to an instance of a sub
|
||||
component rendered by Owl. In that case, we need to access it with the `comp`
|
||||
property instead of `el`:
|
||||
|
||||
```xml
|
||||
<div t-ref="component_{{id}}"/>
|
||||
<div>
|
||||
<SubComponent t-ref="sub"/>
|
||||
<button t-on-click="doSomething">Click</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
```js
|
||||
this.refs.component_44;
|
||||
import { useRef } from "owl/hooks";
|
||||
|
||||
class SomeComponent extends Component {
|
||||
static components = { SubComponent };
|
||||
subRef = useRef("sub");
|
||||
|
||||
doSomething() {
|
||||
this.subRef.comp.doSomeThingElse();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that these two examples uses the suffix `ref` to name the reference. This
|
||||
is not mandatory, but it is a useful convention, so we do not forget to access
|
||||
it with the `el` or `comp` suffix.
|
||||
|
||||
### Slots
|
||||
|
||||
To make generic components, it is useful to be able for a parent component to _inject_
|
||||
@@ -1163,19 +1174,19 @@ env.qweb.on("error", null, function(error) {
|
||||
|
||||
### Functional Components
|
||||
|
||||
Owl does not exactly have functional components. However, there is an extremely
|
||||
Owl does not exactly have functional components. However, there is an extremely
|
||||
close alternative: calling sub templates.
|
||||
|
||||
A stateless functional component in react is usually some kind of function that
|
||||
maps props to a virtual dom (often with `jsx`). So, basically, almost like a
|
||||
template rendered with `props`. In Owl, this can be done by
|
||||
template rendered with `props`. In Owl, this can be done by
|
||||
simply defining a template, that will access the `props` object:
|
||||
|
||||
```js
|
||||
const Welcome = xml`<h1>Hello, {props.name}</h1>`;
|
||||
|
||||
class MyComponent extends Component {
|
||||
static template = xml`
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-call=${Welcome}/>
|
||||
<div>something</div>
|
||||
@@ -1186,4 +1197,4 @@ class MyComponent extends Component {
|
||||
|
||||
The way this works is that sub templates are inlined, and have access to the
|
||||
ambient context. They can therefore access `props`, and any other part of the
|
||||
caller component.
|
||||
caller component.
|
||||
|
||||
+86
-33
@@ -5,14 +5,16 @@
|
||||
- [Overview](#overview)
|
||||
- [Example](#example)
|
||||
- [Reference](#reference)
|
||||
- [One Rule](#one-rule)
|
||||
- [`useState`](#usestate)
|
||||
- [`onMounted`](#onmounted)
|
||||
- [`onWillUnmount`](#onWillUnmount)
|
||||
- [One Rule](#one-rule)
|
||||
- [`useState`](#usestate)
|
||||
- [`onMounted`](#onmounted)
|
||||
- [`onWillUnmount`](#onwillunmount)
|
||||
- [`useRef`](#useref)
|
||||
|
||||
## Overview
|
||||
|
||||
Hooks were popularised by React as a way to solve the following issues:
|
||||
|
||||
- help reusing stateful logic between components
|
||||
- help organizing code by feature in complex components
|
||||
- use state in functional components, without writing a class.
|
||||
@@ -22,51 +24,49 @@ Owl hooks serve the same purpose, except that they work for class components
|
||||
there seems to be the misconception that hooks are in opposition to class. This
|
||||
is clearly not true, as shown by Owl hooks).
|
||||
|
||||
|
||||
Hooks works beautifully with Owl components: they solve the problems mentioned
|
||||
above, and in particular, they are the perfect way to make your component
|
||||
reactive.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
Here is the classical example of a non trivial hook to track the mouse position.
|
||||
|
||||
```js
|
||||
const {useState, onMounted, onWillUnmount} = owl.hooks;
|
||||
const { useState, onMounted, onWillUnmount } = owl.hooks;
|
||||
|
||||
// We define here a custom behaviour: this hook tracks the state of the mouse
|
||||
// position
|
||||
function useMouse() {
|
||||
const position = useState({x:0, y: 0});
|
||||
const position = useState({ x: 0, y: 0 });
|
||||
|
||||
function update(e) {
|
||||
position.x = e.clientX;
|
||||
position.y = e.clientY;
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener('mousemove', update);
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
window.removeEventListener('mousemove', update);
|
||||
});
|
||||
function update(e) {
|
||||
position.x = e.clientX;
|
||||
position.y = e.clientY;
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener("mousemove", update);
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
window.removeEventListener("mousemove", update);
|
||||
});
|
||||
|
||||
return position;
|
||||
return position;
|
||||
}
|
||||
|
||||
// Main root component
|
||||
class App extends owl.Component {
|
||||
static template = xml`
|
||||
static template = xml`
|
||||
<div t-name="App">
|
||||
<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>
|
||||
</div>`;
|
||||
|
||||
// this hooks is bound to the 'mouse' property.
|
||||
mouse = useMouse();
|
||||
// this hooks is bound to the 'mouse' property.
|
||||
mouse = useMouse();
|
||||
}
|
||||
```
|
||||
|
||||
Note that we use the prefix `use` for hooks, just like in React. This is just
|
||||
Note that we use the prefix `use` for hooks, just like in React. This is just
|
||||
a convention.
|
||||
|
||||
## Reference
|
||||
@@ -79,22 +79,22 @@ constructor (or in class fields):
|
||||
```js
|
||||
// ok
|
||||
class SomeComponent extends Component {
|
||||
state = useState();
|
||||
state = useState({value: 0});
|
||||
}
|
||||
|
||||
// also ok
|
||||
class SomeComponent extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = useState();
|
||||
}
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = useState({value: 0});
|
||||
}
|
||||
}
|
||||
|
||||
// not ok: this is executed after the constructor is called
|
||||
class SomeComponent extends Component {
|
||||
async willStart() {
|
||||
this.state = useState();
|
||||
}
|
||||
async willStart() {
|
||||
this.state = useState({value: 0});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -126,11 +126,64 @@ class Counter extends owl.Component {
|
||||
### `onMounted`
|
||||
|
||||
`onMounted` is not an hook, but is a building block designed to help make useful
|
||||
hooks. `onMounted` register a callback, which will be called when the component
|
||||
hooks. `onMounted` register a callback, which will be called when the component
|
||||
is mounted (see example on top of this page).
|
||||
|
||||
### `onWillUnmount`
|
||||
|
||||
`onWillUnmount` is not an hook, but is a building block designed to help make useful
|
||||
hooks. `onWillUnmount` register a callback, which will be called when the component
|
||||
hooks. `onWillUnmount` register a callback, which will be called when the component
|
||||
is unmounted (see example on top of this page).
|
||||
|
||||
### `useRef`
|
||||
|
||||
The `useRef` hook is useful when we need a way to interact with some inside part
|
||||
of a component, rendered by Owl. It can work either on a DOM node, or on a component,
|
||||
tagged by the `t-ref` directive:
|
||||
|
||||
```xml
|
||||
<div>
|
||||
<div t-ref="someDiv"/>
|
||||
<SubComponent t-ref="someComponent"/>
|
||||
</div>
|
||||
```
|
||||
|
||||
In this example, the component will be able to access the `div` and the component
|
||||
`SubComponent` using the `useRef` hook:
|
||||
|
||||
```js
|
||||
class Parent extends Component {
|
||||
subRef = useRef("someComponent");
|
||||
divRef = useRef("someDiv");
|
||||
|
||||
someMethod() {
|
||||
// here, if component is mounted, refs are active:
|
||||
// - this.divRef.el is the div HTMLElement
|
||||
// - this.subRef.comp is the instance of the sub component
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As shown by the example above, html elements are accessed by using the `el`
|
||||
key, and components references are accessed with `comp`.
|
||||
|
||||
Note: if used on a component, the reference will be set in the `refs`
|
||||
variable between `willPatch` and `patched`.
|
||||
|
||||
The `t-ref` directive also accepts dynamic values with string interpolation
|
||||
(like the [`t-attf-`](qweb.md#dynamic-attributes) and
|
||||
`t-component` directives). For example,
|
||||
|
||||
```xml
|
||||
<div t-ref="component_{{someCondition ? '1' : '2'}}"/>
|
||||
```
|
||||
|
||||
Here, the references needs to be set like this:
|
||||
|
||||
```js
|
||||
this.ref1 = useRef("component_1");
|
||||
this.ref2 = useRef("component_2");
|
||||
```
|
||||
|
||||
References are only guaranteed to be active while the parent component is mounted.
|
||||
If this is not the case, accessing `el` or `comp` on it will return `null`.
|
||||
|
||||
+2
-2
@@ -11,10 +11,10 @@ For example, this code will display `update` in the console:
|
||||
```javascript
|
||||
const observer = new owl.Observer();
|
||||
observer.notifyCB = () => console.log("update");
|
||||
const obj = observer.observe( { a: { b: 1 } });
|
||||
const obj = observer.observe({ a: { b: 1 } });
|
||||
|
||||
obj.a.b = 2;
|
||||
```
|
||||
|
||||
The observer is implemented with the native `Proxy` object. Note that this
|
||||
The observer is implemented with the native `Proxy` object. Note that this
|
||||
means that it will not work on older browsers.
|
||||
|
||||
@@ -17,6 +17,7 @@ owl
|
||||
onMounted
|
||||
onWillUnmount
|
||||
useState
|
||||
useRef
|
||||
router
|
||||
Link
|
||||
RouteComponent
|
||||
|
||||
+5
-6
@@ -61,10 +61,9 @@ useful to compare various performance metrics on some tasks.
|
||||
|
||||
If you want to have `xml` syntax highlighting while using the `xml` helper which
|
||||
helps you define inline templates, there is a VS Code addon `Comment tagged template`
|
||||
which, if installed, does exactly that. To enable it, you need to add a comment,
|
||||
which, if installed, does exactly that. To enable it, you need to add a comment,
|
||||
like this:
|
||||
|
||||
|
||||
```js
|
||||
// -----------------------------------------------------------------------------
|
||||
// TEMPLATE
|
||||
@@ -79,9 +78,9 @@ const TEMPLATE = xml/* xml */ `
|
||||
// CODE
|
||||
// -----------------------------------------------------------------------------
|
||||
class MyComponent extends Component {
|
||||
static template = TEMPLATE;
|
||||
static components = { Sidebar, Content };
|
||||
static template = TEMPLATE;
|
||||
static components = { Sidebar, Content };
|
||||
|
||||
// rest of component...
|
||||
// rest of component...
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -85,6 +85,7 @@ interface Internal<T extends Env, Props> {
|
||||
render: CompiledTemplate | null;
|
||||
mountedHandlers: { [key: number]: Function };
|
||||
classObj: { [key: string]: boolean } | null;
|
||||
refs: { [key: string]: Component<T, any> | HTMLElement | undefined } | null;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -97,6 +98,9 @@ export class Component<T extends Env, Props extends {}> {
|
||||
static template?: string | null = null;
|
||||
static _template?: string | null = null;
|
||||
static _current?: any | null = null;
|
||||
static components = {};
|
||||
static props?: any;
|
||||
static defaultProps?: any;
|
||||
|
||||
/**
|
||||
* The `el` is the root element of the component. Note that it could be null:
|
||||
@@ -105,19 +109,10 @@ export class Component<T extends Env, Props extends {}> {
|
||||
get el(): HTMLElement | null {
|
||||
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
|
||||
}
|
||||
static components = {};
|
||||
|
||||
env: T;
|
||||
props: Props;
|
||||
|
||||
// type of props is not easily representable in typescript...
|
||||
static props?: any;
|
||||
static defaultProps?: any;
|
||||
|
||||
refs: {
|
||||
[key: string]: Component<T, any> | HTMLElement | undefined;
|
||||
} = {};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -192,7 +187,8 @@ export class Component<T extends Env, Props extends {}> {
|
||||
mountedHandlers: {},
|
||||
observer: null,
|
||||
render: null,
|
||||
classObj: null
|
||||
classObj: null,
|
||||
refs: null
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -263,9 +263,10 @@ QWeb.addDirective({
|
||||
let refExpr = "";
|
||||
let refKey: string = "";
|
||||
if (ref) {
|
||||
ctx.rootContext.shouldDefineRefs = true;
|
||||
refKey = `ref${ctx.generateID()}`;
|
||||
ctx.addLine(`const ${refKey} = ${ctx.interpolate(ref)};`);
|
||||
refExpr = `context.refs[${refKey}] = w${componentID};`;
|
||||
refExpr = `context.__owl__.refs[${refKey}] = w${componentID};`;
|
||||
}
|
||||
let transitionsInsertCode = "";
|
||||
if (transition) {
|
||||
@@ -273,7 +274,7 @@ QWeb.addDirective({
|
||||
}
|
||||
let finalizeComponentCode = `w${componentID}.${keepAlive ? "unmount" : "destroy"}();`;
|
||||
if (ref && !keepAlive) {
|
||||
finalizeComponentCode += `delete context.refs[${refKey}];`;
|
||||
finalizeComponentCode += `delete context.__owl__.refs[${refKey}];`;
|
||||
}
|
||||
if (transition) {
|
||||
finalizeComponentCode = `let finalize = () => {
|
||||
|
||||
+29
-4
@@ -9,9 +9,9 @@ import { Observer } from "./core/observer";
|
||||
* - useState (reactive state)
|
||||
* - onMounted
|
||||
* - onWillUnmount
|
||||
* - useRef
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* useState hook
|
||||
*
|
||||
@@ -20,7 +20,7 @@ import { Observer } from "./core/observer";
|
||||
* trigger a rerendering of the current component.
|
||||
*/
|
||||
export function useState<T>(state: T): T {
|
||||
const component: Component<any,any> = Component._current;
|
||||
const component: Component<any, any> = Component._current;
|
||||
const __owl__ = component.__owl__;
|
||||
if (!__owl__.observer) {
|
||||
__owl__.observer = new Observer();
|
||||
@@ -34,7 +34,7 @@ export function useState<T>(state: T): T {
|
||||
* mounted. Note that the component mounted method is called first.
|
||||
*/
|
||||
export function onMounted(cb) {
|
||||
const component = Component._current;
|
||||
const component: Component<any, any> = Component._current;
|
||||
const current = component.mounted;
|
||||
component.mounted = function() {
|
||||
current.call(component);
|
||||
@@ -47,10 +47,35 @@ export function onMounted(cb) {
|
||||
* willUnmounted. Note that the component mounted method is called last.
|
||||
*/
|
||||
export function onWillUnmount(cb) {
|
||||
const component = Component._current;
|
||||
const component: Component<any, any> = Component._current;
|
||||
const current = component.willUnmount;
|
||||
component.willUnmount = function() {
|
||||
cb();
|
||||
current.call(component);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* useRef hook
|
||||
*
|
||||
* The purpose of this hook is to allow components to get a reference to a sub
|
||||
* html node or component.
|
||||
*/
|
||||
interface Ref {
|
||||
el: HTMLElement | null;
|
||||
comp: Component<any, any> | null;
|
||||
}
|
||||
|
||||
export function useRef(name: string): Ref {
|
||||
const __owl__ = Component._current.__owl__;
|
||||
return {
|
||||
get el(): HTMLElement | null {
|
||||
const val = __owl__.refs && __owl__.refs[name];
|
||||
return val instanceof HTMLElement ? val : null;
|
||||
},
|
||||
get comp(): Component<any, any> | null {
|
||||
const val = __owl__.refs && __owl__.refs[name];
|
||||
return val instanceof Component ? val : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export class Context {
|
||||
shouldDefineParent: boolean = false;
|
||||
shouldDefineQWeb: boolean = false;
|
||||
shouldDefineUtils: boolean = false;
|
||||
shouldDefineRefs: boolean = false;
|
||||
shouldDefineResult: boolean = true;
|
||||
shouldProtectContext: boolean = false;
|
||||
shouldTrackScope: boolean = false;
|
||||
@@ -62,6 +63,9 @@ export class Context {
|
||||
if (this.shouldDefineResult) {
|
||||
this.code.unshift(" let result;");
|
||||
}
|
||||
if (this.shouldDefineRefs) {
|
||||
this.code.unshift(" context.__owl__.refs = context.__owl__.refs || {};");
|
||||
}
|
||||
if (this.shouldDefineOwner) {
|
||||
// this is necessary to prevent some directives (t-forach for ex) to
|
||||
// pollute the rendering context by adding some keys in it.
|
||||
|
||||
@@ -79,9 +79,10 @@ QWeb.addDirective({
|
||||
name: "ref",
|
||||
priority: 95,
|
||||
atNodeCreation({ ctx, value, addNodeHook }) {
|
||||
ctx.rootContext.shouldDefineRefs = true;
|
||||
const refKey = `ref${ctx.generateID()}`;
|
||||
ctx.addLine(`const ${refKey} = ${ctx.interpolate(value)};`);
|
||||
addNodeHook("create", `context.refs[${refKey}] = n.elm;`);
|
||||
addNodeHook("create", `context.__owl__.refs[${refKey}] = n.elm;`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Env } from "../src/component/component";
|
||||
import { QWeb } from "../src/qweb/index";
|
||||
import { useState } from "../src/hooks";
|
||||
import { useState, useRef } from "../src/hooks";
|
||||
import {
|
||||
makeDeferred,
|
||||
makeTestFixture,
|
||||
@@ -150,6 +150,7 @@ describe("animations", () => {
|
||||
);
|
||||
class TestWidget extends Widget {
|
||||
state = useState({ hide: false });
|
||||
span = useRef("span");
|
||||
}
|
||||
const widget = new TestWidget(env);
|
||||
|
||||
@@ -164,7 +165,7 @@ describe("animations", () => {
|
||||
});
|
||||
await widget.mount(fixture);
|
||||
spanNode = widget.el!.children[0];
|
||||
expect(widget.refs.span).toBe(spanNode);
|
||||
expect(widget.span.el).toBe(spanNode);
|
||||
expect(spanNode.className).toBe("chimay-enter chimay-enter-active");
|
||||
await def; // wait for the mocked repaint to be done
|
||||
spanNode.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
||||
|
||||
@@ -299,6 +299,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
||||
let QWeb = this.constructor;
|
||||
let parent = context;
|
||||
let owner = context;
|
||||
context.__owl__.refs = context.__owl__.refs || {};
|
||||
var h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
var vn1 = h('div', p1, c1);
|
||||
@@ -327,7 +328,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
||||
w4 = new W4(parent, props4);
|
||||
parent.__owl__.cmap[4] = w4.__owl__.id;
|
||||
def3 = w4.__prepare(extra.fiber, undefined, undefined);
|
||||
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;context.refs[ref5] = w4;},remove() {},destroy(vn) {w4.destroy();delete context.refs[ref5];}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref5] = w4;},remove() {},destroy(vn) {w4.destroy();delete context.__owl__.refs[ref5];}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||
} else {
|
||||
def3 = def3 || w4.__updateProps(props4, extra.fiber, undefined, undefined);
|
||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||
@@ -359,6 +360,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
||||
let QWeb = this.constructor;
|
||||
let parent = context;
|
||||
let owner = context;
|
||||
context.__owl__.refs = context.__owl__.refs || {};
|
||||
var h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
var vn1 = h('div', p1, c1);
|
||||
@@ -387,7 +389,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
||||
w4 = new W4(parent, props4);
|
||||
parent.__owl__.cmap[4] = w4.__owl__.id;
|
||||
def3 = w4.__prepare(extra.fiber, undefined, undefined);
|
||||
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;context.refs[ref5] = w4;},remove() {},destroy(vn) {w4.destroy();delete context.refs[ref5];}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||
def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref5] = w4;},remove() {},destroy(vn) {w4.destroy();delete context.__owl__.refs[ref5];}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||
} else {
|
||||
def3 = def3 || w4.__updateProps(props4, extra.fiber, undefined, undefined);
|
||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||
@@ -1552,6 +1554,7 @@ exports[`t-slot directive refs are properly bound in slots 1`] = `
|
||||
"function anonymous(context,extra
|
||||
) {
|
||||
let owner = context;
|
||||
context.__owl__.refs = context.__owl__.refs || {};
|
||||
var h = this.h;
|
||||
let c1 = extra.parentNode;
|
||||
Object.assign(context, extra.fiber.scope);
|
||||
@@ -1566,7 +1569,7 @@ exports[`t-slot directive refs are properly bound in slots 1`] = `
|
||||
const ref11 = \`myButton\`;
|
||||
p10.hook = {
|
||||
create: (_, n) => {
|
||||
context.refs[ref11] = n.elm;
|
||||
context.__owl__.refs[ref11] = n.elm;
|
||||
},
|
||||
};
|
||||
c10.push({text: \`do something\`});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, Env } from "../../src/component/component";
|
||||
import { QWeb } from "../../src/qweb/qweb";
|
||||
import { xml } from "../../src/tags";
|
||||
import { useState } from "../../src/hooks";
|
||||
import { useState, useRef } from "../../src/hooks";
|
||||
import { EventBus } from "../../src/core/event_bus";
|
||||
import {
|
||||
makeDeferred,
|
||||
@@ -989,13 +989,15 @@ describe("composition", () => {
|
||||
});
|
||||
|
||||
test("t-refs on widget are components", async () => {
|
||||
env.qweb.addTemplate("WidgetC", `<div>Hello<t t-ref="mywidgetb" t-component="b"/></div>`);
|
||||
class WidgetC extends Widget {
|
||||
static components = { b: WidgetB };
|
||||
static template = xml`<div>Hello<WidgetB t-ref="mywidgetb" /></div>`;
|
||||
static components = { WidgetB };
|
||||
widget = useRef("mywidgetb");
|
||||
}
|
||||
|
||||
const widget = new WidgetC(env);
|
||||
await widget.mount(fixture);
|
||||
expect(widget.refs.mywidgetb instanceof WidgetB).toBe(true);
|
||||
expect(widget.widget.comp).toBeInstanceOf(WidgetB);
|
||||
});
|
||||
|
||||
test("t-refs are bound at proper timing", async () => {
|
||||
@@ -1008,11 +1010,12 @@ describe("composition", () => {
|
||||
`;
|
||||
static components = { Widget };
|
||||
state = useState({ list: <any>[] });
|
||||
child = useRef("child");
|
||||
willPatch() {
|
||||
expect(this.refs.child).toBeUndefined();
|
||||
expect(this.child.comp).toBeNull();
|
||||
}
|
||||
patched() {
|
||||
expect(this.refs.child).not.toBeUndefined();
|
||||
expect(this.child.comp).not.toBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,29 +1038,31 @@ describe("composition", () => {
|
||||
class ParentWidget extends Widget {
|
||||
static components = { Widget };
|
||||
state = useState({ child1: true, child2: false });
|
||||
child1 = useRef("child1");
|
||||
child2 = useRef("child2");
|
||||
count = 0;
|
||||
mounted() {
|
||||
expect(this.refs.child1).toBeDefined();
|
||||
expect(this.refs.child2).toBeUndefined();
|
||||
expect(this.child1.comp).toBeDefined();
|
||||
expect(this.child2.comp).toBeNull();
|
||||
}
|
||||
willPatch() {
|
||||
if (this.count === 0) {
|
||||
expect(this.refs.child1).toBeDefined();
|
||||
expect(this.refs.child2).toBeUndefined();
|
||||
expect(this.child1.comp).toBeDefined();
|
||||
expect(this.child2.comp).toBeNull();
|
||||
}
|
||||
if (this.count === 1) {
|
||||
expect(this.refs.child1).toBeDefined();
|
||||
expect(this.refs.child2).toBeDefined();
|
||||
expect(this.child1.comp).toBeDefined();
|
||||
expect(this.child2.comp).toBeDefined();
|
||||
}
|
||||
}
|
||||
patched() {
|
||||
if (this.count === 0) {
|
||||
expect(this.refs.child1).toBeDefined();
|
||||
expect(this.refs.child2).toBeDefined();
|
||||
expect(this.child1.comp).toBeDefined();
|
||||
expect(this.child2.comp).toBeDefined();
|
||||
}
|
||||
if (this.count === 1) {
|
||||
expect(this.refs.child1).toBeUndefined();
|
||||
expect(this.refs.child2).toBeDefined();
|
||||
expect(this.child1.comp).toBeNull();
|
||||
expect(this.child2.comp).toBeDefined();
|
||||
}
|
||||
this.count++;
|
||||
}
|
||||
@@ -1095,12 +1100,19 @@ describe("composition", () => {
|
||||
</div>`
|
||||
);
|
||||
class ParentWidget extends Widget {
|
||||
state = useState({ items: [1, 2, 3] });
|
||||
static components = { Child: Widget };
|
||||
elem1 = useRef("1");
|
||||
elem2 = useRef("2");
|
||||
elem3 = useRef("3");
|
||||
elem4 = useRef("4");
|
||||
state = useState({ items: [1, 2, 3] });
|
||||
}
|
||||
const parent = new ParentWidget(env);
|
||||
await parent.mount(fixture);
|
||||
expect(Object.keys(parent.refs)).toEqual(["1", "2", "3"]);
|
||||
expect(parent.elem1.comp).toBeDefined();
|
||||
expect(parent.elem2.comp).toBeDefined();
|
||||
expect(parent.elem3.comp).toBeDefined();
|
||||
expect(parent.elem4.comp).toBeNull();
|
||||
});
|
||||
|
||||
test("parent's elm for a children === children's elm, even after rerender", async () => {
|
||||
@@ -1217,36 +1229,37 @@ describe("composition", () => {
|
||||
});
|
||||
|
||||
test("sub widget with t-ref and t-keepalive", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="ParentWidget">
|
||||
<t t-if="state.ok"><ChildWidget t-ref="child" t-keepalive="1"/></t>
|
||||
</div>
|
||||
<span t-name="ChildWidget">Hello</span>
|
||||
</templates>`);
|
||||
class ChildWidget extends Widget {}
|
||||
class ChildWidget extends Widget {
|
||||
static template = xml`<span>Hello</span>`;
|
||||
}
|
||||
class ParentWidget extends Widget {
|
||||
state = useState({ ok: true });
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-if="state.ok"><ChildWidget t-ref="child" t-keepalive="1"/></t>
|
||||
</div>
|
||||
`;
|
||||
static components = { ChildWidget };
|
||||
state = useState({ ok: true });
|
||||
child = useRef("child");
|
||||
}
|
||||
const widget = new ParentWidget(env);
|
||||
await widget.mount(fixture);
|
||||
let child = children(widget)[0];
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><span>Hello</span></div>");
|
||||
expect(widget.refs.child).toEqual(child);
|
||||
expect(widget.child.comp).toEqual(child);
|
||||
|
||||
widget.state.ok = false;
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div></div>");
|
||||
expect(widget.refs.child).toEqual(child);
|
||||
expect(widget.child.comp).toEqual(child);
|
||||
|
||||
widget.state.ok = true;
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><span>Hello</span></div>");
|
||||
expect(widget.refs.child).toEqual(child);
|
||||
expect(widget.child.comp).toEqual(child);
|
||||
});
|
||||
|
||||
test("sub components rendered in a loop", async () => {
|
||||
@@ -1582,6 +1595,7 @@ describe("class and style attributes with t-component", () => {
|
||||
class ParentWidget extends Widget {
|
||||
static components = { Child };
|
||||
state = useState({ b: true });
|
||||
child = useRef("child");
|
||||
}
|
||||
const widget = new ParentWidget(env);
|
||||
await widget.mount(fixture);
|
||||
@@ -1593,7 +1607,7 @@ describe("class and style attributes with t-component", () => {
|
||||
await nextTick();
|
||||
expect(span.className).toBe("c d a");
|
||||
|
||||
(<any>widget.refs.child).state.d = false;
|
||||
(widget.child.comp as Child).state.d = false;
|
||||
await nextTick();
|
||||
expect(span.className).toBe("c a");
|
||||
|
||||
@@ -1601,7 +1615,7 @@ describe("class and style attributes with t-component", () => {
|
||||
await nextTick();
|
||||
expect(span.className).toBe("c a b");
|
||||
|
||||
(<any>widget.refs.child).state.d = true;
|
||||
(widget.child.comp as Child).state.d = true;
|
||||
await nextTick();
|
||||
expect(span.className).toBe("c a b d");
|
||||
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
||||
@@ -1623,6 +1637,7 @@ describe("class and style attributes with t-component", () => {
|
||||
class ParentWidget extends Widget {
|
||||
static components = { Child };
|
||||
state = useState({ b: true });
|
||||
child = useRef("child");
|
||||
}
|
||||
const widget = new ParentWidget(env);
|
||||
await widget.mount(fixture);
|
||||
@@ -1634,7 +1649,7 @@ describe("class and style attributes with t-component", () => {
|
||||
await nextTick();
|
||||
expect(span.className).toBe("c d a");
|
||||
|
||||
(<any>widget.refs.child).state.d = false;
|
||||
(widget.child.comp as Child).state.d = false;
|
||||
await nextTick();
|
||||
expect(span.className).toBe("c a");
|
||||
|
||||
@@ -1642,7 +1657,7 @@ describe("class and style attributes with t-component", () => {
|
||||
await nextTick();
|
||||
expect(span.className).toBe("c a b");
|
||||
|
||||
(<any>widget.refs.child).state.d = true;
|
||||
(widget.child.comp as Child).state.d = true;
|
||||
await nextTick();
|
||||
expect(span.className).toBe("c a b d");
|
||||
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
||||
@@ -3000,12 +3015,13 @@ describe("t-mounted directive", () => {
|
||||
test("combined with a t-ref", async () => {
|
||||
env.qweb.addTemplate("TestWidget", `<div><input t-ref="input" t-mounted="f"/></div>`);
|
||||
class TestWidget extends Widget {
|
||||
input = useRef("input");
|
||||
f() {}
|
||||
}
|
||||
const widget = new TestWidget(env);
|
||||
widget.f = jest.fn();
|
||||
await widget.mount(fixture);
|
||||
expect(widget.refs.input).toBeDefined();
|
||||
expect(widget.input.el).toBeDefined();
|
||||
expect(widget.f).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -3233,21 +3249,21 @@ describe("t-slot directive", () => {
|
||||
});
|
||||
|
||||
test("refs are properly bound in slots", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="Parent">
|
||||
class Dialog extends Widget {
|
||||
static template = xml`<span><t t-slot="footer"/></span>`;
|
||||
}
|
||||
class Parent extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<span class="counter"><t t-esc="state.val"/></span>
|
||||
<Dialog>
|
||||
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
|
||||
</Dialog>
|
||||
</div>
|
||||
<span t-name="Dialog"><t t-slot="footer"/></span>
|
||||
</templates>
|
||||
`);
|
||||
class Dialog extends Widget {}
|
||||
class Parent extends Widget {
|
||||
`;
|
||||
static components = { Dialog };
|
||||
state = useState({ val: 0 });
|
||||
button = useRef("myButton");
|
||||
doSomething() {
|
||||
this.state.val++;
|
||||
}
|
||||
@@ -3259,7 +3275,7 @@ describe("t-slot directive", () => {
|
||||
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
|
||||
);
|
||||
|
||||
(<any>parent.refs.myButton).click();
|
||||
parent.button.el!.click();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
|
||||
+19
-1
@@ -1,6 +1,6 @@
|
||||
import { makeTestEnv, makeTestFixture, nextTick } from "./helpers";
|
||||
import { Component, Env } from "../src/component/component";
|
||||
import { useState, onMounted, onWillUnmount } from "../src/hooks";
|
||||
import { useState, onMounted, onWillUnmount, useRef } from "../src/hooks";
|
||||
import { xml } from "../src/tags";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -129,4 +129,22 @@ describe("hooks", () => {
|
||||
"hook:willunmount1"
|
||||
]);
|
||||
});
|
||||
|
||||
test("useRef hook", async () => {
|
||||
class Counter extends Component<any, any> {
|
||||
static template = xml`<div><button t-ref="button"><t t-esc="value"/></button></div>`;
|
||||
button = useRef("button");
|
||||
value = 0;
|
||||
increment() {
|
||||
this.value++;
|
||||
(this.button.el as HTMLButtonElement).innerHTML = String(this.value);
|
||||
}
|
||||
}
|
||||
const counter = new Counter(env);
|
||||
await counter.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><button>0</button></div>");
|
||||
counter.increment();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><button>1</button></div>");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1915,6 +1915,7 @@ exports[`t-raw variable 1`] = `
|
||||
exports[`t-ref can get a dynamic ref on a node 1`] = `
|
||||
"function anonymous(context,extra
|
||||
) {
|
||||
context.__owl__.refs = context.__owl__.refs || {};
|
||||
var h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
var vn1 = h('div', p1, c1);
|
||||
@@ -1925,7 +1926,7 @@ exports[`t-ref can get a dynamic ref on a node 1`] = `
|
||||
const ref3 = \`myspan\${context['id']}\`;
|
||||
p2.hook = {
|
||||
create: (_, n) => {
|
||||
context.refs[ref3] = n.elm;
|
||||
context.__owl__.refs[ref3] = n.elm;
|
||||
},
|
||||
};
|
||||
return vn1;
|
||||
@@ -1935,6 +1936,7 @@ exports[`t-ref can get a dynamic ref on a node 1`] = `
|
||||
exports[`t-ref can get a ref on a node 1`] = `
|
||||
"function anonymous(context,extra
|
||||
) {
|
||||
context.__owl__.refs = context.__owl__.refs || {};
|
||||
var h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
var vn1 = h('div', p1, c1);
|
||||
@@ -1945,7 +1947,7 @@ exports[`t-ref can get a ref on a node 1`] = `
|
||||
const ref3 = \`myspan\`;
|
||||
p2.hook = {
|
||||
create: (_, n) => {
|
||||
context.refs[ref3] = n.elm;
|
||||
context.__owl__.refs[ref3] = n.elm;
|
||||
},
|
||||
};
|
||||
return vn1;
|
||||
@@ -1955,6 +1957,7 @@ exports[`t-ref can get a ref on a node 1`] = `
|
||||
exports[`t-ref refs in a loop 1`] = `
|
||||
"function anonymous(context,extra
|
||||
) {
|
||||
context.__owl__.refs = context.__owl__.refs || {};
|
||||
context = Object.create(context);
|
||||
var h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
@@ -1981,7 +1984,7 @@ exports[`t-ref refs in a loop 1`] = `
|
||||
const ref6 = (context['item']);
|
||||
p5.hook = {
|
||||
create: (_, n) => {
|
||||
context.refs[ref6] = n.elm;
|
||||
context.__owl__.refs[ref6] = n.elm;
|
||||
},
|
||||
};
|
||||
var _7 = context['item'];
|
||||
|
||||
@@ -1092,14 +1092,14 @@ describe("t-ref", () => {
|
||||
test("can get a ref on a node", () => {
|
||||
qweb.addTemplate("test", `<div><span t-ref="myspan"/></div>`);
|
||||
let refs: any = {};
|
||||
renderToDOM(qweb, "test", { refs });
|
||||
renderToDOM(qweb, "test", { __owl__: { refs } });
|
||||
expect(refs.myspan.tagName).toBe("SPAN");
|
||||
});
|
||||
|
||||
test("can get a dynamic ref on a node", () => {
|
||||
qweb.addTemplate("test", `<div><span t-ref="myspan{{id}}"/></div>`);
|
||||
let refs: any = {};
|
||||
renderToDOM(qweb, "test", { refs, id: 3 });
|
||||
renderToDOM(qweb, "test", { id: 3, __owl__: { refs } });
|
||||
expect(refs.myspan3.tagName).toBe("SPAN");
|
||||
});
|
||||
|
||||
@@ -1114,7 +1114,7 @@ describe("t-ref", () => {
|
||||
</div>`
|
||||
);
|
||||
let refs: any = {};
|
||||
renderToDOM(qweb, "test", { refs, items: [1, 2, 3] });
|
||||
renderToDOM(qweb, "test", { items: [1, 2, 3], __owl__: { refs } });
|
||||
expect(Object.keys(refs)).toEqual(["1", "2", "3"]);
|
||||
});
|
||||
});
|
||||
@@ -1333,5 +1333,5 @@ describe("properly support svg", () => {
|
||||
expect(renderToString(qweb, "test")).toBe(
|
||||
`<g><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </g>`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
|
||||
|
||||
const useState = owl.hooks.useState;
|
||||
const { useState, useRef } = owl.hooks;
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -34,6 +34,7 @@ class Message extends owl.Component {
|
||||
class App extends owl.Component {
|
||||
static components = { Message };
|
||||
state = useState({ messages: [], multipleFlag: false, clearAfterFlag: false });
|
||||
logRef = useRef("log");
|
||||
|
||||
mounted() {
|
||||
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
|
||||
@@ -130,12 +131,12 @@ class App extends owl.Component {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.refs.log.appendChild(div);
|
||||
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||
this.logRef.el.appendChild(div);
|
||||
this.logRef.el.scrollTop = this.logRef.el.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.refs.log.innerHTML = "";
|
||||
this.logRef.el.innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SAMPLES } from "./samples.js";
|
||||
const useState = owl.hooks.useState;
|
||||
const {useState, useRef} = owl.hooks;
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants, helpers, utils
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -163,11 +163,11 @@ class TabbedEditor extends owl.Component {
|
||||
|
||||
this.sessions = {};
|
||||
this._setupSessions(props);
|
||||
this.editor = null;
|
||||
this.editorNode = useRef("editor");
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.editor = this.editor || ace.edit(this.refs.editor);
|
||||
this.editor = this.editor || ace.edit(this.editorNode.el);
|
||||
|
||||
this.editor.setValue(this.props[this.state.currentTab], -1);
|
||||
this.editor.setFontSize("12px");
|
||||
@@ -264,13 +264,14 @@ class App extends owl.Component {
|
||||
this.toggleLayout = owl.utils.debounce(this.toggleLayout, 250, true);
|
||||
this.runCode = owl.utils.debounce(this.runCode, 250, true);
|
||||
this.downloadCode = owl.utils.debounce(this.downloadCode, 250, true);
|
||||
this.content = useRef("content");
|
||||
}
|
||||
|
||||
displayError(error) {
|
||||
this.state.error = error;
|
||||
if (error) {
|
||||
setTimeout(() => {
|
||||
this.refs.content.innerHTML = "";
|
||||
this.content.el.innerHTML = "";
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -296,8 +297,8 @@ class App extends owl.Component {
|
||||
} else {
|
||||
this.state.error = false;
|
||||
}
|
||||
this.refs.content.innerHTML = "";
|
||||
this.refs.content.appendChild(subiframe);
|
||||
this.content.el.innerHTML = "";
|
||||
this.content.el.appendChild(subiframe);
|
||||
}
|
||||
|
||||
setSample(ev) {
|
||||
|
||||
@@ -325,6 +325,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 { useRef } = owl.hooks;
|
||||
|
||||
const ENTER_KEY = 13;
|
||||
const ESC_KEY = 27;
|
||||
@@ -397,6 +398,7 @@ function makeStore() {
|
||||
//------------------------------------------------------------------------------
|
||||
class TodoItem extends Component {
|
||||
state = useState({ isEditing: false });
|
||||
inputRef = useRef("input");
|
||||
|
||||
removeTodo() {
|
||||
this.env.store.dispatch("removeTodo", this.props.id);
|
||||
@@ -411,9 +413,9 @@ class TodoItem extends Component {
|
||||
}
|
||||
|
||||
focusInput() {
|
||||
this.refs.input.value = "";
|
||||
this.refs.input.focus();
|
||||
this.refs.input.value = this.props.title;
|
||||
this.inputRef.el.value = "";
|
||||
this.inputRef.el.focus();
|
||||
this.inputRef.el.value = this.props.title;
|
||||
}
|
||||
|
||||
handleKeyup(ev) {
|
||||
@@ -1380,6 +1382,7 @@ const WMS = `// This example is slightly more complex than usual. We demonstrate
|
||||
// - better heuristic for initial window position
|
||||
// - ...
|
||||
const { Component, useState } = owl;
|
||||
const { useRef } = owl.hooks;
|
||||
|
||||
class HelloWorld extends Component {}
|
||||
|
||||
@@ -1392,14 +1395,17 @@ class Counter extends Component {
|
||||
}
|
||||
|
||||
class Window extends Component {
|
||||
|
||||
get style() {
|
||||
let { width, height, top, left, zindex } = this.props.info;
|
||||
|
||||
return \`width: \${width}px;height: \${height}px;top:\${top}px;left:\${left}px;z-index:\${zindex}\`;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.trigger("close-window", { id: this.props.info.id });
|
||||
}
|
||||
|
||||
startDragAndDrop(ev) {
|
||||
this.updateZIndex();
|
||||
this.el.classList.add('dragging');
|
||||
@@ -1425,6 +1431,7 @@ class Window extends Component {
|
||||
self.trigger("set-window-position", options);
|
||||
}
|
||||
}
|
||||
|
||||
updateZIndex() {
|
||||
this.trigger("update-z-index", { id: this.props.info.id });
|
||||
}
|
||||
@@ -1435,20 +1442,26 @@ class WindowManager extends Component {
|
||||
windows = [];
|
||||
nextId = 1;
|
||||
currentZindex = 1;
|
||||
nextLeft = 0;
|
||||
nextTop = 0;
|
||||
|
||||
addWindow(name) {
|
||||
const info = this.env.windows.find(w => w.name === name);
|
||||
this.nextLeft = this.nextLeft + 30;
|
||||
this.nextTop = this.nextTop + 30;
|
||||
this.windows.push({
|
||||
id: this.nextId++,
|
||||
title: info.title,
|
||||
width: info.defaultWidth,
|
||||
height: info.defaultHeight,
|
||||
top: 0,
|
||||
left: 0,
|
||||
top: this.nextTop,
|
||||
left: this.nextLeft,
|
||||
zindex: this.currentZindex++,
|
||||
component: info.component
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
||||
closeWindow(ev) {
|
||||
const id = ev.detail.id;
|
||||
delete this.constructor.components[id];
|
||||
@@ -1456,12 +1469,14 @@ class WindowManager extends Component {
|
||||
this.windows.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
|
||||
setWindowPosition(ev) {
|
||||
const id = ev.detail.id;
|
||||
const w = this.windows.find(w => w.id === id);
|
||||
w.top = ev.detail.top;
|
||||
w.left = ev.detail.left;
|
||||
}
|
||||
|
||||
updateZIndex(ev) {
|
||||
const id = ev.detail.id;
|
||||
const w = this.windows.find(w => w.id === id);
|
||||
@@ -1472,9 +1487,10 @@ class WindowManager extends Component {
|
||||
|
||||
class App extends Component {
|
||||
static components = { WindowManager };
|
||||
wmRef = useRef("wm");
|
||||
|
||||
addWindow(name) {
|
||||
this.refs.wm.addWindow(name);
|
||||
this.wmRef.comp.addWindow(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user