2019-04-24 11:54:26 +02:00
# 🦉 OWL Component 🦉
2019-03-14 12:51:19 +01:00
2019-04-24 11:54:26 +02:00
## Content
- [Overview ](#overview )
- [Example ](#example )
- [Reference ](#reference )
- [Properties ](#properties )
2019-05-28 09:54:25 +02:00
- [Static Properties ](#static-properties )
2019-04-24 11:54:26 +02:00
- [Methods ](#methods )
- [Lifecycle ](#lifecycle )
2019-06-22 14:44:09 +02:00
- [Root Component ](#root-component )
- [Environment ](#environment )
2019-06-04 13:16:50 +02:00
- [Composition ](#composition )
- [Event Handling ](#event-handling )
2019-06-14 15:45:16 +02:00
- [Form Input Bindings ](#form-input-bindings )
2019-06-14 15:24:04 +02:00
- [`t-key` Directive ](#t-key-directive )
- [`t-mounted` Directive ](#t-mounted-directive )
2019-06-13 16:02:13 +02:00
- [Semantics ](#semantics )
2019-05-28 09:54:25 +02:00
- [Props Validation ](#props-validation )
2019-06-14 15:24:04 +02:00
- [References ](#references )
2019-06-11 15:02:10 +02:00
- [Slots ](#slots )
2019-06-14 15:24:04 +02:00
- [Asynchronous Rendering ](#asynchronous-rendering )
2019-07-09 14:33:56 +02:00
- [Error Handling ](#error-handling )
2019-04-24 11:54:26 +02:00
## Overview
OWL components are the building blocks for user interface. They are designed to be:
2019-04-23 15:26:00 +02:00
1. **declarative: ** the user interface should be described in term of the state
of the application, not as a sequence of imperative steps.
2019-06-20 09:42:33 +02:00
2. **composable: ** each component can seamlessly be created in a parent component by
a simple tag or directive in its template.
2019-04-23 15:26:00 +02:00
3. **asynchronous rendering: ** the framework will transparently wait for each
2019-06-20 09:42:33 +02:00
sub components to be ready before applying the rendering. It uses native promises
2019-04-23 15:26:00 +02:00
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.
2019-04-24 11:54:26 +02:00
OWL components are defined as a subclass of Component. The rendering is
2019-05-15 11:15:08 +02:00
exclusively done by a [QWeb ](qweb.md ) template (which needs to be preloaded in QWeb).
2019-05-11 12:11:00 +02:00
Rendering a component generates a virtual dom representation
2019-06-20 09:42:33 +02:00
of the component, which is then patched to the DOM, in order to apply the changes in an efficient way.
2019-04-25 15:28:20 +02:00
2019-03-14 14:23:27 +01:00
2019-04-24 11:54:26 +02:00
## Example
2019-03-14 14:23:27 +01:00
2019-04-24 11:54:26 +02:00
Let us have a look at a simple component:
2019-03-14 14:23:27 +01:00
2019-04-24 11:54:26 +02:00
``` javascript
2019-09-24 14:06:53 +02:00
const { useState } = owl . hooks ;
2019-04-24 11:54:26 +02:00
class ClickCounter extends owl . Component {
2019-09-24 14:06:53 +02:00
state = useState ( { value : 0 } ) ;
2019-03-14 14:23:27 +01:00
2019-04-24 11:54:26 +02:00
increment ( ) {
this . state . value ++ ;
2019-03-14 14:23:27 +01:00
}
}
```
2019-04-05 23:44:07 +02:00
``` xml
2019-05-14 23:34:28 +02:00
<button t-name= "ClickCounter" t-on-click= "increment" >
2019-04-24 11:54:26 +02:00
Click Me! [<t t-esc= "state.value" /> ]
</button>
2019-04-05 23:44:07 +02:00
```
2019-04-24 11:54:26 +02:00
Note that this code is written in ESNext style, so it will only run on the
latest browsers without a transpilation step.
2019-04-05 23:44:07 +02:00
2019-04-24 11:54:26 +02:00
This example show how a component should be defined: it simply subclasses the
2019-09-12 09:45:32 +02:00
Component class. If no static `template` key is defined, then
2019-05-14 23:34:28 +02:00
Owl will use the component's name as template name. Here,
2019-09-24 14:06:53 +02:00
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.
## Reactive system
OWL components can be made reactive by observing some part of their state. See the [hooks ](hooks.md ) section for more details.
The main idea is that the `useState` hook generate a proxy version of an object
(this is done by an [observer ](observer.md )), which allows the component to
react to any change.
``` javascript
const { useState } = owl . hooks ;
class SomeComponent extends owl . Component {
state = useState ( { a : 0 , b : 1 } ) ;
}
```
Note that there is an important limitation: hooks need to be called in the
constructor.
2019-04-24 11:54:26 +02:00
## Reference
2019-06-20 09:42:33 +02:00
An Owl component is a small class which represent a component or some UI element.
2019-04-24 11:54:26 +02:00
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.
2019-05-14 23:34:28 +02:00
Be aware that the name of the component may be significant: if a component does
2019-05-15 11:15:08 +02:00
not define a `template` key, then Owl will lookup in QWeb to
2019-05-14 23:34:28 +02:00
find a template with the component name (or one of its ancestor).
2019-04-24 11:54:26 +02:00
### 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.
- **`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.
2019-05-28 09:54:25 +02:00
As such, it should not ever be modified by the component!!
2019-04-24 11:54:26 +02:00
- **`refs` ** (Object): the `refs` object contains all references to sub DOM nodes
2019-06-20 09:42:33 +02:00
or sub components defined by a `t-ref` directive in the component's template.
2019-04-24 11:54:26 +02:00
2019-05-28 09:54:25 +02:00
### Static Properties
2019-09-12 09:45:32 +02:00
- **`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
make it easy to define an inline template.
* **`components` ** (Object, optional): if given, this is an object that contains
2019-09-10 22:37:08 +02:00
the classes of any sub components needed by the template. This is the main way
used by Owl to be able to create sub components.
```js
class ParentComponent extends owl.Component {
2019-09-11 14:01:49 +02:00
static components = { SubComponent };
2019-09-10 22:37:08 +02:00
}
` ``
2019-09-12 09:45:32 +02:00
* **` props`** (Object, optional): if given, this is an object that describes the
2019-05-28 09:54:25 +02:00
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.
2019-06-28 12:59:27 +02:00
` ``js
class Counter extends owl.Component {
2019-06-28 15:48:33 +02:00
static props = {
initialValue: Number,
optional: true
};
2019-06-28 12:59:27 +02:00
}
` ``
2019-09-12 09:45:32 +02:00
- **` defaultProps`** (Object, optional): if given, this object define default
2019-05-28 09:54:25 +02:00
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.
2019-06-28 12:59:27 +02:00
` ``js
class Counter extends owl.Component {
2019-06-28 15:48:33 +02:00
static defaultProps = {
initialValue: 0
};
2019-06-28 12:59:27 +02:00
}
` ``
2019-04-24 11:54:26 +02:00
### Methods
2019-06-24 13:02:12 +02:00
We explain here all the public methods of the ` Component` class.
2019-08-28 10:02:38 +02:00
- **` mount(target, renderBeforeRemount=false)`** (async): this is the main way a
component 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.
2019-04-24 11:54:26 +02:00
2019-08-28 10:02:38 +02:00
The ` renderBeforeRemount` argument is useful when a component is unmounted and remounted.
In that case, we may want to rerender the component _before_ it is remounted, if
we know that its state (or something in the environment, or ...) has changed.
In that case, it should simply set to ` true`.
2019-08-22 13:32:53 +02:00
2019-04-24 11:54:26 +02:00
- **` 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.
2019-04-25 15:28:20 +02:00
Note that the render method is asynchronous, so one cannot observe the updated
DOM in the same stack frame.
2019-04-24 11:54:26 +02:00
- **` shouldUpdate(nextProps)`**: this method is called each time a component's props
2019-06-20 09:42:33 +02:00
are updated. It returns a boolean, which indicates if the component should
2019-04-24 11:54:26 +02:00
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.
2019-06-24 13:02:12 +02:00
Obviously, these methods are reserved for Owl, and should not be used by Owl
2019-06-28 15:48:33 +02:00
users, unless they want to override them. Also, Owl reserves all method names
2019-06-24 13:02:12 +02:00
starting with ` __`, in order to prevent possible future conflicts with user code
whenever Owl needs to change.
2019-04-24 11:54:26 +02:00
### 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:
2019-07-09 14:33:56 +02:00
| 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 |
| **[catchError](#catcherrorerror)** | catch errors (see [error handling section](#error-handling)) |
2019-04-05 23:44:07 +02:00
2019-04-29 12:07:20 +02:00
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
2019-05-08 11:59:40 +02:00
called by the owl framework whenever it is required.
2019-04-05 23:44:07 +02:00
2019-04-24 11:54:26 +02:00
#### ` constructor(parent, props)`
2019-04-05 23:44:07 +02:00
2019-04-24 11:54:26 +02:00
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.
2019-04-05 23:44:07 +02:00
2019-04-24 11:54:26 +02:00
This is usually where you would set the initial state and the template of the
component.
2019-04-05 23:44:07 +02:00
2019-04-24 11:54:26 +02:00
` ``javascript
constructor(parent, props) {
super(parent, props);
2019-09-24 14:06:53 +02:00
this.state = useState({someValue: true});
2019-04-24 11:54:26 +02:00
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 {
2019-09-24 14:06:53 +02:00
state = useState({ value: 0 });
2019-04-24 11:54:26 +02:00
...
}
` ``
#### ` willStart()`
willStart is an asynchronous hook that can be implemented to
perform some action before the initial rendering of a component.
2019-04-05 23:44:07 +02:00
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)
2019-06-20 09:42:33 +02:00
before the component is rendered. Another use case is to load data from a server.
2019-04-05 23:44:07 +02:00
2019-04-24 11:54:26 +02:00
` ``javascript
2019-04-29 11:21:02 +02:00
async willStart() {
await owl.utils.loadJS("my-awesome-lib.js");
2019-04-24 11:54:26 +02:00
}
` ``
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
2019-04-05 23:44:07 +02:00
fast as possible.
2019-04-24 11:54:26 +02:00
#### ` 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
2019-04-05 23:44:07 +02:00
DOM, if the component needs to perform some measure for example.
2019-04-24 11:54:26 +02:00
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.
2019-04-05 23:44:07 +02:00
2019-09-24 14:06:53 +02:00
It is 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.
2019-04-24 11:54:26 +02:00
#### ` willUpdateProps(nextProps)`
2019-04-09 13:51:04 +02:00
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).
2019-04-24 11:54:26 +02:00
` ``javascript
willUpdateProps(nextProps) {
return this.loadData({id: nextProps.id});
}
` ``
2019-04-09 13:51:04 +02:00
This hook is not called during the first render (but willStart is called
and performs a similar job).
2019-04-24 11:54:26 +02:00
#### ` willPatch()`
2019-04-05 23:44:07 +02:00
The willPatch hook is called just before the DOM patching process starts.
2019-04-24 11:54:26 +02:00
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.
2019-09-24 14:06:53 +02:00
Note that modifying the state is not allowed here. This method is called just
2019-04-24 11:54:26 +02:00
before an actual DOM patch, and is only intended to be used to save some local
2019-06-20 09:42:33 +02:00
DOM state. Also, it will not be called if the component is not in the DOM (this can
happen with components with ` t-keepalive`).
2019-04-05 23:44:07 +02:00
2019-04-25 14:46:04 +02:00
The return value of this method will be given as the first argument of the
corresponding ` patched` call.
#### ` patched(snapshot)`
2019-04-05 23:44:07 +02:00
2019-04-24 11:54:26 +02:00
This hook is called whenever a component did actually update its DOM (most
likely via a change in its state/props or environment).
2019-04-05 23:44:07 +02:00
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
2019-06-20 09:42:33 +02:00
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`).
2019-04-05 23:44:07 +02:00
2019-04-25 14:46:04 +02:00
The ` snapshot` parameter is the result of the previous ` willPatch` call.
2019-06-20 09:42:33 +02:00
Updating the compoent state in this hook is possible, but not encouraged.
2019-04-05 23:44:07 +02:00
One need to be careful, because updates here will cause rerender, which in
2019-04-09 13:51:04 +02:00
turn will cause other calls to patched. So, we need to be particularly
2019-04-05 23:44:07 +02:00
careful at avoiding endless cycles.
2019-04-24 11:54:26 +02:00
#### ` willUnmount()`
2019-04-05 23:44:07 +02:00
2019-04-09 12:57:34 +02:00
willUnmount is a hook that is called each time just before a component is unmounted from
2019-04-05 23:44:07 +02:00
the DOM. This is a good place to remove some listeners, for example.
2019-04-24 11:54:26 +02:00
` ``javascript
mounted() {
this.env.bus.on('someevent', this, this.doSomething);
}
willUnmount() {
this.env.bus.off('someevent', this, this.doSomething);
}
` ``
2019-05-04 13:50:43 +02:00
This is the opposite method of ` mounted`.
2019-05-08 11:59:40 +02:00
2019-07-09 14:33:56 +02:00
#### ` catchError(error)`
The ` catchError` method is useful when we need to intercept and properly react
to (rendering) errors that occur in some sub components. See the section on
[error handling](#error-handling)
2019-06-22 14:44:09 +02:00
### 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
2019-06-28 15:48:33 +02:00
user interface. There is however a mechanism to force root widgets to rerender
2019-09-24 14:06:53 +02:00
themselves whenever the environment is modified: one only needs to call the
` forceUpdate` method on the QWeb instance. For example, a responsive environment
could be done like this:
2019-06-22 14:44:09 +02:00
` ``js
function setupResponsivePlugin(env) {
2019-06-28 15:48:33 +02:00
const isMobile = () => window.innerWidth <= 768;
env.isMobile = isMobile();
const updateEnv = owl.utils.debounce(() => {
if (env.isMobile !== isMobile()) {
env.isMobile = !env.isMobile;
2019-09-24 14:06:53 +02:00
env.qweb.forceUpdate();
2019-06-28 15:48:33 +02:00
}
}, 15);
window.addEventListener("resize", updateEnv);
2019-06-22 14:44:09 +02:00
}
` ``
2019-06-14 10:17:13 +02:00
### Composition
2019-06-04 13:16:50 +02:00
2019-06-18 09:24:52 +02:00
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.
2019-06-04 13:16:50 +02:00
` ``xml
2019-06-20 09:42:33 +02:00
<div t-name="ParentComponent">
2019-06-04 13:16:50 +02:00
<span>some text</span>
2019-06-20 09:42:33 +02:00
<MyComponent info="13" />
2019-06-04 13:16:50 +02:00
</div>
` ``
` ``js
2019-06-20 09:42:33 +02:00
class ParentComponent extends owl.Component {
2019-09-10 22:37:08 +02:00
static components = { MyComponent: MyComponent};
2019-06-04 13:16:50 +02:00
...
}
` ``
2019-06-20 09:42:33 +02:00
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
2019-06-18 09:24:52 +02:00
` props` is a string which represents a javascript (QWeb) expression, so it is
2019-06-04 13:16:50 +02:00
dynamic. If it is necessary to give a string, this can be done by quoting it:
2019-06-22 14:44:09 +02:00
` someString="'somevalue'"`.
2019-06-04 13:16:50 +02:00
2019-06-20 09:42:33 +02:00
Note that the rendering context for the template is the component itself. This means
2019-09-24 14:06:53 +02:00
that the template can access ` state` (if it exists), ` props`, ` env`, or any
methods defined in the component.
2019-06-04 13:16:50 +02:00
` ``xml
2019-06-20 09:42:33 +02:00
<div t-name="ParentComponent">
<ChildComponent count="state.val" />
2019-06-04 13:16:50 +02:00
</div>
` ``
` ``js
2019-06-20 09:42:33 +02:00
class ParentComponent {
2019-09-10 22:37:08 +02:00
static components = { ChildComponent };
2019-09-24 14:06:53 +02:00
state = useState({ val: 4 });
2019-06-04 13:16:50 +02:00
}
` ``
2019-06-20 09:42:33 +02:00
Whenever the template is rendered, it will automatically create the subcomponent
` ChildComponent` at the correct place. It needs to find the reference to the
2019-09-10 22:37:08 +02:00
actual component class in the special static ` components` key, or the class registered in
2019-06-04 13:16:50 +02:00
QWeb's global registry (see ` register` function of QWeb). It first looks inside
2019-09-10 22:37:08 +02:00
the static ` components` key, then fallbacks on the global registry.
2019-06-04 13:16:50 +02:00
2019-06-20 09:42:33 +02:00
_Props_: In this example, the child component will receive the object ` {count: 4}` in its
2019-06-04 13:16:50 +02:00
constructor. This will be assigned to the ` props` variable, which can be accessed
2019-06-20 09:42:33 +02:00
on the component (and also, in the template). Whenever the state is updated, then
the sub component will also be updated automatically.
2019-06-04 13:16:50 +02:00
Note that there are some restrictions on prop names: ` class`, ` style` and any
string which starts with ` t-` are not allowed.
2019-09-11 14:01:49 +02:00
It is not common, but sometimes we need a dynamic component name and/or dynamic props. In this case,
the ` t-component` directive can also be used to accept dynamic values with string interpolation (like the [` t-attf-`](qweb.md#dynamic-attributes) directive):
2019-06-04 13:16:50 +02:00
` ``xml
2019-06-20 09:42:33 +02:00
<div t-name="ParentComponent">
<t t-component="ChildComponent{{id}}" />
2019-06-04 13:16:50 +02:00
</div>
` ``
` ``js
2019-06-20 09:42:33 +02:00
class ParentComponent {
2019-09-10 22:37:08 +02:00
static components = { ChildComponent1, ChildComponent2 };
2019-06-04 13:16:50 +02:00
state = { id: 1 };
}
` ``
2019-09-11 14:01:49 +02:00
And the ` t-props` directive can be used to specify totally dynamic props:
` ``xml
<div t-name="ParentComponent">
<Child t-props="some.obj"/>
</div>
` ``
` ``js
class ParentComponent {
static components = { Child };
some = { obj: { a: 1, b: 2 } };
}
` ``
2019-09-21 09:28:56 +02:00
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
class that will be used to create the component:
` ``js
class A extends Component<any, any, any> {
static template = xml` <span>child a</span>`;
}
class B extends Component<any, any, any> {
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"/>`;
state = { child: "a" };
get myComponent() {
return this.state.child === "a" ? A : B;
}
}
` ``
In this example, the component ` App` selects dynamically the concrete sub
component class.
2019-06-04 13:16:50 +02:00
**CSS and style:** there is some specific support to allow the parent to declare
2019-06-20 09:42:33 +02:00
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.
2019-06-04 13:16:50 +02:00
` ``xml
2019-06-20 09:42:33 +02:00
<div t-name="ParentComponent">
<MyComponent class="someClass" style="font-weight:bold;" info="13" />
2019-06-04 13:16:50 +02:00
</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
2019-06-20 09:42:33 +02:00
<MyComponent t-att-class="{a: state.flagA, b: state.flagB}" />
2019-06-04 13:16:50 +02:00
` ``
2019-06-14 15:24:04 +02:00
### Event Handling
2019-06-04 13:16:50 +02:00
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
2019-06-20 09:42:33 +02:00
button.addEventListener("click", component.someMethod.bind(component));
2019-06-04 13:16:50 +02:00
` ``
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
2019-06-20 09:42:33 +02:00
<MyComponent t-on-menu-loaded="someMethod" />
2019-06-04 13:16:50 +02:00
` ``
` ``js
2019-06-20 09:42:33 +02:00
class MyComponent {
2019-06-04 13:16:50 +02:00
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
2019-06-20 09:42:33 +02:00
(` this.el`). The event bubbles and is cancelable. The parent component listening
2019-06-04 13:16:50 +02:00
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
2019-06-20 09:42:33 +02:00
class ParentComponent {
2019-06-04 13:16:50 +02:00
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.
2019-06-14 15:45:16 +02:00
### 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 {
2019-09-24 14:06:53 +02:00
state = useState({ text: "" });
2019-06-14 15:45:16 +02:00
_updateInputValue(event) {
this.state.text = event.target.value;
}
}
` ``
` ``xml
<div>
2019-06-18 09:24:52 +02:00
<input t-on-input="_updateInputValue" />
<span t-esc="state.text" />
2019-06-14 15:45:16 +02:00
</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
2019-09-24 14:06:53 +02:00
should be an observed value in the component (usually ` state.someValue`). With
the ` t-model` directive, we can write a shorter code, equivalent to the previous
example:
2019-06-14 15:45:16 +02:00
` ``js
class Form extends owl.Component {
state = { text: "" };
}
` ``
` ``xml
<div>
2019-09-24 14:06:53 +02:00
<input t-model="state.text" />
2019-06-18 09:24:52 +02:00
<span t-esc="state.text" />
2019-06-14 15:45:16 +02:00
</div>
` ``
The ` t-model` directive works with ` <input>`, ` <input type="checkbox">`,
` <input type="radio">`, ` <textarea>` and ` <select>`:
` ``xml
<div>
2019-09-24 14:06:53 +02:00
<div>Text in an input: <input t-model="state.someVal"/></div>
<div>Textarea: <textarea t-model="state.otherVal"/></div>
<div>Boolean value: <input type="checkbox" t-model="state.someFlag"/></div>
2019-06-14 15:45:16 +02:00
<div>Selection:
2019-09-24 14:06:53 +02:00
<select t-model="state.color">
2019-06-14 15:45:16 +02:00
<option value="">Select a color</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
</div>
<div>
Selection with radio buttons:
<span>
2019-09-24 14:06:53 +02:00
<input type="radio" name="color" id="red" value="red" t-model="state.color"/>
2019-06-14 15:45:16 +02:00
<label for="red">Red</label>
</span>
<span>
2019-09-24 14:06:53 +02:00
<input type="radio" name="color" id="blue" value="blue" t-model="state.color" />
2019-06-14 15:45:16 +02:00
<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
2019-09-24 14:06:53 +02:00
<input t-model.lazy="state.someVal" />
2019-06-14 15:45:16 +02:00
` ``
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.
2019-06-14 15:24:04 +02:00
### ` t-key` Directive
2019-06-04 13:16:50 +02:00
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">
2019-06-18 09:24:52 +02:00
<t t-esc="todo.text" />
2019-06-04 13:16:50 +02:00
</span>
` ``
- _` t-if`/` t-else`_
- _animations_: give a different identity to a component. Ex: thread id with
animations on add/remove message.
2019-06-14 15:24:04 +02:00
### ` t-mounted` Directive
2019-06-04 13:16:50 +02:00
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
2019-06-20 09:42:33 +02:00
class MyComponent extends owl.Component {
2019-06-04 13:16:50 +02:00
...
focusMe() {
this.refs.someInput.focus();
}
}
` ``
2019-05-08 11:59:40 +02:00
### 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.
2019-05-10 10:49:14 +02:00
**Scenario 1: initial rendering** Imagine we want to render the following component tree:
2019-05-08 11:59:40 +02:00
` ``
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.
2019-06-20 09:42:33 +02:00
- component ` B` is created
2019-05-08 11:59:40 +02:00
1. ` willStart` is called on ` B`
2. template ` B` is rendered
2019-06-20 09:42:33 +02:00
- component ` C` is created
2019-05-08 11:59:40 +02:00
1. ` willStart` is called on ` C`
2. template ` C` is rendered
2019-06-20 09:42:33 +02:00
- component ` D` is created
2019-05-08 11:59:40 +02:00
1. ` willStart` is called on ` D`
2. template ` D` is rendered
2019-06-20 09:42:33 +02:00
- component ` E` is created
2019-05-08 11:59:40 +02:00
1. ` willStart` is called on ` E`
2. template ` E` is rendered
2019-06-20 09:42:33 +02:00
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
2019-05-08 11:59:40 +02:00
patching of the ` B`, ` C`, ` D` and ` E` DOM trees. (so the actual full DOM tree is created
in one pass)
2019-06-20 09:42:33 +02:00
4. the component ` A` root element is actually appended to ` document.body`
2019-05-08 11:59:40 +02:00
2019-06-20 09:42:33 +02:00
5. The method ` mounted` is called recursively on all components in the following
2019-05-08 11:59:40 +02:00
order: ` B`, ` D`, ` E`, ` C`, ` A`.
2019-05-10 10:49:14 +02:00
**Scenario 2: rerendering a component**. Now, let's assume that the user clicked on some
2019-05-08 11:59:40 +02:00
button in ` C`, and this results in a state update, which is supposed to:
- update ` D`,
- remove ` E`,
2019-06-20 09:42:33 +02:00
- add new component ` F`.
2019-05-08 11:59:40 +02:00
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
2019-06-20 09:42:33 +02:00
- component ` D` is updated:
2019-05-08 11:59:40 +02:00
1. hook ` willUpdateProps` is called on ` D` (async)
2. template ` D` is rerendered
2019-06-20 09:42:33 +02:00
- component ` F` is created:
2019-05-08 11:59:40 +02:00
1. hook ` willStart` is called on ` E` (async)
2019-05-08 15:21:33 +02:00
2. template ` F` is rendered
2019-05-08 11:59:40 +02:00
2019-06-20 09:42:33 +02:00
3. ` willPatch` hooks are called recursively on components ` C`, ` D` (not on ` F`,
2019-05-08 11:59:40 +02:00
because it is not mounted yet)
2019-06-20 09:42:33 +02:00
4. component ` C` is patched, which will cause recursively:
2019-05-08 11:59:40 +02:00
2. ` willUnmount` hook on ` E`, then destruction of ` E`,
3. (initial) patching of ` F`, then hook ` mounted` is called on ` F`
2019-05-10 10:49:14 +02:00
5. patching of ` D`
2019-05-08 15:21:33 +02:00
6. ` patched` hooks are called on ` D`, ` C`
2019-05-11 12:11:00 +02:00
2019-05-28 09:54:25 +02:00
### 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
2019-06-28 15:48:33 +02:00
- it validates keys defined in (static) ` props`. Additional keys given by the
parent will cause an error.
2019-05-28 09:54:25 +02:00
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
2019-06-28 15:48:33 +02:00
of the props. Also, if the name ends with ` ?`, it is considered optional.
2019-05-28 09:54:25 +02:00
- 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)
2019-06-28 15:48:33 +02:00
For each key, a ` prop` definition is either a boolean, a constructor, a list of constructors, or an object:
2019-05-28 09:54:25 +02:00
2019-06-28 15:48:33 +02:00
- a boolean: indicate that the props exists, and is mandatory.
2019-05-28 09:54:25 +02:00
- 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'];
` ``
2019-06-28 15:48:33 +02:00
` ``js
// size is optional
static props = ['message', 'size?'];
` ``
2019-05-28 09:54:25 +02:00
` ``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`)
2019-06-28 15:48:33 +02:00
someVal: [Boolean, Date], // either a boolean or a date
otherValue: true, // indicates that it is a prop
2019-05-28 09:54:25 +02:00
};
` ``
2019-06-14 15:24:04 +02:00
### References
2019-06-04 13:16:50 +02:00
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"/>
2019-06-20 09:42:33 +02:00
<SubComponent t-ref="someComponent"/>
2019-06-04 13:16:50 +02:00
</div>
` ``
2019-06-20 09:42:33 +02:00
In this example, the component will be able to access the ` div` and the component
2019-06-04 13:16:50 +02:00
inside the special ` refs` variable:
` ``js
this.refs.someDiv;
2019-06-20 09:42:33 +02:00
this.refs.someComponent;
2019-06-04 13:16:50 +02:00
` ``
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
2019-06-20 09:42:33 +02:00
some method on a sub component.
2019-06-04 13:16:50 +02:00
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
2019-06-07 14:31:22 +02:00
(like the [` t-attf-`](qweb.md#dynamic-attributes) and
2019-06-20 09:42:33 +02:00
` t-component` directives). For example, if we have
2019-06-04 13:16:50 +02:00
` id` set to 44 in the rendering context,
` ``xml
2019-06-20 09:42:33 +02:00
<div t-ref="component_{{id}}"/>
2019-06-04 13:16:50 +02:00
` ``
` ``js
2019-06-20 09:42:33 +02:00
this.refs.component_44;
2019-06-04 13:16:50 +02:00
` ``
2019-06-11 15:02:10 +02:00
### Slots
2019-06-20 09:42:33 +02:00
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
2019-06-11 15:02:10 +02:00
will need to render some content, some footer, but with the parent as the
rendering context.
2019-06-13 16:27:12 +02:00
This is what _slots_ are for.
2019-06-11 15:02:10 +02:00
` ``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
2019-06-20 09:42:33 +02:00
<div t-name="SomeComponent">
<div>some component</div>
2019-06-18 09:24:52 +02:00
<Dialog title="Some Dialog">
2019-06-11 15:02:10 +02:00
<t t-set="content">
<div>hey</div>
</t>
<t t-set="footer">
<button t-on-click="doSomething">ok</button>
</t>
2019-06-18 09:24:52 +02:00
</Dialog>
2019-06-11 15:02:10 +02:00
</div>
` ``
2019-06-20 09:42:33 +02:00
In this example, the component ` Dialog` will render the slots ` content` and ` footer`
2019-06-13 16:27:12 +02:00
with its parent as rendering context. This means that clicking on the button
2019-06-11 15:02:10 +02:00
will execute the ` doSomething` method on the parent, not on the dialog.
2019-06-20 09:42:33 +02:00
Default slot: the first element inside the component which is not a named slot will
2019-06-15 14:47:05 +02:00
be considered the ` default` slot. For example:
` ``xml
<div t-name="Parent">
2019-06-18 09:24:52 +02:00
<Child>
2019-06-15 14:47:05 +02:00
<span>some content</span>
2019-06-18 09:24:52 +02:00
</Child>
2019-06-15 14:47:05 +02:00
</div>
<div t-name="Child">
<t t-slot="default"/>
</div>
` ``
2019-06-14 15:24:04 +02:00
### Asynchronous Rendering
2019-05-11 12:11:00 +02:00
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:
2019-06-20 09:42:33 +02:00
- any component can delay the rendering (initial and subsequent) of the whole
2019-05-11 12:11:00 +02:00
application
2019-06-20 09:42:33 +02:00
- for a given component, there are two independant situations that will trigger an
2019-05-11 12:11:00 +02:00
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.
2019-06-20 09:42:33 +02:00
Here are a few tips on how to work with asynchronous components:
2019-05-11 12:11:00 +02:00
2019-06-20 09:42:33 +02:00
1. Minimize the use of asynchronous components!
2019-05-11 12:11:00 +02:00
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`)
2019-06-14 15:40:32 +02:00
4. For all the other cases, the ` t-asyncroot` directive (to use alongside
2019-06-20 09:42:33 +02:00
` t-component`) is there to help you. When this directive is met, a new rendering
2019-06-14 10:17:13 +02:00
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
2019-06-20 09:42:33 +02:00
<div t-name="ParentComponent">
2019-06-18 09:24:52 +02:00
<SyncChild />
<AsyncChild t-asyncroot="1"/>
2019-06-14 10:17:13 +02:00
</div>
` ``
2019-07-09 14:33:56 +02:00
### Error Handling
By default, whenever an error occurs in the rendering of an Owl application, we
destroy the whole application. Otherwise, we cannot offer any guarantee on the
state of the resulting component tree. It might be hopelessly corrupted, but
without any user-visible state.
Clearly, it sometimes is a little bit extreme to destroy the application. This
is why we have a builtin mechanism to handle rendering errors (and errors coming
from lifecycle hooks): the ` catchError` hook.
Whenever the ` catchError` lifecycle hook is implemented, all errors coming from
sub components rendering and/or lifecycle method calls will be caught and given
to the ` catchError` method. This allow us to properly handle the error, and to
not break the application.
For example, here is how we could implement an ` ErrorBoundary` component:
` ``xml
<div t-name="ErrorBoundary">
<t t-if="state.error">
Error handled
</t>
<t t-else="1">
<t t-slot="default" />
</t>
</div>
` ``
` ``js
2019-09-24 14:06:53 +02:00
class ErrorBoundary extends Component {
state = useState({ error: false });
2019-07-09 14:33:56 +02:00
catchError() {
this.state.error = true;
}
}
` ``
Using the ` ErrorBoundary` is then extremely simple:
` ``xml
<ErrorBoundary><SomeOtherComponent/></ErrorBoundary>
` ``
Note that we need to be careful here: the fallback UI should not throw any
error, otherwise we risk going into an infinite loop.
Also, it may be useful to know that whenever an error is caught, it is then
broadcasted to the application by an event on the ` qweb` instance. It may be
useful, for example, to log the error somewhere.
` ``js
env.qweb.on("error", null, function(error) {
// do something
// react to the error
});
` ``