mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
54c3dd76e4
closes #149
506 lines
15 KiB
Markdown
506 lines
15 KiB
Markdown
# 🦉 QWeb 🦉
|
||
|
||
## Content
|
||
|
||
- [Overview](#overview)
|
||
- [Directives](#directives)
|
||
- [QWeb Engine](#qweb-engine)
|
||
- [Reference](#reference)
|
||
- [White spaces](#white-spaces)
|
||
- [Root nodes](#root-nodes)
|
||
- [Expression evaluation](#expression-evaluation)
|
||
- [Static html nodes](#static-html-nodes)
|
||
- [Outputting data](#outputting-data)
|
||
- [Setting Variables](#setting-variables)
|
||
- [Conditionals](#conditionals)
|
||
- [Dynamic attributes](#dynamic-attributes)
|
||
- [Loops](#loops)
|
||
- [Rendering Sub Templates](#rendering-sub-templates)
|
||
- [Debugging](#debugging)
|
||
|
||
## Overview
|
||
|
||
[QWeb](https://www.odoo.com/documentation/12.0/reference/qweb.html) is the primary templating engine used by Odoo. It is based on the XML format, and used
|
||
mostly to generate HTML. In OWL, QWeb templates are compiled into functions that
|
||
generate a virtual dom representation of the HTML.
|
||
|
||
Template directives are specified as XML attributes prefixed with `t-`, for instance `t-if` for conditionals, with elements and other attributes being rendered directly.
|
||
|
||
To avoid element rendering, a placeholder element `<t>` is also available, which executes its directive but doesn’t generate any output in and of itself.
|
||
|
||
```xml
|
||
<div>
|
||
<span t-if="somecondition">Some string</span>
|
||
<ul t-else="1">
|
||
<li t-foreach="messages" t-as="message">
|
||
<t t-esc="message">
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
```
|
||
|
||
The QWeb class in the OWL project is an implementation of that specification
|
||
with a few interesting points:
|
||
|
||
- it compiles templates into functions that output a virtual DOM instead of a
|
||
string. This is necessary for the component system.
|
||
- it has a few extra directives: `t-widget`, `t-on`, ...
|
||
|
||
## Directives
|
||
|
||
We present here a list of all standard QWeb directives:
|
||
|
||
| Name | Description |
|
||
| ------------------------------ | ------------------------------------------------------------ |
|
||
| `t-esc` | [Outputting safely a value](#outputting-data) |
|
||
| `t-raw` | [Outputting value, without escaping](#outputting-data) |
|
||
| `t-set`, `t-value` | [Setting variables](#setting-variables) |
|
||
| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) |
|
||
| `t-foreach`, `t-as` | [Loops](#loops) |
|
||
| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) |
|
||
| `t-call` | [Rendering sub templates](#rendering-sub-templates) |
|
||
| `t-debug`, `t-log` | [Debugging](#debugging) |
|
||
| `t-name` | [Defining a template (not really a directive)](#qweb-engine) |
|
||
|
||
The component system in Owl requires additional directives, to express various
|
||
needs. Here is a list of all Owl specific directives:
|
||
|
||
| Name | Description |
|
||
| ------------------------- | --------------------------------------------------------------------------------------- |
|
||
| `t-widget`, `t-keepalive` | [Defining a sub component](component.md#composition) |
|
||
| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#keeping-references) |
|
||
| `t-key` | [Defining a key (to help virtual dom reconciliation)](component.md#t-key-directive) |
|
||
| `t-on-*` | [Event handling](component.md#event-handling) |
|
||
| `t-transition` | [Defining an animation](animations.md#css-transitions) |
|
||
| `t-mounted` | [Callback when a node or component is mounted](component.md#t-mounted-directive) |
|
||
|
||
## QWeb Engine
|
||
|
||
This section is about the javascript code that implements the `QWeb` specification.
|
||
Owl exports a `QWeb` class in `owl.QWeb`. To use it, it just needs to be
|
||
instantiated:
|
||
|
||
```js
|
||
const qweb = new owl.QWeb();
|
||
```
|
||
|
||
It's API is quite simple:
|
||
|
||
- **`constructor(data)`**: constructor. Takes an optional string to add initial
|
||
templates (see `addTemplates` for more information on format of the string).
|
||
|
||
```js
|
||
const qweb = new owl.QWeb(TEMPLATES);
|
||
```
|
||
|
||
- **`addTemplate(name, xmlStr)`**: add a specific template.
|
||
|
||
```js
|
||
qweb.addTemplate("mytemplate", "<div>hello</div>");
|
||
```
|
||
|
||
- **`addTemplates(xmlStr)`**: add a list of templates (identified by `t-name`
|
||
attribute).
|
||
|
||
```js
|
||
const TEMPLATES = `
|
||
<templates>
|
||
<div t-name="App" class="main">main</div>
|
||
<div t-name="OtherWidget">other widget</div>
|
||
</templates>`;
|
||
qweb.addTemplates(TEMPLATES);
|
||
```
|
||
|
||
- **`render(name, context, extra)`**: renders a template. This returns a `vnode`,
|
||
which is a virtual representation of the DOM (see [vdom doc](vdom.md)).
|
||
|
||
```js
|
||
const vnode = qweb.render("App", widget);
|
||
```
|
||
|
||
- **`register(name, Component)`**: static function to register an OWL Component
|
||
to QWeb's global registry. Globally registered Components can be used in
|
||
templates (see the `t-widget` directive). This is useful for commonly used
|
||
components accross the application.
|
||
|
||
```js
|
||
class Dialog extends owl.Component { ... }
|
||
QWeb.register("Dialog", Dialog);
|
||
|
||
...
|
||
|
||
class ParentWidget extends owl.Component { ... }
|
||
qweb.addTemplate("ParentWidget", "<div><t t-widget='Dialog'/></div>");
|
||
```
|
||
|
||
## Reference
|
||
|
||
We define in this section the specification of how `QWeb` templates should be
|
||
rendered. Note that we only document here the standard QWeb specification. Owl
|
||
specific extensions are documented in various other parts of the documentation.
|
||
|
||
### White spaces
|
||
|
||
White spaces in a templates are handled in a special way:
|
||
|
||
- consecutive whitespaces are always condensed to a single whitespace
|
||
- if a whitespace-only text node contains a linebreak, it is ignored
|
||
- the previous rules do not apply if we are in a `<pre>` tag
|
||
|
||
### Root nodes
|
||
|
||
For many reasons, Owl QWeb templates should have a single root node. More
|
||
precisely, the result of a template rendering should have a single root node:
|
||
|
||
```xml
|
||
<!–– not ok: two root nodes ––>
|
||
<t>
|
||
<div>foo</div>
|
||
<div>bar</div>
|
||
</t>
|
||
|
||
<!–– ok: result has one single root node ––>
|
||
<t>
|
||
<div t-if="someCondition">foo</div>
|
||
<span t-else="1">bar</span>
|
||
</t>
|
||
```
|
||
|
||
Extra root nodes will actually be ignored (even though they will be rendered
|
||
in memory).
|
||
|
||
Note: this does not apply to subtemplates (see the `t-call` directive). In that
|
||
case, they will be inlined in the main template, and can actually have many
|
||
root nodes.
|
||
|
||
### Expression evaluation
|
||
|
||
QWeb expressions are strings that will be processed at compile time. Each variable in
|
||
the javascript expression will be replaced by a lookup in the context (so, the
|
||
widget). For example, `a + b.c(d)` will be converted into:
|
||
|
||
```js
|
||
context['a'] + context['b'].c(context['d'])
|
||
```
|
||
|
||
It is useful to explain the various rules that applies on these expressions:
|
||
|
||
1. it should be a simple expression which returns a value. It cannot be a statement.
|
||
|
||
```xml
|
||
<div><p t-if="1 + 2 === 3">ok</p></div>
|
||
```
|
||
|
||
is valid, but the following is not valid:
|
||
|
||
```xml
|
||
<div><p t-if="console.log(1)">NOT valid</p></div>
|
||
```
|
||
|
||
2. it can use anything in the rendering context (typically, the component):
|
||
|
||
```xml
|
||
<p t-if="user.birthday === today()">Happy bithday!</p>
|
||
```
|
||
|
||
is valid, and will read the `user` object from the context, and call the
|
||
`today` function.
|
||
|
||
3. it can use a few special operators to avoid using symbols such as `<`, `>`,
|
||
`&` or `|`. This is useful to make sure that we still write valid XML.
|
||
|
||
| Word | will be replaced by |
|
||
| ----- | ------------------- |
|
||
| `and` | `&&` |
|
||
| `or` | `\|\|` |
|
||
| `gt` | `>` |
|
||
| `gte` | `>=` |
|
||
| `lt` | `<` |
|
||
| `lte` | `<=` |
|
||
|
||
So, one can write this:
|
||
|
||
```xml
|
||
<div><p t-if="10 + 2 gt 5">ok</p></div>
|
||
```
|
||
|
||
### Static Html Nodes
|
||
|
||
Normal, regular html nodes are rendered into themselves:
|
||
|
||
```xml
|
||
<div>hello</div> <!–– rendered as itself ––>
|
||
```
|
||
|
||
### Outputting Data
|
||
|
||
The `t-esc` directive is necessary whenever you want to add a dynamic text
|
||
expression in a template. The text is escaped to avoid security issues.
|
||
|
||
```xml
|
||
<p><t t-esc="value"/></p>
|
||
```
|
||
|
||
rendered with the value `value` set to `42` in the rendering context yields:
|
||
|
||
```html
|
||
<p>42</p>
|
||
```
|
||
|
||
The `t-raw` directive is almost the same as `t-esc`, but without the escaping.
|
||
This is mostly useful to inject a raw html string somewhere. Obviously, this
|
||
is unsafe to do in general, and should only be used for strings known to be safe.
|
||
|
||
```xml
|
||
<p><t t-raw="value"/></p>
|
||
```
|
||
|
||
rendered with the value `value` set to `<span>foo</span>` in the rendering context yields:
|
||
|
||
```html
|
||
<p><span>foo</span></p>
|
||
```
|
||
|
||
### Setting Variables
|
||
|
||
QWeb allows creating variables from within the template, to memoize a computation (to use it multiple times), give a piece of data a clearer name, ...
|
||
|
||
This is done via the `t-set` directive, which takes the name of the variable to create. The value to set can be provided in two ways:
|
||
|
||
1. a `t-value` attribute containing an expression, and the result of its
|
||
evaluation will be set:
|
||
|
||
```xml
|
||
<t t-set="foo" t-value="2 + 1"/>
|
||
<t t-esc="foo"/>
|
||
```
|
||
|
||
will print `3`. Note that the evaluation is done at rendering time, not at
|
||
compilte time.
|
||
|
||
2. if there is no `t-value` attribute, the node’s body is saved and its value is
|
||
set as the variable’s value:
|
||
|
||
```xml
|
||
<t t-set="foo">
|
||
<li>ok</li>
|
||
</t>
|
||
<t t-esc="foo"/>
|
||
```
|
||
|
||
will generate `<li>ok</li>` (the content is escaped as we used the `t-esc` directive)
|
||
|
||
The `t-set` directive acts like a regular variable in most programming language.
|
||
It is lexically scoped (inner nodes are sub scopes), can be shadowed, ...
|
||
|
||
### Conditionals
|
||
|
||
The `t-if` directive is useful to conditionally render something. It evaluates
|
||
the expression given as attribute value, and then acts accordingly.
|
||
|
||
```xml
|
||
<div>
|
||
<t t-if="condition">
|
||
<p>ok</p>
|
||
</t>
|
||
</div>
|
||
```
|
||
|
||
The element is rendered if the condition (evaluated with the current rendering
|
||
context) is true:
|
||
|
||
```xml
|
||
<div>
|
||
<p>ok</p>
|
||
</div>
|
||
```
|
||
|
||
but if the condition is false it is removed from the result:
|
||
|
||
```xml
|
||
<div>
|
||
</div>
|
||
```
|
||
|
||
The conditional rendering applies to the bearer of the directive, which does not
|
||
have to be `<t>`:
|
||
|
||
```xml
|
||
<div>
|
||
<p t-if="condition">ok</p>
|
||
</div>
|
||
```
|
||
|
||
will give the same results as the previous example.
|
||
|
||
Extra conditional branching directives `t-elif` and `t-else` are also available:
|
||
|
||
```xml
|
||
<div>
|
||
<p t-if="user.birthday == today()">Happy bithday!</p>
|
||
<p t-elif="user.login == 'root'">Welcome master!</p>
|
||
<p t-else="">Welcome!</p>
|
||
</div>
|
||
```
|
||
|
||
### Dynamic attributes
|
||
|
||
One can use the `t-att-` directive to add dynamic attributes. Its main use is to
|
||
evaluate an expression (at rendering time) and bind an attribute to its result:
|
||
|
||
For example, if we have `id` set to 32 in the rendering context,
|
||
|
||
```xml
|
||
<div t-att-data-action-id="id"/> <!-- result: <div data-action-id="32"></div> -->
|
||
```
|
||
|
||
If an expression evaluates to a falsy value, it will not be set at all:
|
||
|
||
```xml
|
||
<div t-att-foo="false"/> <!-- result: <div></div> -->
|
||
```
|
||
|
||
There is another way to format a string attribute: the `t-attf-` directive. With
|
||
it, you get string interpolation:
|
||
|
||
```xml
|
||
<div t-attf-foo="a {{value1}} is {{value2}} of {{value3}} ]"/>
|
||
<!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> -->
|
||
```
|
||
|
||
### Loops
|
||
|
||
QWeb has an iteration directive `t-foreach` which take an expression returning the
|
||
collection to iterate on, and a second parameter `t-as` providing the name to use
|
||
for the current item of the iteration:
|
||
|
||
```xml
|
||
<t t-foreach="[1, 2, 3]" t-as="i">
|
||
<p><t t-esc="i"/></p>
|
||
</t>
|
||
```
|
||
|
||
will be rendered as:
|
||
|
||
```xml
|
||
<p>1</p>
|
||
<p>2</p>
|
||
<p>3</p>
|
||
```
|
||
|
||
Like conditions, `t-foreach` applies to the element bearing the directive’s attribute, and
|
||
|
||
```xml
|
||
<p t-foreach="[1, 2, 3]" t-as="i">
|
||
<t t-esc="i"/>
|
||
</p>
|
||
```
|
||
|
||
is equivalent to the previous example.
|
||
|
||
`t-foreach` can iterate on an array (the current item will be the current value)
|
||
or an object (the current item will be the current key).
|
||
|
||
In addition to the name passed via t-as, `t-foreach` provides a few other
|
||
variables for various data points (note: `$as` will be replaced by the name
|
||
passed to `t-as`):
|
||
|
||
- `$as_value`: the current iteration value, identical to `$as` for lists and
|
||
integers, but for objects, it provides the value (where `$as` provides the key)
|
||
- `$as_index`: the current iteration index (the first item of the iteration has index 0)
|
||
- `$as_first`: whether the current item is the first of the iteration
|
||
(equivalent to `$as_index == 0`)
|
||
- `$as_last`: whether the current item is the last of the iteration
|
||
(equivalent to `$as_index + 1 == $as_size`), requires the iteratee’s size be
|
||
available
|
||
|
||
These extra variables provided and all new variables created into the `t-foreach`
|
||
are only available in the scope of the `t-foreach`. If the variable exists outside
|
||
the context of the `t-foreach`, the value is copied at the end of the foreach
|
||
into the global context.
|
||
|
||
```xml
|
||
<t t-set="existing_variable" t-value="False"/>
|
||
<!-- existing_variable now False -->
|
||
|
||
<p t-foreach="Array(3)" t-as="i">
|
||
<t t-set="existing_variable" t-value="True"/>
|
||
<t t-set="new_variable" t-value="True"/>
|
||
<!-- existing_variable and new_variable now True -->
|
||
</p>
|
||
|
||
<!-- existing_variable always True -->
|
||
<!-- new_variable undefined -->
|
||
```
|
||
|
||
### Rendering Sub Templates
|
||
|
||
QWeb templates can be used for top level rendering, but they can also be used
|
||
from within another template (to avoid duplication or give names to parts of
|
||
templates), using the `t-call` directive:
|
||
|
||
```xml
|
||
<div t-name="other-template">
|
||
<p><t t-value="var"/></p>
|
||
</div>
|
||
|
||
<div t-name="main-template">
|
||
<t t-set="var" t-value="owl"/>
|
||
<t t-call="other-template"/>
|
||
</div>
|
||
```
|
||
|
||
will be rendered as `<div><p>owl</p></div>`. This example shows that the sub
|
||
template is rendered with the execution context of the parent. The sub template
|
||
is actually inlined in the main template, but in a sub scope: variables defined
|
||
in the sub template do not escape.
|
||
|
||
Sometimes, one might want to pass information to the sub template. In that case,
|
||
the content of the body of the `t-call` directive is available as a special
|
||
magic variable `0`:
|
||
|
||
```xml
|
||
<t t-name="other-template">
|
||
This template was called with content:
|
||
<t t-raw="0"/>
|
||
</t>
|
||
|
||
<div t-name="main-template">
|
||
<t t-call="other-template">
|
||
<em>content</em>
|
||
</t>
|
||
</div>
|
||
```
|
||
|
||
will result in :
|
||
|
||
```xml
|
||
<div>
|
||
This template was called with content:
|
||
<em>content</em>
|
||
</div>
|
||
```
|
||
|
||
### Debugging
|
||
|
||
The javascript QWeb implementation provides two useful debugging directives:
|
||
|
||
`t-debug` adds a debugger statement during template rendering:
|
||
|
||
```xml
|
||
<t t-if="a_test">
|
||
<t t-debug="">
|
||
</t>
|
||
```
|
||
|
||
will stop execution if the browser dev tools are open.
|
||
|
||
`t-log` takes an expression parameter, evaluates the expression during rendering and logs its result with console.log:
|
||
|
||
```xml
|
||
<t t-set="foo" t-value="42"/>
|
||
<t t-log="foo"/>
|
||
```
|
||
|
||
will print 42 to the console
|