Adding QWeb dynamic content

The QWeb parser looks for special attributes (directives) in the templates and replaces them with dynamically generated HTML.

For kanban views, the parsing is done by client-side JavaScript. This means that the expression evaluations done by QWeb should be written using the JavaScript syntax, not Python.

When displaying a kanban view, the internal steps are roughly as follows:

  • Get the XML for the templates to render.
  • Call the server read() method to get the data for the fields in the templates.
  • Locate the kanban-box template and parse it using QWeb to output the final HTML fragments.
  • Inject the HTML in the browser's display (the DOM).

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 explore the several QWeb directives available, using examples that enhance our to-do task kanban card.

Conditional rendering with t-if

The t-if directive, used in the previous example, accepts a JavaScript expression to be evaluated. The tag and its content will be rendered if the condition evaluates to true.

For example, in the card kanban, to display the Task effort estimate, only if it has a value, after the date_deadline field, add the following:

<t t-if="record.effort_estimate.raw_value > 0"><li>Estimate <field name="effort_estimate"/></li></t>

The JavaScript evaluation context has a record object 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: This is the value returned by the read() server method, so it's more suitable to use in condition expressions.
  • value: This is formatted according to the user settings, and is meant to be used for display in the user interface.

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 detail. For reference purposes, the following identifiers are available in QWeb expression evaluation:

  • widget: This 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: This is a shortcut for widget.records and provides access to the fields available, using dot notation.
  • read_only_mode: This 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: This 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. You may use a negated >= instead. Anyway, alternative symbols are available for inequality operations as follows:

  • lt: This is for less than.
  • lte: This is for less than or equal to.
  • gt: This is for greater than.
  • gte: This is for greater than or equal to.

Rendering values with t-esc and t-raw

We have 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 its HTML escaped value, as shown in the following:

<t t-esc="record.message_follower_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 code:

<t t-raw="record.message_follower_ids.raw_value" />

Loop rendering with t-foreach

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

In the previous example, we loop through the task followers, stored in the message_follower_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_follower_ids.raw_value.slice(0, 3)"

The rec variable holds each iteration's value, a Partner ID in this case.

A few helper variables are also automatically created. They have the name defined in t-as as prefix. In our example we used rec, so the helper variables available are as follows:

  • rec_index: This is the iteration index, starting from zero.
  • rec_size: This is the number of elements of the collection.
  • rec_first: This is true on the first element of the iteration.
  • rec_last: This is true on the last element of the iteration.
  • rec_even: This is true on even indexes.
  • rec_odd: This is true on odd indexes.
  • rec_parity: This is either odd or even, depending on the current index.
  • rec_all: This represents the object being iterated over.
  • rec_value: This, 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_follower_ids.raw_value.slice(0, 3)"
   t-as="rec">
      <t t-esc="rec" /><t t-if="!rec_last">;</t>
</t>

Dynamic attributes with t-att- prefixes

We need to render the image for each follower. The final HTML would be something like <img src="URL" />, where the URL needs to be dynamically generated. QWeb can do this using t-att- prefixed directives. In this case, the src attribute can be rendered using a t-att-src directive with a JavaScript expression.

We also need to access the Partner's avatar stored in the database. Kanban views provide a helper function to conveniently generate that: kanban_image(). It accepts as arguments the model name, the field name holding the image we want, and the ID for the record to retrieve.

With this, we can rewrite the followers loop as follows:

<div>
  <t t-foreach="record.message_follower_ids.raw_value.slice(0, 3)"
     t-as="rec">
    <img t-att-src="kanban_image(
                      'res.partner', 'image_small', rec)"
         class="oe_kanban_image oe_kanban_avatar_smallbox"/>
  </t>
</div>

We used it for the src attribute, but any attribute can be dynamically generated with a t-att- prefix.

String substitution in attributes with t-attf- prefixes

Another way to dynamically generate tag attributes is using string substitution. This is helpful to have parts of larger strings generated dynamically, such as a URL address or CSS class names.

The directive contains 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.

Now let's replace <field name="date_deadline"/> in our kanban card with the following:

<span 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"/>
</span>

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.

Tip

The lower than sign (<) is not allowed in the expressions, and we chose to work around this by using a negated greater than comparison. Another possibility would be to use the lt (lower than) symbol instead.

Setting variables with t-set

For more complex logic, we can store the result of an expression into a variable to be used 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 assigned value.

As an example, the following code renders missed deadlines in red, just as in the previous section, but uses a variable with 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' : ''" />
<span t-att-class="red_or_black">
    <field name="date_deadline" />
</span>

It is also possible to assign HTML content to a variable, as in the following example:

<t t-set="calendar_sign">
    <span class="oe_e">&#128197;</span>
</t>
<t t-raw="calendar_sign" />

The oe_e CSS class uses the Entypo pictogram font. The HTML representation for the calendar sign is stored in a variable that can then be used when needed in the template.

Calling other templates with t-call

