What is QWeb template in Odoo 16?

What is QWeb template in Odoo 16?

15 minutes, 48 seconds Read

QWeb specifications are used to describe Owl templates. It is mostly used to generate HTML and is based on the XML format. QWeb templates are assembled into OWL functions that produce a simulated DOM representation of the HTML. Additionally, as Owl is a live component system, it has its own unique directives (like t-on).
<div>
    <span t-if="someCondition">Some string</span>
    <ul t-else="">
        <li t-foreach="messages" t-as="message">
            <t t-esc="message"/>
        </li>
    </ul>
</div>
The Directives
Name Description
t-esc Outputting safely a value
t-out Outputting value, possibly without escaping
t-if, t-elif, t-else, Conditional 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

The component system in Owl requires additional directives to express various needs. Here is a list of all Owl-specific directives:

t-component, t-props Defining subcomponent
t-ref Setting a reference  to a dom node or a sub component
t-key Defining a key
t-on-* Event handling
t-portal Portal
t-slot, t-set-slot, t-slot-scope Rendering a slots
t-model Form input binding
t-tag Rendering nodes with dynamic tag name

Go through each one deeply.

Expression Evaluation

QWeb expressions are strings to be processed at runtime. Each variable in the Javascript expression is replaced by a view (i.e., element) in the context. For example, a + b.c(d) changes to:

context["a"] + context["b"].c(context["d"]);

It is useful to explain the various rules that apply to these expressions:

1. it should be a simple expression that returns a value. It cannot be a statement.

       <div><p t-if="1 + 2 === 3">ok</p></div>

       It’s valid, but the following are not valid:     

      <div><p t-if="console.log(1)">NOT valid</p></div>

2. it can use anything in the rendering context (which typically contains the properties of the component):

   <p t-if="user.birthday === today()">Happy birthday!</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 Replaced With
and &&
or ||
> >
>e; >=
< <
<e; <=
& &
"
'

So, it is written in the form is

<div><p t-if="10 + 2 > 5">ok</p></div>

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.

<p><t t-esc="value"/></p>

rendered with the value set to 23 in the rendering context yields:

<p>23</p>

The t-out directive is almost the same as t-esc, but possibly without the escaping. The difference is that the value received by the t-out directive will only be not-escaped if it has been marked as such, using the markup utility function.

For example, in the following component:

const { markup, Component, xml } = owl;
class SomeComponent extends Component {
  static template = xml`
    <t t-out="value1"/>
    <t t-out="value2"/>`;
  value1 = "<div>some text 1</div>";
  value2 = markup("<div>some text 2</div>");
}

The first t-out will act as a t-esc directive, which means that the content of value1 will be escaped. However, since value2 has been tagged as a markup, this will be injected as html.

Conditionals

Qweb  has a conditional directive if, which evaluates an expression given as attribute value:

<div>
    <t t-if="condition">
        <p>ok</p>
    </t>
</div>

The element is rendered if the condition is true:

<div>
    <p>ok</p>
</div>

But if the condition is false, it is removed from the result:

<div>
</div>

The conditional rendering applies to the bearer of the directive, which does not have to be <t>:

<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:

<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>

Setting Variables

In Qweb, we can create a variable to hold data for future computations( to use it multiple times). This is done using the t-set directive, which is to hold the data in the given name( variable name).

The value to set can be provided in two ways:

a t-value attribute containing an expression, and the result of its evaluation will be set:

<t t-set="foo" t-value="2 + 1"/>
<t t-out="foo"/>

will print 

Note that the evaluation is done at rendering time, not compile time.

if there is no t-value attribute, the node’s body is saved, and its value is set as the variable’s value:

<t t-set="foo">
    <li>ok</li>
</t>
<t t-out="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 languages. It is lexically scoped (inner nodes are sub scopes), can be shadowed,…

Dynamic Attributes

You 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,

<div t-att-data-action-id="id"/> 
<!-- result: <div data-action-id="32"></div> -->

If an expression evaluates to a false value, it will not be set at all.

<div t-att-foo="false"/>  <!-- result: <div></div> -->

It is sometimes convenient to format an attribute with string interpolation. In that case, the t-attf- directive can be used. It is useful when we need to mix literal and dynamic elements, such as CSS classes. The dynamic elements can be specified with either {{…}} or #{…}:

<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> -->

If we need completely dynamic attribute names, then there is an additional directive: t-att, which takes either an object (with keys mapping to their values) or a pair [key, value]. For example:

<div t-att="{'a': 1, 'b': 2}"/> <!-- result: <div a="1" b="2"></div> -->
<div t-att="['a', 'b']"/> <!-- <div a="b"></div> -->

Dynamic class attribute

For convenience, Owl supports a special case for the t-att-class case: one can use an object with keys describing the classes, and values boolean value denoting if the class is or is not present:

<div t-att-class="{'a': true, 'b': true}"/> <!-- result: <div class="a b"></div> -->
<div t-att-class="{'a b': true, 'c': true}"/> <!-- result: <div class="a b c"></div> -->

Note that it can be combined with the normal class attributes:

<div class="a" t-att-class="{'b': true}"/>  
<!-- result: <div class="a b"></div> -->

Dynamic tag names

When writing generic components or templates, the specific concrete tag for an HTML element is not known yet. In those situations, the t-tag directive is useful. It simply dynamically evaluates an expression to use as a tag name. The template:

<t t-tag="tag">
    <span>content</span>
</t>

will be rendered as <div><span>content</span></div> if the tag context key is set to div.

Loops

In  Qweb templates, the t-foreach is used to iterate through the items. the t-as is used to set a name for a current item of the iteration loop.

<t t-foreach="[1, 2, 3]" t-as="i" t-key="i">
    <p><t t-esc="i"/></p>
</t>

will be rendered as:

<p>1</p>
<p>2</p>
<p>3</p>

Like conditions, t-foreach applies to the element bearing the directive’s attribute, and

<p t-foreach="[1, 2, 3]" t-as="i" t-key="i">
    <t t-esc="i"/>
</p>

is equivalent to the previous example.

An important difference should be made with the usual QWeb behaviour: Owl requires the presence of a t-key directive to be able to properly reconcile renderings.

t-foreach can iterate on any iterable and also has special support for objects and maps. It will expose the key of the current iteration as the contents of the t-as and the corresponding value with the same name and the suffix _value.

In addition to the name passed via t-as, t-foreach provides a few other useful variables (note: $as will be replaced with the name passed to t-as):

$as_value: the current iteration value, identical to $as for arrays and other iterables, but for objects and maps, 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 in the t-foreach are only available within 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.

<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 -->

Even though Owl tries to be as declarative as possible, the DOM does not fully expose its state declaratively in the DOM tree. For example, the scrolling state, the current user selection, the focused element, or the state of an input are not set as attributes in the DOM tree. This is why we use a virtual DOM algorithm to make sure we keep the actual DOM node instead of replacing it with a new one.

Consider the following situation: we have a list of two items [{text: “a”}, {text: “b”}] and we render them in this template:

<p t-foreach="items" t-as="item" t-key="item_index"><t t-esc="item.text"/></p>

The result will be two <p> tags with text a and b. Now, if we swap them and re-render the template, Owl needs to know what the intent is:

Should Owl actually swap the DOM nodes?

Or should it keep the DOM nodes but with updated text content?

This might look trivial, but it actually matters. These two possibilities lead to different results in some cases. For example, if the user selects the text of the first p, swapping them will keep the selection, while updating the text content will not.

There are many other cases where this is important: input tags with their values, CSS classes and animations, scroll position, etc…

So, the t-key is used to assign an identity or uniqueness to an element. It allows the owl to easily identify whether the different elements of a list are unique or not.

The above example could be modified by adding an ID: [{id: 1, text: “a”}, {id: 2, text: “b”}]. Then, the template could look like this:

<p t-foreach="items" t-as="item" t-key="item.id"><t t-esc="item.text"/></p>

The t-key directive is useful for lists (t-foreach). A key should be a unique number or string (objects will not work: they will be cast to the “[object Object]” string, which is obviously not unique).

Also, the key can be set on a t tag or on its children. The following variations are all equivalent:

<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>

If there is no t-key directive, Owl will use the index as a default key.

Note: The t-foreach directive only accepts arrays (lists) or objects. It does not work with other iterables, such as Set. However, it is only a matter of using the … javascript operator. For example:

<t t-foreach="[...items]" t-as="item">...</t>

The… operator will convert the Set (or any other iterable) into a list, which will work with Owl QWeb.

Sub Templates

The Qweb templates are used for the top-level rendering, and they can also be used inside other templates. It avoids duplication, and we can reduce and optimize the code by reusing the code. This is done using the t-call directives.

<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:

<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:

<div>
    This template was called with content:
    <em>content</em>
</div>

This can be used to define 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 simply the current rendering context. However, it may be useful to be able to specify a specific object as context. This can be done by using the t-call-context directive:

<t t-call="other-template" t-call-context="obj"/>

Dynamic sub-templates

The t-call directive can also be used to dynamically call a sub-template using string interpolation. For example:

<div t-name="main-template">
    <t t-call="{{template}}">
        <em>content</em>
    </t>
</div>

Here, the name of the template is obtained from the template value in the template rendering context.

Debugging

The javascript QWeb implementation provides two useful debugging directives:

t-debug adds a debugger statement during template rendering:

<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 to the console.log:

<t t-set="foo" t-value="42"/>
<t t-log="foo"/>

will print 42 on the console.

Inline templates

Most real applications will define their templates in an XML file to benefit from the XML ecosystem and to do some additional processing, such as translating them. However, in some cases, it is convenient to be able to define a template inline. To do so, you can use the xml helper function:

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>
  `;
    ...
}
mount(MyComponent, document.body);

This function simply generates a unique string id, and registers the template under that id in the internals of Owl, then returns the id.

Rendering svg

Owl components can be used to generate dynamic SVG graphs:

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 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" }] },
    ],
  };
}

This RootNode component will then display a live SVG representation of the graph described by the graph property. Note that there is a recursive structure here: the node component uses itself as a subcomponent.

Important note: Owl needs to properly set the namespace for each SVG element. Since Owl compiles each template separately, it is not able to determine easily if a template is supposed to be included in an SVG namespace or not. Therefore, Owl depends on a heuristic: if a tag is either svg, g or path, then it will be considered svg. In practice, this means that each component or each sub-template (included with t-call) should have one of these tags as root tags.

Restrictions

Note that Owl templates forbid the use of tags or attributes starting with the block-string. This restriction prevents name collisions with the internal code of Owl.

<div><block-1>this will not be accepted by Owl</block-1></div>

In conclusion, Odoo 16’s Qweb templates and operations offer a dynamic and user-friendly way to customize and enhance the platform’s functionality. These tools empower businesses to create visually appealing reports, documents, and views, while the flexibility of operations like loops and conditions allows for tailored solutions.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *