mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
34695883c2
closes #202
997 lines
35 KiB
Markdown
997 lines
35 KiB
Markdown
# 🦉 OWL Component 🦉
|
|
|
|
## Content
|
|
|
|
- [Overview](#overview)
|
|
- [Example](#example)
|
|
- [Reference](#reference)
|
|
- [Properties](#properties)
|
|
- [Static Properties](#static-properties)
|
|
- [Methods](#methods)
|
|
- [Lifecycle](#lifecycle)
|
|
- [Root Component](#root-component)
|
|
- [Environment](#environment)
|
|
- [Composition](#composition)
|
|
- [Event Handling](#event-handling)
|
|
- [Form Input Bindings](#form-input-bindings)
|
|
- [`t-key` Directive](#t-key-directive)
|
|
- [`t-mounted` Directive](#t-mounted-directive)
|
|
- [Semantics](#semantics)
|
|
- [Props Validation](#props-validation)
|
|
- [References](#references)
|
|
- [Slots](#slots)
|
|
- [Asynchronous Rendering](#asynchronous-rendering)
|
|
|
|
## Overview
|
|
|
|
OWL components are the building blocks for user interface. They are designed to be:
|
|
|
|
1. **declarative:** the user interface should be described in term of the state
|
|
of the application, not as a sequence of imperative steps.
|
|
|
|
2. **composable:** each component can seamlessly be created in a parent component by
|
|
a simple tag or directive in its template.
|
|
|
|
3. **asynchronous rendering:** the framework will transparently wait for each
|
|
sub components to be ready before applying the rendering. It uses native promises
|
|
under the hood.
|
|
|
|
4. **uses QWeb as a template system:** the templates are described in XML
|
|
and follow the QWeb specification. This is a requirement for Odoo.
|
|
|
|
OWL components are defined as a subclass of Component. The rendering is
|
|
exclusively done by a [QWeb](qweb.md) template (which needs to be preloaded in QWeb).
|
|
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.
|
|
|
|
OWL components observe their states, and rerender themselves whenever it is
|
|
changed. This is done by an [observer](observer.md).
|
|
|
|
## Example
|
|
|
|
Let us have a look at a simple component:
|
|
|
|
```javascript
|
|
class ClickCounter extends owl.Component {
|
|
state = { value: 0 };
|
|
|
|
increment() {
|
|
this.state.value++;
|
|
}
|
|
}
|
|
```
|
|
|
|
```xml
|
|
<button t-name="ClickCounter" t-on-click="increment">
|
|
Click Me! [<t t-esc="state.value"/>]
|
|
</button>
|
|
```
|
|
|
|
Note that this code is written in ESNext style, so it will only run on the
|
|
latest browsers without a transpilation step.
|
|
|
|
This example show how a component should be defined: it simply subclasses the
|
|
Component class. If no `template` key is defined, then
|
|
Owl will use the component's name as template name. Here,
|
|
a state object is defined. It is not mandatory to use the state object, but it
|
|
is certainly encouraged. The state object 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.
|
|
It exists in the context of an environment (`env`), which is propagated from a
|
|
parent to its children. The environment needs to have a QWeb instance, which
|
|
will be used to render the component template.
|
|
|
|
Be aware that the name of the component may be significant: if a component does
|
|
not define a `template` key, then Owl will lookup in QWeb to
|
|
find a template with the component name (or one of its ancestor).
|
|
|
|
### Properties
|
|
|
|
- **`el`** (HTMLElement | null): reference to the DOM root node of the element. It is `null` when the
|
|
component is not mounted.
|
|
|
|
- **`env`** (Object): the component environment, which contains a QWeb instance.
|
|
|
|
- **`template`** (string, optional): if given, this is the name of the QWeb template that will render
|
|
the component.
|
|
|
|
- **`state`** (Object): this is the location of the component's state, if there is
|
|
any. After the willStart method, the `state` property is observed, and each
|
|
change will cause the component to rerender itself.
|
|
|
|
- **`props`** (Object): this is an object given (in the constructor) by the parent
|
|
to configure the component. It can be dynamically changed later by the parent,
|
|
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
|
|
|
|
- **`props`** (Object, optional): if given, this is an object that describes the
|
|
type and shape of the (actual) props given to the component. If Owl mode is
|
|
`dev`, this will be used to validate the props each time the component is
|
|
created/updated. See [Props Validation](#props-validation) for more information.
|
|
- **`defaultProps`** (Object, optional): if given, this object define default
|
|
values for (top-level) props. Whenever `props` are given to the object, they
|
|
will be altered to add default value (if missing). Note that it does not
|
|
change the initial object, a new object will be created instead.
|
|
|
|
### Methods
|
|
|
|
We explain here all the public methods of the `Component` class.
|
|
|
|
- **`mount(target)`** (async): this is the main way a component's hierarchy is added to the
|
|
DOM: the root component is mounted to a target HTMLElement. Obviously, this
|
|
is asynchronous, since each children need to be created as well. Most applications
|
|
will need to call `mount` exactly once, on the root component.
|
|
|
|
- **`unmount()`**: in case a component need 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.
|
|
|
|
- **`render()`** (async): calling this method directly will cause a rerender. Note
|
|
that this should be very rare to have to do it manually, the Owl framework is
|
|
most of the time responsible for doing that at an appropriate moment.
|
|
|
|
Note that the render method is asynchronous, so one cannot observe the updated
|
|
DOM in the same stack frame.
|
|
|
|
- **`shouldUpdate(nextProps)`**: this method is called each time a component's props
|
|
are updated. It returns a boolean, which indicates if the component should
|
|
ignore a props update. If it returns false, then `willUpdateProps` will not
|
|
be called, and no rendering will occur. Its default implementation is to
|
|
always return true. This is an optimization, similar to React's `shouldComponentUpdate`. Most of the time, this should not be used, but it
|
|
can be useful if we are handling large number of components.
|
|
|
|
- **`updateEnv(nextEnv)`**: update the environment of a component and all its
|
|
children. This forces a complete rerender. For example, this could be useful
|
|
if we have a `isMobile` key in the environment, to decide if we want a mobile
|
|
interface or a destkop one.
|
|
|
|
- **`destroy()`**. As its name suggests, this method will remove the component,
|
|
and perform all necessary cleanup, such as unmounting the component, its children,
|
|
removing the parent/children relationship. This method should almost never be
|
|
called directly (except maybe on the root component), but should be done by the
|
|
framework instead.
|
|
|
|
Obviously, these methods are reserved for Owl, and should not be used by Owl
|
|
users, unless they want to override them. Also, Owl reserves all method names
|
|
starting with `__`, in order to prevent possible future conflicts with user code
|
|
whenever Owl needs to change.
|
|
|
|
### Lifecycle
|
|
|
|
A solid and robust component system needs useful hooks/methods to help
|
|
developers write components. Here is a complete description of the lifecycle of
|
|
a owl component:
|
|
|
|
| Method | Description |
|
|
| ------------------------------------------------ | ----------------------------------------------------- |
|
|
| **[constructor](#constructorparent-props)** | constructor |
|
|
| **[willStart](#willstart)** | async, before first rendering |
|
|
| **[mounted](#mounted)** | just after component is rendered and added to the DOM |
|
|
| **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update |
|
|
| **[willPatch](#willpatch)** | just before the DOM is patched |
|
|
| **[patched](#patchedsnapshot)** | just after the DOM is patched |
|
|
| **[willUnmount](#willunmount)** | just before removing component from DOM |
|
|
|
|
Notes:
|
|
|
|
- hooks call order is precisely defined: `[willX]` hooks are called first on parent,
|
|
then on children, and `[Xed]` are called in the reverse order: first children,
|
|
then parent.
|
|
- no hook method should ever be called manually. They are supposed to be
|
|
called by the owl framework whenever it is required.
|
|
|
|
#### `constructor(parent, props)`
|
|
|
|
The constructor is not exactly a hook, it is the regular,
|
|
normal, constructor of the component. Since it is not a hook, you need to make
|
|
sure that `super` is called.
|
|
|
|
This is usually where you would set the initial state and the template of the
|
|
component.
|
|
|
|
```javascript
|
|
constructor(parent, props) {
|
|
super(parent, props);
|
|
this.state = {someValue: true};
|
|
this.template = 'mytemplate';
|
|
}
|
|
```
|
|
|
|
Note that with ESNext class fields, the constructor method does not need to be
|
|
implemented in most cases:
|
|
|
|
```javascript
|
|
class ClickCounter extends owl.Component {
|
|
state = { value: 0 };
|
|
|
|
...
|
|
}
|
|
```
|
|
|
|
#### `willStart()`
|
|
|
|
willStart is an asynchronous hook that can be implemented to
|
|
perform some action before the initial rendering of a component.
|
|
|
|
It will be called exactly once before the initial rendering. It is useful
|
|
in some cases, for example, to load external assets (such as a JS library)
|
|
before the component is rendered. Another use case is to load data from a server.
|
|
|
|
```javascript
|
|
async willStart() {
|
|
await owl.utils.loadJS("my-awesome-lib.js");
|
|
}
|
|
```
|
|
|
|
At this point, the component is not yet rendered. Note that a slow `willStart` method will slow down the rendering of the user
|
|
interface. Therefore, some care should be made to make this method as
|
|
fast as possible.
|
|
|
|
The component rendering will take place after `willStart` is completed.
|
|
|
|
#### `mounted()`
|
|
|
|
`mounted` is called each time a component is attached to the
|
|
DOM, after the initial rendering and possibly later if the component was unmounted
|
|
and remounted. At this point, the component is considered _active_. This is a good place to add some listeners, or to interact with the
|
|
DOM, if the component needs to perform some measure for example.
|
|
|
|
It is the opposite of `willUnmount`. If a component has been mounted, it will
|
|
always be unmounted at some point in the future.
|
|
|
|
The mounted method will be called recursively on each of its children. First,
|
|
the parent, then all its children.
|
|
|
|
Note that the state is now observed. It is however allowed (but not encouraged)
|
|
to modify the state in the `mounted` hook. Doing so will cause a rerender,
|
|
which will not be perceptible by the user, but will slightly slow down the
|
|
component.
|
|
|
|
#### `willUpdateProps(nextProps)`
|
|
|
|
The willUpdateProps is an asynchronous hook, called just before new props
|
|
are set. This is useful if the component needs some asynchronous task
|
|
performed, depending on the props (for example, assuming that the props are
|
|
some record Id, fetching the record data).
|
|
|
|
```javascript
|
|
willUpdateProps(nextProps) {
|
|
return this.loadData({id: nextProps.id});
|
|
}
|
|
```
|
|
|
|
This hook is not called during the first render (but willStart is called
|
|
and performs a similar job).
|
|
|
|
#### `willPatch()`
|
|
|
|
The willPatch hook is called just before the DOM patching process starts.
|
|
It is not called on the initial render. This is useful to read some
|
|
information from the DOM. For example, the current position of the
|
|
scrollbar.
|
|
|
|
Note that modifying the state object is not allowed here. This method is called just
|
|
before an actual DOM patch, and is only intended to be used to save some local
|
|
DOM state. Also, it will not be called if the component is not in the DOM (this can
|
|
happen with components with `t-keepalive`).
|
|
|
|
The return value of this method will be given as the first argument of the
|
|
corresponding `patched` call.
|
|
|
|
#### `patched(snapshot)`
|
|
|
|
This hook is called whenever a component did actually update its DOM (most
|
|
likely via a change in its state/props or environment).
|
|
|
|
This method is not called on the initial render. It is useful to interact
|
|
with the DOM (for example, through an external library) whenever the
|
|
component was patched. Note that this hook will not be called if the compoent is
|
|
not in the DOM (this can happen with components with `t-keepalive`).
|
|
|
|
The `snapshot` parameter is the result of the previous `willPatch` call.
|
|
|
|
Updating the compoent state in this hook is possible, but not encouraged.
|
|
One need to be careful, because updates here will cause rerender, which in
|
|
turn will cause other calls to patched. So, we need to be particularly
|
|
careful at avoiding endless cycles.
|
|
|
|
#### `willUnmount()`
|
|
|
|
willUnmount is a hook that is called each time just before a component is unmounted from
|
|
the DOM. This is a good place to remove some listeners, for example.
|
|
|
|
```javascript
|
|
mounted() {
|
|
this.env.bus.on('someevent', this, this.doSomething);
|
|
}
|
|
willUnmount() {
|
|
this.env.bus.off('someevent', this, this.doSomething);
|
|
}
|
|
```
|
|
|
|
This is the opposite method of `mounted`.
|
|
|
|
### Root Component
|
|
|
|
Most of the time, an Owl component will be created automatically by a tag (or the `t-component`
|
|
directive) in a template. There is however an obvious exception: the root component
|
|
of an Owl application has to be created manually:
|
|
|
|
```js
|
|
class App extends owl.Component { ... }
|
|
|
|
const qweb = new owl.QWeb(TEMPLATES);
|
|
const env = { qweb: qweb };
|
|
const app = new App(env);
|
|
app.mount(document.body);
|
|
```
|
|
|
|
The root component needs an environment.
|
|
|
|
### Environment
|
|
|
|
In Owl, an environment is an object with a `qweb` key, which has to be a
|
|
[QWeb](qweb.md) instance. This qweb instance will be used to render everything.
|
|
|
|
The environment is meant to contain (mostly) static global information and
|
|
methods for the whole application. For example, settings keys (`mode` to determine
|
|
if we are in desktop or mobile mode, or `theme`: dark or light), `rpc` methods,
|
|
session information, ...
|
|
|
|
The environment will be given to each child, unchanged, in the `env` property.
|
|
This can be very useful to share common information/methods. For example, all
|
|
rpcs can be made through a `rpc` method in the environment. This makes it very
|
|
easy to test a component.
|
|
|
|
Updating the environment is not as simple as changing a component's state: its
|
|
content is not observed, so updates will not be reflected immediately in the
|
|
user interface. There is however a mechanism to force root widgets to rerender
|
|
themselves whenever the environment is modified: one only needs to trigger the
|
|
`update` event on the QWeb instance. For example, a responsive environment
|
|
could be programmed like this:
|
|
|
|
```js
|
|
function setupResponsivePlugin(env) {
|
|
const isMobile = () => window.innerWidth <= 768;
|
|
env.isMobile = isMobile();
|
|
const updateEnv = owl.utils.debounce(() => {
|
|
if (env.isMobile !== isMobile()) {
|
|
env.isMobile = !env.isMobile;
|
|
env.qweb.trigger('update');
|
|
}
|
|
}, 15);
|
|
window.addEventListener("resize", updateEnv);
|
|
}
|
|
```
|
|
|
|
### Composition
|
|
|
|
The example above shows a QWeb template with a sub component. In a template,
|
|
components are declared with a tagname corresponding to the class name. It has
|
|
to be capitalized.
|
|
|
|
```xml
|
|
<div t-name="ParentComponent">
|
|
<span>some text</span>
|
|
<MyComponent info="13" />
|
|
</div>
|
|
```
|
|
|
|
```js
|
|
class ParentComponent extends owl.Component {
|
|
components = { MyComponent: MyComponent};
|
|
...
|
|
}
|
|
```
|
|
|
|
In this example, the `ParentComponent`'s template creates a component `MyComponent` just
|
|
after the span. The `info` key will be added to the subcomponent's `props`. Each
|
|
`props` is a string which represents a javascript (QWeb) expression, so it is
|
|
dynamic. If it is necessary to give a string, this can be done by quoting it:
|
|
`someString="'somevalue'"`.
|
|
|
|
Note that the rendering context for the template is the component itself. This means
|
|
that the template can access `state`, `props`, `env`, or any methods defined in the component.
|
|
|
|
```xml
|
|
<div t-name="ParentComponent">
|
|
<ChildComponent count="state.val" />
|
|
</div>
|
|
```
|
|
|
|
```js
|
|
class ParentComponent {
|
|
components = { ChildComponent };
|
|
state = { val: 4 };
|
|
}
|
|
```
|
|
|
|
Whenever the template is rendered, it will automatically create the subcomponent
|
|
`ChildComponent` at the correct place. It needs to find the reference to the
|
|
actual component class in the special `components` key, or the class registered in
|
|
QWeb's global registry (see `register` function of QWeb). It first looks inside
|
|
the local `components` key, then fallbacks on the global registry.
|
|
|
|
_Props_: In this example, the child component will receive the object `{count: 4}` in its
|
|
constructor. This will be assigned to the `props` variable, which can be accessed
|
|
on the component (and also, in the template). Whenever the state is updated, then
|
|
the sub component will also be updated automatically.
|
|
|
|
Note that there are some restrictions on prop names: `class`, `style` and any
|
|
string which starts with `t-` are not allowed.
|
|
|
|
The `t-component` directive can also be used to accept dynamic values with string interpolation (like the [`t-attf-`](qweb.md#dynamic-attributes) directive):
|
|
|
|
```xml
|
|
<div t-name="ParentComponent">
|
|
<t t-component="ChildComponent{{id}}" />
|
|
</div>
|
|
```
|
|
|
|
```js
|
|
class ParentComponent {
|
|
components = { ChildComponent1, ChildComponent2 };
|
|
state = { id: 1 };
|
|
}
|
|
```
|
|
|
|
**CSS and style:** there is some specific support to allow the parent to declare
|
|
additional css classes or style for the sub component: css declared in `class`, `style`, `t-att-class` or `t-att-style` will be added to the
|
|
root component element.
|
|
|
|
```xml
|
|
<div t-name="ParentComponent">
|
|
<MyComponent class="someClass" style="font-weight:bold;" info="13" />
|
|
</div>
|
|
```
|
|
|
|
Warning: there is a small caveat with dynamic class attributes: since Owl needs
|
|
to be able to add/remove proper classes whenever necessary, it needs to be aware
|
|
of the possible classes. Otherwise, it will not be able to make the difference
|
|
between a valid css class added by the component, or some custom code, and a
|
|
class that need to be removed. This is why we only support the explicit syntax
|
|
with a class object:
|
|
|
|
```xml
|
|
<MyComponent t-att-class="{a: state.flagA, b: state.flagB}" />
|
|
```
|
|
|
|
### Event Handling
|
|
|
|
In a component's template, it is useful to be able to register handlers on some
|
|
elements to some specific events. This is what makes a template _alive_. There
|
|
are four different use cases.
|
|
|
|
1. Register an event handler on a DOM node (_pure_ DOM event)
|
|
2. Register an event handler on a component (_pure_ DOM event)
|
|
3. Register an event handler on a DOM node (_business_ DOM event)
|
|
4. Register an event handler on a component (_business_ DOM event)
|
|
|
|
A _pure_ DOM event is directly triggered by a user interaction (e.g. a `click`).
|
|
|
|
```xml
|
|
<button t-on-click="someMethod">Do something</button>
|
|
```
|
|
|
|
This will be roughly translated in javascript like this:
|
|
|
|
```js
|
|
button.addEventListener("click", component.someMethod.bind(component));
|
|
```
|
|
|
|
The suffix (`click` in this example) is simply the name of the actual DOM
|
|
event.
|
|
|
|
A _business_ DOM event is triggered by a call to `trigger` on a component.
|
|
|
|
```xml
|
|
<MyComponent t-on-menu-loaded="someMethod" />
|
|
```
|
|
|
|
```js
|
|
class MyComponent {
|
|
someWhere() {
|
|
const payload = ...;
|
|
this.trigger('menu-loaded', payload);
|
|
}
|
|
}
|
|
```
|
|
|
|
The call to `trigger` generates a [_CustomEvent_](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events)
|
|
of type `menu-loaded` and dispatches it on the component's DOM element
|
|
(`this.el`). The event bubbles and is cancelable. The parent component listening
|
|
to event `menu-loaded` will receive the payload in its `someMethod` handler
|
|
(in the `detail` property of the event), whenever the event is triggered.
|
|
|
|
```js
|
|
class ParentComponent {
|
|
someMethod(ev) {
|
|
const payload = ev.detail;
|
|
...
|
|
}
|
|
}
|
|
```
|
|
|
|
By convention, we use KebabCase for the name of _business_ events.
|
|
|
|
In order to remove the DOM event details from the event handlers (like calls to
|
|
`event.preventDefault`) and let them focus on data logic, _modifiers_ can be
|
|
specified as additional suffixes of the `t-on` directive.
|
|
|
|
| Modifier | Description |
|
|
| ---------- | ----------------------------------------------------------------- |
|
|
| `.stop` | calls `event.stopPropagation()` before calling the method |
|
|
| `.prevent` | calls `event.preventDefault()` before calling the method |
|
|
| `.self` | calls the method only if the `event.target` is the element itself |
|
|
|
|
```xml
|
|
<button t-on-click.stop="someMethod">Do something</button>
|
|
```
|
|
|
|
Note that modifiers can be combined (ex: `t-on-click.stop.prevent`), and that
|
|
the order may matter. For instance `t-on-click.prevent.self` will prevent all
|
|
clicks while `t-on-click.self.prevent` will only prevent clicks on the element
|
|
itself.
|
|
|
|
The `t-on` directive also allows to prebind some arguments. For example,
|
|
|
|
```xml
|
|
<button t-on-click="someMethod(expr)">Do something</button>
|
|
```
|
|
|
|
Here, `expr` is a valid Owl expression, so it could be `true` or some variable
|
|
from the rendering context.
|
|
|
|
### Form Input Bindings
|
|
|
|
It is very common to need to be able to read the value out of an html `input` (or
|
|
`textarea`, or `select`) in order to use it (note: it does not need to be in a
|
|
form!). A possible way to do this is to do it by hand:
|
|
|
|
```js
|
|
class Form extends owl.Component {
|
|
state = { text: "" };
|
|
|
|
_updateInputValue(event) {
|
|
this.state.text = event.target.value;
|
|
}
|
|
}
|
|
```
|
|
|
|
```xml
|
|
<div>
|
|
<input t-on-input="_updateInputValue" />
|
|
<span t-esc="state.text" />
|
|
</div>
|
|
```
|
|
|
|
This works. However, this requires a little bit of _plumbing_ code. Also, the
|
|
plumbing code is slightly different if you need to interact with a checkbox,
|
|
or with radio buttons, or with select tags.
|
|
|
|
To help with this situation, Owl has a builtin directive `t-model`: its value
|
|
is the (top-level) name in the state object. With the `t-model` directive, we
|
|
can write a shorter code, equivalent to the previous example:
|
|
|
|
```js
|
|
class Form extends owl.Component {
|
|
state = { text: "" };
|
|
}
|
|
```
|
|
|
|
```xml
|
|
<div>
|
|
<input t-model="text" />
|
|
<span t-esc="state.text" />
|
|
</div>
|
|
```
|
|
|
|
The `t-model` directive works with `<input>`, `<input type="checkbox">`,
|
|
`<input type="radio">`, `<textarea>` and `<select>`:
|
|
|
|
```xml
|
|
<div>
|
|
<div>Text in an input: <input t-model="someVal"/></div>
|
|
<div>Textarea: <textarea t-model="otherVal"/></div>
|
|
<div>Boolean value: <input type="checkbox" t-model="someFlag"/></div>
|
|
<div>Selection:
|
|
<select t-model="color">
|
|
<option value="">Select a color</option>
|
|
<option value="red">Red</option>
|
|
<option value="blue">Blue</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
Selection with radio buttons:
|
|
<span>
|
|
<input type="radio" name="color" id="red" value="red" t-model="color"/>
|
|
<label for="red">Red</label>
|
|
</span>
|
|
<span>
|
|
<input type="radio" name="color" id="blue" value="blue" t-model="color" />
|
|
<label for="blue">Blue</label>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
Like event handling, the `t-model` directive accepts some modifiers:
|
|
|
|
| Modifier | Description |
|
|
| --------- | -------------------------------------------------------------------- |
|
|
| `.lazy` | update the value on the `change` event (default is on `input` event) |
|
|
| `.number` | tries to parse the value to a number (using `parseFloat`) |
|
|
| `.trim` | trim the resulting value |
|
|
|
|
For example:
|
|
|
|
```xml
|
|
<input t-model.lazy="someVal" />
|
|
```
|
|
|
|
These modifiers can be combined. For instance, `t-model.lazy.number` will only
|
|
update a number whenever the change is done.
|
|
|
|
Note: the online playground has an example to show how it works.
|
|
|
|
### `t-key` Directive
|
|
|
|
Even though Owl tries to be as declarative as possible, some DOM state is still
|
|
locked inside the DOM: for example, the scrolling state, the current user selection,
|
|
the focused element or the state of an input. This is why we use a virtual dom
|
|
algorithm to keep the actual DOM node as much as possible. However, this is
|
|
sometimes not enough, and we need to help Owl decide if an element is actually
|
|
the same, or is different. The `t-key` directive is used to give an identity to an element.
|
|
|
|
There are three main use cases:
|
|
|
|
- _elements in a list_:
|
|
|
|
```xml
|
|
<span t-foreach="todos" t-as="todo" t-key="todo.id">
|
|
<t t-esc="todo.text" />
|
|
</span>
|
|
```
|
|
|
|
- _`t-if`/`t-else`_
|
|
|
|
- _animations_: give a different identity to a component. Ex: thread id with
|
|
animations on add/remove message.
|
|
|
|
### `t-mounted` Directive
|
|
|
|
The `t-mounted` directive allows to register a callback to execute whenever the node
|
|
is inserted into the DOM.
|
|
|
|
```xml
|
|
<div><input t-ref="someInput" t-mounted="focusMe"/></div>
|
|
```
|
|
|
|
```js
|
|
class MyComponent extends owl.Component {
|
|
...
|
|
focusMe() {
|
|
this.refs.someInput.focus();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Semantics
|
|
|
|
We give here an informal description of the way components are created/updated
|
|
in an application. Here, ordered lists describe actions that are executed
|
|
sequentially, bullet lists describe actions that are executed in parallel.
|
|
|
|
**Scenario 1: initial rendering** Imagine we want to render the following component tree:
|
|
|
|
```
|
|
A
|
|
/ \
|
|
B C
|
|
/ \
|
|
D E
|
|
```
|
|
|
|
Here is what happen whenever we mount the root
|
|
component (with some code like `app.mount(document.body)`).
|
|
|
|
1. `willStart` is called on `A`
|
|
|
|
2. when it is done, template `A` is rendered.
|
|
|
|
- component `B` is created
|
|
1. `willStart` is called on `B`
|
|
2. template `B` is rendered
|
|
- component `C` is created
|
|
1. `willStart` is called on `C`
|
|
2. template `C` is rendered
|
|
- component `D` is created
|
|
1. `willStart` is called on `D`
|
|
2. template `D` is rendered
|
|
- component `E` is created
|
|
1. `willStart` is called on `E`
|
|
2. template `E` is rendered
|
|
|
|
3. component `A` is patched into a detached DOM element. This will create the actual
|
|
component `A` DOM structure. The patching process will cause recursively the
|
|
patching of the `B`, `C`, `D` and `E` DOM trees. (so the actual full DOM tree is created
|
|
in one pass)
|
|
|
|
4. the component `A` root element is actually appended to `document.body`
|
|
|
|
5. The method `mounted` is called recursively on all components in the following
|
|
order: `B`, `D`, `E`, `C`, `A`.
|
|
|
|
**Scenario 2: rerendering a component**. Now, let's assume that the user clicked on some
|
|
button in `C`, and this results in a state update, which is supposed to:
|
|
|
|
- update `D`,
|
|
- remove `E`,
|
|
- add new component `F`.
|
|
|
|
So, the component tree should look like this:
|
|
|
|
```
|
|
A
|
|
/ \
|
|
B C
|
|
/ \
|
|
D F
|
|
```
|
|
|
|
Here is what Owl will do:
|
|
|
|
1. because of a state change, the method `render` is called on `C`
|
|
2. template `C` is rendered again
|
|
|
|
- component `D` is updated:
|
|
1. hook `willUpdateProps` is called on `D` (async)
|
|
2. template `D` is rerendered
|
|
- component `F` is created:
|
|
1. hook `willStart` is called on `E` (async)
|
|
2. template `F` is rendered
|
|
|
|
3. `willPatch` hooks are called recursively on components `C`, `D` (not on `F`,
|
|
because it is not mounted yet)
|
|
|
|
4. component `C` is patched, which will cause recursively:
|
|
|
|
2. `willUnmount` hook on `E`, then destruction of `E`,
|
|
3. (initial) patching of `F`, then hook `mounted` is called on `F`
|
|
|
|
5. patching of `D`
|
|
|
|
6. `patched` hooks are called on `D`, `C`
|
|
|
|
### Props Validation
|
|
|
|
As an application becomes complex, it may be quite unsafe to define props in an informal way. This leads to two issues:
|
|
|
|
- hard to tell how a component should be used, by looking at its code.
|
|
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parent.
|
|
|
|
A props type system would solve both issues, by describing the types and shapes
|
|
of the props. Here is how it works in Owl:
|
|
|
|
- `props` key is a static key (so, different from `this.props` in a component instance)
|
|
- it is optional: it is ok for a component to not define a `props` key.
|
|
- props are validated whenever a component is created/updated
|
|
- props are only validated in `dev` mode (see [tooling page](tooling.md#development-mode))
|
|
- if a key does not match the description, an error is thrown
|
|
- it only validates keys defined in (static) `props`. Additional keys in (component) `props` are not validated.
|
|
|
|
For example:
|
|
|
|
```js
|
|
class ComponentA extends owl.Component {
|
|
static props = ['id', 'url'];
|
|
|
|
...
|
|
}
|
|
|
|
class ComponentB extends owl.Component {
|
|
static props = {
|
|
count: {type: Number},
|
|
messages: {
|
|
type: Array,
|
|
element: {type: Object, shape: {id: Boolean, text: 'string' }
|
|
},
|
|
date: Date,
|
|
combinedVal: [Number, Boolean]
|
|
};
|
|
|
|
...
|
|
}
|
|
```
|
|
|
|
- it is an object or a list of strings
|
|
- a list of strings is a simplified props definition, which only lists the name
|
|
of the props.
|
|
- all props are by default required, unless they are defined with `optional: true`
|
|
(in that case, validation is only done if there is a value)
|
|
- valid types are: `Number, String, Boolean, Object, Array, Date, Function`, and all
|
|
constructor functions (so, if you have a `Person` class, it can be used as a type)
|
|
- arrays are homogeneous (all elements have the same type/shape)
|
|
|
|
For each key, a `prop` definition is either a constructor, a list of constructors, or an object:
|
|
|
|
- a constructor: this should describe the type, for example: `id: Number` describe
|
|
the props `id` as a number
|
|
- a list of constructors. In that case, this means that we allow more than one
|
|
type. For example, `id: [Number, String]` means that `id` can be either a string
|
|
or a number.
|
|
- an object. This makes it possible to have more expressive definition. The following sub keys are then allowed:
|
|
- `type`: the main type of the prop being validated
|
|
- `element`: if the type was `Array`, then the `element` key describes the type of each element in the array. It is optional (not set means that we only validate the array, not its elements),
|
|
- `shape`: if the type was `Object`, then the `shape` key describes the interface of the object. It is optional (not set means that we only validate the object, not its elements)
|
|
|
|
Examples:
|
|
|
|
```js
|
|
// only the existence of those 3 keys is documented
|
|
static props = ['message', 'id', 'date'];
|
|
```
|
|
|
|
```js
|
|
static props = {
|
|
messageIds: {type: Array, element: Number}, // list of number
|
|
otherArr: {type: Array}, // just array. no validation is made on sub elements
|
|
otherArr2: Array, // same as otherArr
|
|
someObj: {type: Object}, // just an object, no internal validation
|
|
someObj2: {
|
|
type: Object,
|
|
shape: {
|
|
id: Number,
|
|
name: {type: String, optional: true},
|
|
url: String
|
|
]}, // object, with keys id (number), name (string, optional) and url (string)
|
|
someFlag: Boolean, // a boolean, mandatory (even if `false`)
|
|
someVal: [Boolean, Date] // either a boolean or a date
|
|
};
|
|
```
|
|
|
|
### 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:
|
|
|
|
```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
|
|
inside the special `refs` variable:
|
|
|
|
```js
|
|
this.refs.someDiv;
|
|
this.refs.someComponent;
|
|
```
|
|
|
|
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,
|
|
|
|
```xml
|
|
<div t-ref="component_{{id}}"/>
|
|
```
|
|
|
|
```js
|
|
this.refs.component_44;
|
|
```
|
|
|
|
### Slots
|
|
|
|
To make generic components, it is useful to be able for a parent component to _inject_
|
|
some sub template, but still be the owner. For example, a generic dialog component
|
|
will need to render some content, some footer, but with the parent as the
|
|
rendering context.
|
|
|
|
This is what _slots_ are for.
|
|
|
|
```xml
|
|
<div t-name="Dialog" class="modal">
|
|
<div class="modal-title"><t t-esc="props.title"/></div>
|
|
<div class="modal-content">
|
|
<t t-slot="content"/>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<t t-slot="footer"/>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
Slots are defined by the caller, with the `t-set` directive:
|
|
|
|
```xml
|
|
<div t-name="SomeComponent">
|
|
<div>some component</div>
|
|
<Dialog title="Some Dialog">
|
|
<t t-set="content">
|
|
<div>hey</div>
|
|
</t>
|
|
<t t-set="footer">
|
|
<button t-on-click="doSomething">ok</button>
|
|
</t>
|
|
</Dialog>
|
|
</div>
|
|
```
|
|
|
|
In this example, the component `Dialog` will render the slots `content` and `footer`
|
|
with its parent as rendering context. This means that clicking on the button
|
|
will execute the `doSomething` method on the parent, not on the dialog.
|
|
|
|
Default slot: the first element inside the component which is not a named slot will
|
|
be considered the `default` slot. For example:
|
|
|
|
```xml
|
|
<div t-name="Parent">
|
|
<Child>
|
|
<span>some content</span>
|
|
</Child>
|
|
</div>
|
|
|
|
<div t-name="Child">
|
|
<t t-slot="default"/>
|
|
</div>
|
|
```
|
|
|
|
### Asynchronous Rendering
|
|
|
|
Working with asynchronous code always adds a lot of complexity to a system. Whenever
|
|
different parts of a system are active at the same time, one needs to think
|
|
carefully about all possible interactions. Clearly, this is also true for Owl
|
|
components.
|
|
|
|
There are two different common problems with Owl asynchronous rendering model:
|
|
|
|
- any component can delay the rendering (initial and subsequent) of the whole
|
|
application
|
|
- for a given component, there are two independant situations that will trigger an
|
|
asynchronous rerendering: a change in the state, or a change in the props.
|
|
These changes may be done at different times, and Owl has no way of knowing
|
|
how to reconcile the resulting renderings.
|
|
|
|
Here are a few tips on how to work with asynchronous components:
|
|
|
|
1. Minimize the use of asynchronous components!
|
|
2. Maybe move the asynchronous logic in a store, which then triggers (mostly)
|
|
synchronous renderings
|
|
3. Lazy loading external libraries is a good use case for async rendering. This
|
|
is mostly fine, because we can assume that it will only takes a fraction of a
|
|
second, and only once (see `owl.utils.loadJS`)
|
|
4. For all the other cases, the `t-asyncroot` directive (to use alongside
|
|
`t-component`) is there to help you. When this directive is met, a new rendering
|
|
sub tree is created, such that the rendering of that component (and its
|
|
children) is not tied to the rendering of the rest of the interface. It can
|
|
be used on an asynchronous component, to prevent it from delaying the
|
|
rendering of the whole interface, or on a synchronous one, such that its
|
|
rendering isn't delayed by other (asynchronous) components. Note that this
|
|
directive has no effect on the first rendering, but only on subsequent ones
|
|
(triggered by state or props changes).
|
|
|
|
```xml
|
|
<div t-name="ParentComponent">
|
|
<SyncChild />
|
|
<AsyncChild t-asyncroot="1"/>
|
|
</div>
|
|
```
|