We can have QWeb templates for HTML snippets that are reused in other templates. This makes it possible to have buildings blocks to use for composing the user interface views.

Additional templates are defined inside the <templates> tag and identified by a top element with a t-name other than kanban-box. A template can be included using the t-call directive.

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_follower_ids.raw_value.slice(0, 3)"
     t-as="rec">
    <img t-att-src="kanban_image(
         'res.partner', 'image_small', rec)"
         class="oe_kanban_image oe_kanban_avatar_smallbox"/>
  </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" />

We can also call templates defined in other modules. For this we just 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 solution 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 "3" fixed value by a variable, arg_max for example:

<t t-name="follower_avatars">
<div>
  <t t-foreach="record.message_follower_ids.raw_value.slice(
     0, arg_max)" t-as="rec">
    <img t-att-src="kanban_image(
         'res.partner', 'image_small', rec)"
         class="oe_kanban_image oe_kanban_avatar_smallbox"/>
  </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 the argument variables, we can define an HTML code fragment that could be inserted in the sub-template using <t t-raw="0" />.

Other QWeb directives

We have gone through through the most important QWeb directives, but there are a few more we should be aware of. We'll do a short explanation on them.

We have seen 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" />

Advanced kanban elements

We've seen the basics about kanban views and QWeb templates. There are still a few techniques we can use to bring a richer user experience to our kanban cards.

Adding a kanban card option menu

Kanban cards can have an option menu, placed at the top right. Usual actions are to edit or delete the record, but any action callable from a button is possible. There is also available a widget to set the card's color.

The following is a baseline HTML code for the option menu to be added at the top of the oe_kanban_content element:

<div class="oe_dropdown_kanban oe_dropdown_toggle">
    <span class="oe_e">í</span>
    <ul class="oe_dropdown_menu">
        <t t-if="widget.view.is_action_enabled('edit')">
        <li><a type="edit">Edit...</a></li>
        </t>
        <t t-if="widget.view.is_action_enabled('delete')">
        <li><a type="delete">Delete</a></li>
        </t>
        <!-- Color picker option: -->
        <li><ul class="oe_kanban_colorpicker"
                data-field="color"/></li>
    </ul>
</div>

It is basically an HTML list of <a> elements. The Edit and Delete options use QWeb to make them visible only when their actions are enabled on the view. The widget.view.is_action_enabled function allows us to inspect if the edit and delete actions are available and to decide what to make available to the current user.

Adding colors to kanban cards

The color picker option allows the user to choose the color of a kanban card. The color is stored in a model field as a numeric index.

We should start by adding this field to the to-do task model, by adding to todo_kanban/todo_model.py the following line:

    color = fields.Integer('Color Index')

Here we used the usual name for the field, color, and this is what is expected in the data-field attribute on the color picker.

Next, for the colors selected with the picker to have any effect on the card, we must add some dynamic CSS based on the color field value. On the kanban view, just before the <templates> tag, we must also declare the color field, as shown in the following:

<field name="color" />

And, we need to replace the kanban card top element, <div class="oe_kanban_card">, with the following:

<div t-attf-class="oe_kanban_card
                   #{kanban_color(record.color.raw_value)}">

The kanban_color helper function does the translation of the color index into the corresponding CSS class name.

And that's it! You can now enjoy changing the kanban card colors at your will!

Using text ellipsis for long texts

Sometimes field texts may be too long to properly present in a kanban card. One way to avoid this is to cut the excessive text replacing it by an ellipsis (...). A helper function for this is available in kanban views.

For example, to limit our to-do task titles to the first 32 characters, we should replace the <field name="name" /> element with the following:

<t t-esc="kanban_text_ellipsis(record.name.value, 32)" />

Custom CSS and JavaScript assets

As we have seen, kanban views are mostly HTML and make heavy use of CSS classes. We have been introducing some frequently used CSS classes provided by the standard product. But for best results, modules can also add their own CSS.

We are not going into details here on how to write CSS, but it's relevant to explain how a module can add its own CSS (and JavaScript) web assets. Odoo assets are declared in the assets_backend template. To add our module assets, we should extend that template. The XML file for this is usually placed inside a views/ module subdirectory.

The following is a sample XML file to add a CSS and a JavaScript file to the todo_kanban module, and it could be at todo_kanban/views/todo_kanban_assets.xml:

<?xml version="1.0" encoding="utf-8"?>
<openerp>
  <data>
    <template id="assets_backend"
              inherit_id="web.assets_backend"
              name="Todo Kanban Assets" >
      <xpath expr="." position="inside">
        <link rel="stylesheet"
         href="/todo_kanban/static/src/css/todo_kanban.css"
        />
        <script type="text/javascript"
         src="/todo_kanban/static/src/js/todo_kanban.js">
        </script>
      </xpath>
    </template>
  </data>
</openerp>

As usual, it should be referenced in the __openerp__.py descriptor file. Notice that the assets are located inside a /static/src subdirectory. This is not required, but is a convention used in all standard modules and is the recommended practice.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset