QWeb specifications serve as a framework for defining Owl templates, primarily employed for HTML generation and built on the XML format. These templates are organized into OWL functions, general a virtual DOM representation of the HTML. Owl, being a dynamic component system, introduces its distinct directives, such as the t-on directive.(like t-on)
<div>
<span t-if="Condition" >Sample string</span>
<ul t-else=" >
<li t-foreach= "iterative message" t-as= "message" >
< t t-esc= "message" / >
</li>
</ul>
</div>
Now, we have to discuss some common directives.
Name | Description |
t-esc | Outputting safe a value |
tout | Outputting value, possible without escaping |
t-if, t-elif, t-else, | Constitutional rendering |
t-set, t-value | Setting a variable |
t-foreach, t-as | Loops |
t-att, t-attf-*, t-att-* | Dynamic attributes |
t-call | Rendering sub-template |
t-debug, t-log | Debugging |
t-translation | Disabling the translation of a node |
In Owl, additional directives are requested beyond those specified above. The following directives are necessary:
Name | Description |
t-component, t-props | Defining subcomponent |
t-tag | Rendering nodes with dynamic tag name |
t-key | Defining a key |
t-slot, t-set-slot, t-slot-scope | Rendering a slots |
t-ref | Setting a reference to a dom node or a sub-component |
t-model | Form input binding |
t-on-* | Event handling |
t-portal | Portal |
Now we can see each other deeply.
Data output
Wherever you want to add a dynamic expression, here we have to use these directives (t-esc and t-out).
* t-esc
T-esc directives are used to render a value of the field or a variable into a template.
< p> < t-esc="value"/> < / p>
In the above code, the value of the variable set is 20. Then, its output is shown below.
< p>20</p>
* t-out
The t-out directive closely resembles t-esc, expect it may not involve escaping. The distinction lies in the fact that the value received by the t-out directive remains unescaped only when specically marked as such through the markup utility function.
For example,
<div>
<span t-if="Condition" >Sample string</span>
<ul t-else=" >
<li t-foreach= "iterative message" t-as= "message" >
< t t-esc= "message" / >
</li>
</ul>
</div>
Initially, the initial t-out functions similarly to a t-esc directive, resulting in the escapement of the content in value1. Nevertheless, as value2 has been designed as markup, it will be inserted as HTML.
Conditionals
QWeb incorporates a conditional directive 'if,' which esses an expression provided as an attribute value:
<div>
< t-if="condition" >
< p>ok</p>
</t>
</div>
If it's the condition is true, then it renders the value “ ok”
<div>
< p>ok</p>
</div>
If it's false, it's removed from the result.
<div>
</div>
Extra conditional directives branches:
<div>
< p t-if="user.birthday = = today () ">Happy birthday!</p>
< p t-elif="user.login = = 'root'" >Welcome master!</p>
< p t-else=" >Welcome!</p>
</div>
Variable Setting
In QWeb, the creation of a variable to store data for subsequent computations, allowing for multiple usages, is achieved through the t-set directive. This directive is employed to store data under the specified name (variable name).
There are two ways to provide the value to be set:
A 't-value' attribute can be utilized, containing an expression whoevaluation result will be set:
<div>
< p t-if="user.birthday = = today () ">Happy birthday!</p>
< p t-elif="user.login = = 'root'" >Welcome master!</p>
< p t-else=" >Welcome!</p>
</div>
It's essential to note that the evaluation occurs at rendering time, not during compile time.
Loops
In QWeb templates, the 't-foreach' directive is employed for iterating through items. The 't-as' directive is utilized to assign a name to the current item with the iteration loop.
< t t-foreach=" [1,3,5,7] "t-as=" i ">
< p> < t-out = "i" / > < / p>
</t>
Its render as:
< p>1</p>
< p>3</p>
< p>5</p>
< p> 7</p>
Similar to conditions, 't-foreach' applies to the element that carries the directive's attribute and
< p t-foreach=" [1, 3, 5,7] "t-as="i "t-key="i ">
< t t-esc="i"/>
</p>
The statement "is equalent to the previous example" is a repetition of the context, so it can be omitted for conciseness:
An important distortion should be noted in comparison to the usual QWeb behavior: Owl mandates the presence of a 't-key' directive for prop reconciliation of renderings.
The 't-foreach' directive can iterate over any iterable and offers special support for objects and maps. It revels the 't-as' content as the key of the ongoing iteration and associates the corresponding value using the same name along with the '_value' suffix.
Beyond the specified name conveyed through 't-as,' 't-foreach' furnishes several other useful variables (please note that $as will be substituted with the provided 't-as' name):
* $as_value: it denotes the current iteration's value, identical to $as for arrays and similar iterables; for objects and maps, it supplies the value, while $as provides the key.
* $as_index: It specifics the current iteration index (the initial item in the iteration holders an index of 0).
* $as_first: It indicates where the present item is the first in the iteration (equivalent to $as_index = = 0).
* $as_last: signatures when the ongoing item is the last in the iteration (equivalent to $as_index + 1 = = $ as_size), contingent upon the iteratee's sizing.
These supplementary variables, along with any newly generated ones within 't-foreach,' remain exclusively accessible within the 't-foreach' scope. Should a variable persist beyond the confines of 't-foreach,' its value is duplicated into the global context upon the 'foreach's conclusion.
< t t-set= "existing_variable" t-value= "false" / >
<!-- existing_variable now False -->
< p t-foreach="Array (3) "t-as=" i "t-key=" 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 -->
While Owl strives for declarativity, the DOM doesn't fully expose its state declaratively in the DOM tree. Critical elements like scrolling state, current user selection, focused elements, or input state aren't set as attributes in the DOM tree. This is why a virtual DOM algorithm is employed to retain the actual DOM node instead of replacing it with a new one.
Consider a scenario where we have a list of two items [{ text: "a" }, { text: "b" }] and render them using the template:
< p t-foreach= "items" t-as="item" t-key= "item_index" > < t-esc= "item.text"/> < / p>
The result yields two `< p>` tags with the text 'a' and 'b'. Now, if we swap them and re-render the template, Owl needs to determine the intent:
- Should Owl swap the DOM nodes?
- Or should it keep the DOM nodes but update the text content?
This seemingly trivial decision matters, leading to different outs in scenarios like maintaining user text selection during swaps.
The `t-key` directive is crucial for assigning identity or uniqueness to an element, enabling Owl to discern where the elements in a list are unique.
For instance, adding an ID to the above example ([{id: 1, text: "a" }, {id: 2, text: "b" }]) shows the template to be modified:
< p t-foreach= "items" t-as="item" t-key= "item.id"> < t-esc= "item.text"/> < / p>
The `t-key` directive is valid for lists (`t-foreach`). A key should be a unique number or string (objects won't work, as they are cast to the "[object Object]" string, which is not unique).
Additionally, the key can be set on a `t` tag or its child, with the following variations being equalent:
< p t-foreach= "items" t-as="item" t-key= "item.id" >
< t t-esc="item.text"/>
</p>
< t t-foreach= "items" t-as="item" t-key= "item.id" >
< p t-esc="item.text"/>
</t>
< t t-foreach= "items" t-as="item ">
< p t-key= "item.id" t-esc= "item.text"/>
</t>
In the absence of a `t-key` directive, Owl defaults to using the index.
Note: The `t-foreach` directive exclusively accesss arrays or objects, not other iterables like Set. However, using the JavaScript spread operator (...) can convert sets (or other iterables) into lists for compatibility:
< t t-foreach=" [...items]" t-as= "item" >...</t>
Dynamic sub-templates
QWeb templates serve not only for top-level rendering but also for inclusion within other templates, eliminating redundancy and enabling code reduction and optimization through reuse. This is facilitated by the use of the `t-call` directive.
For example:
<div t-name= "other-template" >
< p> < t-value="var"/> < / p>
</div>
<div t-name= "main-template" >
< t t-set="var" t-value= "owl"/>
< t t-call="other-template "/ >
</div>
This structure renders as `< div> < p> owl< / p> < / div> `. The sub-template is essentially inlined with the main template but operates in a sub-scope where variables defined in the sub-template do not escape.
In situations where information needs to be passed to the sub-template, the content of the `t-call` directive 's body is available as a special magic variable `0`:
< t t-name="other-template ">
This template was called with content:
< t-raw="0"/>
</t>
<div t-name= "main-template" >
< t t-call= "other-template" >
<em>content</em>
</t>
</div>
This results in:
<div>
This template was called with content:
<em>content</em>
</div>
This mechanism allows for defining variables scoped to a sub-template:
< t t-call= "other-template" >
< t t-set="var" t-value="1"/>
</t>
<!-- "var" does not exist here? -->
Note: By default, the rendering context for a sub-template is the current rendering context. However, specifying a specific object as context is possible using the `t-call-context` directive:
< t t-call= "other-template" t-call-context= "obj"/>
Dynamic sub-templates
The `t-call` directive is versatile, allowing dynamic sub-template invocation through string interpolation. For instance:
<div t-name= "main-template" >
< t t-call=" { { template} } ">
<em>content</em>
</t>
</div>
In this scenario, the template's name is acquired from the `template` value within the template rendering context, facilitating dynamic and flexible template calls.
Inline templates
In many real-world applications, templates are typically defined in an XML file to leverage the XML ecosystem and perform additional processing, such as translation. However, there are cases where defining a template inline can be convenient. To achieve this, the `xml` helper function can be employed:
const { Component, xml } = owl;
class MyComponent extends Component {
static template = xml`
<div>
<span t-if="somecondition" > text</span>
<button t-on-click= "someMethod" >Click< /button>
</div>
`;
// ... other component logic
}
mount (MyComponent, document.body);
This function generates a unique string ID, registers the template under that ID within Owl's internals, and then returns the ID. This approach enables the online definition of templates for certain scenarios.
Rendering svg
class Node extends Component {
static template = xml`
< g>
<circle t-att-cx= "props.x" t-att-cy= "props.y" r= "4" fill="black"/>
< text t-att-x= "props.x - 5" t-att-y="props.y + 18 "> < t-esc=" props.node.label "/ > </text
< t t-set= "childx" t-value= "props.x + 100" / >
< t t-set= "height" t-value= "props.height/(props.node.children | | []).length"/>
< t t-foreach="props.node.children | | [] "t-as=" child ">
< t t-set= "childy" t-value= "props.y + child_index*height"/>
<line t-att-x1="props.x" t-att-y1="props.y" t-att-x2="childx" t-att-y2="childy "stroke= "black" / >
<Node x="childx" y= "childy" node= "child" height= "height"/>
</t>
</g>
`;
static components = { Node };
}
class RootNode extends Component {
static template = xml`
< svg height="180" >
<Node node="graph" x="10 "y="20 "height="180 "/ >
</svg>
`;
static components = { Node };
graph = {
label: "a",
children: [
{ label: "b" },
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
{ label: "f", children: [{ label: "g" }] },
],
};
}
The `RootNode` component is responsible for rendering a dynamic SVG representation of the graph specified by the `graph` property. It's important to note that there is a recurring structure in play, as the `node` component utilizes itself as a subcomponent.
A crucial conscience is that Owl requires a prop namespace setting for each SVG element. Due to Owl compiling each template independently, it faces challenges in easily determining where a template is intended for inclusion in an SVG namespace. To address this, Owl relies on a heuristic: if a tag is either `svg`, `g`, or `path`, it is treated as an SVG element. In practical terms, this implies that each component or sub-template (included with `t-call`) should have one of these tags as its root element. This heuristic helps Owl appropriately handle SVG namespaces in the rendered output.
In summer, Odoo 17's QWeb templates and operations provide a dynamic and user-friendly approach to customizing and enhancing the platform's functionality. These tools empower businesses to craft visually appealing reports, documents, and views. The flexibility offered by operations such as loops and conditions further enables the creation of tailored solutions to meet specific business needs.
To read more about what is the QWeb template in Odoo 17, refer to our blog What is the QWeb template in Odoo 17.