The QWeb parser looks for special directives in the templates and replaces them with dynamically generated HTML. These directives are XML element attributes, and can be used in any valid tag or element, such as <div>
, <span>
, or <field>
.
Sometimes we want to use a QWeb directive but don't want to place it in any of the XML elements in our template. For those cases, we have a <t>
special element that can have QWeb directives, such as a t-if
or a t-foreach
, but is silent and won't have any output on the final XML/HTML produced.
The QWeb directives will frequently make use of evaluated expressions to produce different results depending on the current record values. There are two different QWeb implementations: client-side JavaScript, and server-side Python.
The reports and website pages use the server-side Python implementation. On the other hand, kanban views use the client-side JavaScript implementation. This means that the QWeb expression used in kanban views should be written using the JavaScript syntax, not Python.
When displaying a kanban view, the internal steps are roughly as follows:
read()
method to get the data for the fields in the templates.kanban-box
template and parse it using QWeb to output the final HTML fragments.This is not meant to be technically exact. It is just a mind map that can be useful to understand how things work in kanban views.
Next, we will learn about QWeb expressions evaluation and explore the available QWeb directives, using examples that enhance our to-do task kanban card.
Many of the QWeb directives use expressions that are evaluated to produce some result. When used from the client-side, as is the case for kanban views, these expressions are written in JavaScript. They are evaluated in a context that has a few useful variables available.
A record
object is available, representing the record being rendered, with the fields requested from the server. The field values can be accessed using either the raw_value
or the value
attributes:
raw_value
is the value returned by the read()
server method, so it's more suitable to use in condition expressions.value
is formatted according to the user settings, and is meant to be used for display in the user interface. This is typically relevant for date/datetime and float/monetary fields.The QWeb evaluation context also has references available for the JavaScript web client instance. To make use of them, a good understanding of the web client architecture is needed, but we won't be able to go into that in detail. For reference purposes, the following identifiers are available in QWeb expression evaluation:
widget
is a reference to the current KanbanRecord()
widget object, responsible for the rendering of the current record into a kanban card. It exposes some useful helper functions we can use.record
is a shortcut for widget.records
and provides access to the fields available, using dot notation.read_only_mode
indicates if the current view is in read mode (and not in edit mode). It is a shortcut for widget.view.options.read_only_mode
.instance
is a reference to the full web client instance.It is also noteworthy that some characters are not allowed inside expressions. The lower than sign (<
) is such a case. This is because of the XML standard, where such characters have special meaning and shouldn't be used on the XML content. A negated >=
is a valid alternative, but the common practice is to use the following alternative symbols that are available for inequality operations:
lt
is for less thanlte
is for less than or equal togt
is for greater thangte
is for greater than or equal toOur kanban card is using the t-attf
QWeb directive to dynamically set a class on the top <div>
element so that the card is colored depending on the color
field value. For this, the t-attf-
QWeb directive was used.
The t-attf-
directive dynamically generates tag attributes using string substitution. This allows for parts of larger strings generated dynamically, such as a URL address or CSS class names.
The directive looks for expression blocks that will be evaluated and replaced by the result. These are delimited either by {{
and }}
or by #{
and }
. The content of the blocks can be any valid JavaScript expression and can use any of the variables available for QWeb expressions, such as record
and widget
.
In our case, we also used the kanban_color()
JavaScript function, specially provided to map color index numbers into the CSS class color names.
As a more elaborate example, we can use this directive to dynamically change the color of the Deadline Date, so that overdue dates are shown in red.
For this, replace <field name="date_deadline"/>
in our kanban card with this:
<li t-attf-class="oe_kanban_text_{{ record.date_deadline.raw_value and !(record.date_deadline.raw_value > (new Date())) ? 'red' : 'black' }}"> <field name="date_deadline"/> </li>
This results in either class="oe_kanban_text_red"
or class="oe_kanban_text_black"
, depending on the deadline date. Please note that, while the oe_kanban_text_red
CSS class is available in kanban views, the oe_kanban_text_black
CSS class does not exist and was used to better explain the point.
The t-att-
QWeb directive dynamically generates an attribute value by evaluating an expression. Our kanban card uses it to dynamically set some attributes on the <img>
tag.
The title
element is dynamically rendered using:
t-att-
The field .value
returns its value representation as it should be shown on the screen, for many-to-one fields, this is usually the related record's name
value. For users, this is the username. As a result, when hovering the mouse pointer over the image, you will see the corresponding username.
The src
tag is also dynamically generated, to provide the image corresponding to the responsible user. The image data is provided by the helper JavaScript function, kanban_image()
:
t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)"
The function parameters are: the model to read the image from, the field name to read, and the ID of the record. Here we used .raw_value
, to get the user's database ID instead of its representation text.
It doesn't stop there, and t-att-NAME
and t-attf-NAME
can be made to render any attribute, as the name of the generated attribute is taken from the NAME
suffix used.
A block of HTML can be repeated by iterating through a loop. We can use it to add the avatars of the task followers to the task's kanban card.
Let's start by rendering just the Partner IDs of the task, as follows:
<t t-foreach="record.message_partner_ids.raw_value" t-as="rec"> <t t-esc="rec" />; </t>
The t-foreach
directive accepts a JavaScript expression evaluating to a collection to iterate. In most cases, this will be just the name of a to-many relation field. It is used with a t-as
directive to set the name to be used to refer to each item in the iteration.
The t-esc
directive used next evaluates the provided expression, just the rec
variable name in this case, and renders it as safely escaped HTML.
In the previous example, we loop through the task followers, stored in the message_parter_ids
field. Since there is limited space on the kanban card, we could have used the slice()
JavaScript function to limit the number of followers to display, as shown in the following:
t-foreach="record.message_partner_ids.raw_value.slice(0, 3)"
The rec
variable holds each iteration's value, a Partner ID in this case. With this, we can rewrite the follower's loop as follows:
<t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)" t-as="rec"> <img t-att-src="kanban_image('res.partner', 'image_small', rec)" class="oe_avatar" width="24" height="24" /> </t>
For example, this could be added next to the responsible user image, in the right-hand footer.
A few helper variables are also available. Their name has as prefix the variable name defined in t-as
. In our example, we used rec
, so the helper variables available are as follows:
rec_index
is the iteration index, starting from zerorec_size
is the number of elements of the collectionrec_first
is true on the first element of the iterationrec_last
is true on the last element of the iterationrec_even
is true on even indexesrec_odd
is true on odd indexesrec_parity
is either odd
or even
, depending on the current indexrec_all
represents the object being iterated overrec_value
when iterating through a dictionary, {key: value}
, holds the value (rec
holds the key name)For example, we could make use of the following to avoid a trailing comma on our ID list:
<t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)" t-as="rec"> <t t-esc="rec" /> <t t-if="!rec_last">;</t> </t>
Our kanban view used the t-if
directive in the card option menu to make some options available depending on some conditions. The t-if
directive expects an expression to be evaluated in JavaScript when rendering kanban views in the client-side. The tag and its content will be rendered only if the condition evaluates to true.
As another example, to display the task effort estimate in the card kanban, only if it has a value, add the following after the date_deadline
field:
<t t-if="record.effort_estimate.raw_value gt 0"> <li>Estimate <field name="effort_estimate"/></li> </t>
We used a <t t-if="...">
element so that if the condition is false, the element produces no output. If it is true, only the contained <li>
element is rendered to the output. Notice that the condition expression used the gt
symbol instead of >
, to represent the greater than operator.
We used the <field>
element to render the field content. But field values can also be presented directly without a <field>
tag.
The t-esc
directive evaluates an expression and renders it as an HTML-escaped value, as shown in the following:
<t t-esc="record.message_parter_ids.raw_value" />
In some cases, and if the source data is ensured to be safe, t-raw
can be used to render the field raw value without any escaping, as shown in the following example:
<t t-raw="record.message_parter_ids.raw_value" />
For more complex logic, we can store the result of an expression into a variable to use it later in the template. This is to be done using the t-set
directive, naming the variable to set followed by the t-value
directive with the expression calculating the value to assign.
As an example, the following code renders missed deadlines in red, just as in the previous section, but uses a red_or_black
variable for the CSS class to use, as shown in the following:
<t t-set="red_or_black" t-value=" record.date_deadline.raw_value and record.date_deadline.raw_value lte (new Date()) ? 'oe_kanban_text_red' : ''" /> <li t-att-class="red_or_black"> <field name="date_deadline" /> </li>
Variables can also be assigned HTML content to a variable, as in the following example:
<t t-set="calendar_sign"> <span class="oe_e"></span> </t> <t t-raw="calendar_sign" />
The oe_e
CSS class uses the Entypo pictogram font. The HTML representation of the calendar sign is stored in a variable that can then be used when needed in the template. The Font Awesome icon set is also available out of the box, and could have been used.
QWeb templates can be reusable HTML snippet, that can be inserted in other templates. Instead of repeating the same HTML blocks over and over again, we can design building blocks to compose more complex user interface views.
Reusable templates are defined inside the <templates>
tag and identified by a top element with a t-name
other than kanban-box
. These other templates can then be included using the t-call
directive. This is true for the templates declared alongside in the same kanban view, somewhere else in the same addon module, or in a different addon.
The follower avatar list is something that could be isolated in a reusable snippet. Let's rework it to use a sub-template. We should start by adding another template to our XML file, inside the <templates>
element, after the <t t-name="kanban-box">
node, as shown in the following:
<t t-name="follower_avatars"> <div> <t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)" t-as="rec"> <img t-att-src="kanban_image('res.partner', 'image_small', rec)" class="oe_avatar" width="24" height="24" /> </t> </div> </t>
Calling it from the kanban-box
main template is quite straightforward. Instead of the <div>
element containing the for each
directive, we should use the following:
<t t-call="follower_avatars" />
To call templates defined in other addon modules, we need to use the module.name
full identifier, as we do with the other views. For instance, this snippet can be referred using the full identifier todo_kanban.follower_avatars
.
The called template runs in the same context as the caller, so any variable names available in the caller are also available when processing the called template.
A more elegant alternative is to pass arguments to the called template. This is done by setting variables inside the t-call
tag. These will be evaluated and made available in the sub-template context only, and won't exist in the caller's context.
We could use this to have the maximum number of follower avatars set by the caller instead of being hard-coded in the sub-template. First, we need to replace the fixed value, 3 with a variable, arg_max
for example:
<t t-name="follower_avatars"> <div> <t t-foreach="record.message_parter_ids.raw_value.slice(0, arg_max)" t-as="rec"> <img t-att-src="kanban_image('res.partner', 'image_small', rec)" class="oe_avatar" width="24" height="24" /> </t> </div> </t>
Then, define that variable's value when performing the sub-template call as follows:
<t t-call="follower_avatars"> <t t-set="arg_max" t-value="3" /> </t>
The entire content inside the t-call
element is also available to the sub-template through the magic variable 0
. Instead of argument variables, we can define an HTML code fragment that can be used in the sub-template with <t t-raw="0" />
.
We have gone through the most important QWeb directives, but there are a few more we should be aware of. We'll do a short explanation of them.
We have seen the t-att-NAME
and t-attf-NAME
style dynamic tag attributes. Additionally, the fixed t-att
directive can be used. It accepts either a key-value dictionary mapping or a pair (a two-element list).
Use the following mapping:
<p t-att="{'class': 'oe_bold', 'name': 'test1'}" />
This results in the following:
<p class="oe_bold" name="test1" />
Use the following pair:
<p t-att="['class', 'oe_bold']" />
This results in the following:
<p class="oe_bold" />