widget
rpc
async
- qweb
client_action
testing
+++ /dev/null
-QWeb
-====
-
-QWeb is the template engine used by the OpenERP Web Client. It is an
-XML-based templating language, similar to `Genshi
-<http://en.wikipedia.org/wiki/Genshi_(templating_language)>`_,
-`Thymeleaf <http://en.wikipedia.org/wiki/Thymeleaf>`_ or `Facelets
-<http://en.wikipedia.org/wiki/Facelets>`_ with a few peculiarities:
-
-* It's implemented fully in javascript and rendered in the browser.
-* Each template file (XML files) contains multiple templates, where
- template engine usually have a 1:1 mapping between template files
- and templates.
-* It has special support in OpenERP Web's
- :class:`~instance.web.Widget`, though it can be used outside of
- OpenERP Web (and it's possible to use :class:`~instance.web.Widget`
- without relying on the QWeb integration).
-
-The rationale behind using QWeb instead of a more popular template syntax is
-that its extension mechanism is very similar to the openerp view inheritance
-mechanism. Like openerp views a QWeb template is an xml tree and therefore
-xpath or dom manipulations are easy to performs on it.
-
-Here's an example demonstrating most of the basic QWeb features:
-
-.. code-block:: xml
-
- <templates>
- <div t-name="example_template" t-attf-class="base #{cls}">
- <h4 t-if="title"><t t-esc="title"/></h4>
- <ul>
- <li t-foreach="items" t-as="item" t-att-class="item_parity">
- <t t-call="example_template.sub">
- <t t-set="arg" t-value="item_value"/>
- </t>
- </li>
- </ul>
- </div>
- <t t-name="example_template.sub">
- <t t-esc="arg.name"/>
- <dl>
- <t t-foreach="arg.tags" t-as="tag" t-if="tag_index lt 5">
- <dt><t t-esc="tag"/></dt>
- <dd><t t-esc="tag_value"/></dd>
- </t>
- </dl>
- </t>
- </templates>
-
-rendered with the following context:
-
-.. code-block:: json
-
- {
- "class1": "foo",
- "title": "Random Title",
- "items": [
- { "name": "foo", "tags": {"bar": "baz", "qux": "quux"} },
- { "name": "Lorem", "tags": {
- "ipsum": "dolor",
- "sit": "amet",
- "consectetur": "adipiscing",
- "elit": "Sed",
- "hendrerit": "ullamcorper",
- "ante": "id",
- "vestibulum": "Lorem",
- "ipsum": "dolor",
- "sit": "amet"
- }
- }
- ]
- }
-
-will yield this section of HTML document (reformated for readability):
-
-.. code-block:: html
-
- <div class="base foo">
- <h4>Random Title</h4>
- <ul>
- <li class="even">
- foo
- <dl>
- <dt>bar</dt>
- <dd>baz</dd>
- <dt>qux</dt>
- <dd>quux</dd>
- </dl>
- </li>
- <li class="odd">
- Lorem
- <dl>
- <dt>ipsum</dt>
- <dd>dolor</dd>
- <dt>sit</dt>
- <dd>amet</dd>
- <dt>consectetur</dt>
- <dd>adipiscing</dd>
- <dt>elit</dt>
- <dd>Sed</dd>
- <dt>hendrerit</dt>
- <dd>ullamcorper</dd>
- </dl>
- </li>
- </ul>
- </div>
-
-API
----
-
-While QWeb implements a number of attributes and methods for
-customization and configuration, only two things are really important
-to the user:
-
-.. js:class:: QWeb2.Engine
-
- The QWeb "renderer", handles most of QWeb's logic (loading,
- parsing, compiling and rendering templates).
-
- OpenERP Web instantiates one for the user, and sets it to
- ``instance.web.qweb``. It also loads all the template files of the
- various modules into that QWeb instance.
-
- A :js:class:`QWeb2.Engine` also serves as a "template namespace".
-
- .. js:function:: QWeb2.Engine.render(template[, context])
-
- Renders a previously loaded template to a String, using
- ``context`` (if provided) to find the variables accessed
- during template rendering (e.g. strings to display).
-
- :param String template: the name of the template to render
- :param Object context: the basic namespace to use for template
- rendering
- :returns: String
-
- The engine exposes an other method which may be useful in some
- cases (e.g. if you need a separate template namespace with, in
- OpenERP Web, Kanban views get their own :js:class:`QWeb2.Engine`
- instance so their templates don't collide with more general
- "module" templates):
-
- .. js:function:: QWeb2.Engine.add_template(templates)
-
- Loads a template file (a collection of templates) in the QWeb
- instance. The templates can be specified as:
-
- An XML string
- QWeb will attempt to parse it to an XML document then load
- it.
-
- A URL
- QWeb will attempt to download the URL content, then load
- the resulting XML string.
-
- A ``Document`` or ``Node``
- QWeb will traverse the first level of the document (the
- child nodes of the provided root) and load any named
- template or template override.
-
- :type templates: String | Document | Node
-
- A :js:class:`QWeb2.Engine` also exposes various attributes for
- behavior customization:
-
- .. js:attribute:: QWeb2.Engine.prefix
-
- Prefix used to recognize :ref:`directives <qweb-directives>`
- during parsing. A string. By default, ``t``.
-
- .. js:attribute:: QWeb2.Engine.debug
-
- Boolean flag putting the engine in "debug mode". Normally,
- QWeb intercepts any error raised during template execution. In
- debug mode, it leaves all exceptions go through without
- intercepting them.
-
- .. js:attribute:: QWeb2.Engine.jQuery
-
- The jQuery instance used during :ref:`template inheritance
- <qweb-directives-inheritance>` processing. Defaults to
- ``window.jQuery``.
-
- .. js:attribute:: QWeb2.Engine.preprocess_node
-
- A ``Function``. If present, called before compiling each DOM
- node to template code. In OpenERP Web, this is used to
- automatically translate text content and some attributes in
- templates. Defaults to ``null``.
-
-.. _qweb-directives:
-
-Directives
-----------
-
-A basic QWeb template is nothing more than an XHTML document (as it
-must be valid XML), which will be output as-is. But the rendering can
-be customized with bits of logic called "directives". Directives are
-attributes elements prefixed by :js:attr:`~QWeb2.Engine.prefix` (this
-document will use the default prefix ``t``, as does OpenERP Web).
-
-A directive will usually control or alter the output of the element it
-is set on. If no suitable element is available, the prefix itself can
-be used as a "no-operation" element solely for supporting directives
-(or internal content, which will be rendered). This means:
-
-.. code-block:: xml
-
- <t>Something something</t>
-
-will simply output the string "Something something" (the element
-itself will be skipped and "unwrapped"):
-
-.. code-block:: javascript
-
- var e = new QWeb2.Engine();
- e.add_template('<templates>\
- <t t-name="test1"><t>Test 1</t></t>\
- <t t-name="test2"><span>Test 2</span></t>\
- </templates>');
- e.render('test1'); // Test 1
- e.render('test2'); // <span>Test 2</span>
-
-.. note::
-
- The conventions used in directive descriptions are the following:
-
- * directives are described as compound functions, potentially with
- optional sections. Each section of the function name is an
- attribute of the element bearing the directive.
-
- * a special parameter is ``BODY``, which does not have a name and
- designates the content of the element.
-
- * special parameter types (aside from ``BODY`` which remains
- untyped) are ``Name``, which designates a valid javascript
- variable name, ``Expression`` which designates a valid
- javascript expression, and ``Format`` which designates a
- Ruby-style format string (a literal string with
- ``#{Expression}`` inclusions executed and replaced by their
- result)
-
-.. note::
-
- ``Expression`` actually supports a few extensions on the
- javascript syntax: because some syntactic elements of javascript
- are not compatible with XML and must be escaped, text
- substitutions are performed from forms which don't need to be
- escaped. Thus the following "keyword operators" are available in
- an ``Expression``: ``and`` (maps to ``&&``), ``or`` (maps to
- ``||``), ``gt`` (maps to ``>``), ``gte`` (maps to ``>=``), ``lt``
- (maps to ``<``) and ``lte`` (maps to ``<=``).
-
-.. _qweb-directives-templates:
-
-Defining Templates
-++++++++++++++++++
-
-.. _qweb-directive-name:
-
-.. function:: t-name=name
-
- :param String name: an arbitrary javascript string. Each template
- name is unique in a given
- :js:class:`QWeb2.Engine` instance, defining a
- new template with an existing name will
- overwrite the previous one without warning.
-
- When multiple templates are related, it is
- customary to use dotted names as a kind of
- "namespace" e.g. ``foo`` and ``foo.bar`` which
- will be used either by ``foo`` or by a
- sub-widget of the widget used by ``foo``.
-
- Templates can only be defined as the children of the document
- root. The document root's name is irrelevant (it's not checked)
- but is usually ``<templates>`` for simplicity.
-
- .. code-block:: xml
-
- <templates>
- <t t-name="template1">
- <!-- template code -->
- </t>
- </templates>
-
- :ref:`t-name <qweb-directive-name>` can be used on an element with
- an output as well:
-
- .. code-block:: xml
-
- <templates>
- <div t-name="template2">
- <!-- template code -->
- </div>
- </templates>
-
- which ensures the template has a single root (if a template has
- multiple roots and is then passed directly to jQuery, odd things
- occur).
-
-.. _qweb-directives-output:
-
-Output
-++++++
-
-.. _qweb-directive-esc:
-
-.. function:: t-esc=content
-
- :param Expression content:
-
- Evaluates, html-escapes and outputs ``content``.
-
-.. _qweb-directive-raw:
-
-.. function:: t-raw=content
-
- :param Expression content:
-
- Similar to :ref:`t-esc <qweb-directive-esc>` but does *not*
- html-escape the result of evaluating ``content``. Should only ever
- be used for known-secure content, or will be an XSS attack vector.
-
-.. _qweb-directive-att:
-
-.. function:: t-att=map
-
- :param Expression map:
-
- Evaluates ``map`` expecting an ``Object`` result, sets each
- key:value pair as an attribute (and its value) on the holder
- element:
-
- .. code-block:: xml
-
- <span t-att="{foo: 3, bar: 42}"/>
-
- will yield
-
- .. code-block:: html
-
- <span foo="3" bar="42"/>
-
-.. function:: t-att-ATTNAME=value
-
- :param Name ATTNAME:
- :param Expression value:
-
- Evaluates ``value`` and sets it on the attribute ``ATTNAME`` on
- the holder element.
-
- If ``value``'s result is ``undefined``, suppresses the creation of
- the attribute.
-
-.. _qweb-directive-attf:
-
-.. function:: t-attf-ATTNAME=value
-
- :param Name ATTNAME:
- :param Format value:
-
- Similar to :ref:`t-att-* <qweb-directive-att>` but the value of
- the attribute is specified via a ``Format`` instead of an
- expression. Useful for specifying e.g. classes mixing literal
- classes and computed ones.
-
-.. _qweb-directives-flow:
-
-Flow Control
-++++++++++++
-
-.. _qweb-directive-set:
-
-.. function:: t-set=name (t-value=value | BODY)
-
- :param Name name:
- :param Expression value:
- :param BODY:
-
- Creates a new binding in the template context. If ``value`` is
- specified, evaluates it and sets it to the specified
- ``name``. Otherwise, processes ``BODY`` and uses that instead.
-
-.. _qweb-directive-if:
-
-.. function:: t-if=condition
-
- :param Expression condition:
-
- Evaluates ``condition``, suppresses the output of the holder
- element and its content of the result is falsy.
-
-.. _qweb-directive-foreach:
-
-.. function:: t-foreach=iterable [t-as=name]
-
- :param Expression iterable:
- :param Name name:
-
- Evaluates ``iterable``, iterates on it and evaluates the holder
- element and its body once per iteration round.
-
- If ``name`` is not specified, computes a ``name`` based on
- ``iterable`` (by replacing non-``Name`` characters by ``_``).
-
- If ``iterable`` yields a ``Number``, treats it as a range from 0
- to that number (excluded).
-
- While iterating, :ref:`t-foreach <qweb-directive-foreach>` adds a
- number of variables in the context:
-
- ``#{name}``
- If iterating on an array (or a range), the current value in
- the iteration. If iterating on an *object*, the current key.
- ``#{name}_all``
- The collection being iterated (the array generated for a
- ``Number``)
- ``#{name}_value``
- The current iteration value (current item for an array, value
- for the current item for an object)
- ``#{name}_index``
- The 0-based index of the current iteration round.
- ``#{name}_first``
- Whether the current iteration round is the first one.
- ``#{name}_parity``
- ``"odd"`` if the current iteration round is odd, ``"even"``
- otherwise. ``0`` is considered even.
-
-.. _qweb-directive-call:
-
-.. function:: t-call=template [BODY]
-
- :param String template:
- :param BODY:
-
- Calls the specified ``template`` and returns its result. If
- ``BODY`` is specified, it is evaluated *before* calling
- ``template`` and can be used to specify e.g. parameters. This
- usage is similar to `call-template with with-param in XSLT
- <http://zvon.org/xxl/XSLTreference/OutputOverview/xslt_with-param_frame.html>`_.
-
-.. _qweb-directives-inheritance:
-
-Template Inheritance and Extension
-++++++++++++++++++++++++++++++++++
-
-.. _qweb-directive-extend:
-
-.. function:: t-extend=template BODY
-
- :param String template: name of the template to extend
-
- Works similarly to OpenERP models: if used on its own, will alter
- the specified template in-place; if used in conjunction with
- :ref:`t-name <qweb-directive-name>` will create a new template
- using the old one as a base.
-
- ``BODY`` should be a sequence of :ref:`t-jquery
- <qweb-directive-jquery>` alteration directives.
-
- .. note::
-
- The inheritance in the second form is *static*: the parent
- template is copied and transformed when :ref:`t-extend
- <qweb-directive-extend>` is called. If it is altered later (by
- a :ref:`t-extend <qweb-directive-extend>` without a
- :ref:`t-name <qweb-directive-name>`), these changes will *not*
- appear in the "child" templates.
-
-.. _qweb-directive-jquery:
-
-.. function:: t-jquery=selector [t-operation=operation] BODY
-
- :param String selector: a CSS selector into the parent template
- :param operation: one of ``append``, ``prepend``, ``before``,
- ``after``, ``inner`` or ``replace``.
- :param BODY: ``operation`` argument, or alterations to perform
-
- * If ``operation`` is specified, applies the selector to the
- parent template to find a *context node*, then applies
- ``operation`` (as a jQuery operation) to the *context node*,
- passing ``BODY`` as parameter.
-
- .. note::
-
- ``replace`` maps to jQuery's `replaceWith(newContent)
- <http://api.jquery.com/replaceWith/>`_, ``inner`` maps to
- `html(htmlString) <http://api.jquery.com/html/>`_.
-
- * If ``operation`` is not provided, ``BODY`` is evaluated as
- javascript code, with the *context node* as ``this``.
-
- .. warning::
-
- While this second form is much more powerful than the first,
- it is also much harder to read and maintain and should be
- avoided. It is usually possible to either avoid it or
- replace it with a sequence of ``t-jquery:t-operation:``.
-
-Escape Hatches / debugging
-++++++++++++++++++++++++++
-
-.. _qweb-directive-log:
-
-.. function:: t-log=expression
-
- :param Expression expression:
-
- Evaluates the provided expression (in the current template
- context) and logs its result via ``console.log``.
-
-.. _qweb-directive-debug:
-
-.. function:: t-debug
-
- Injects a debugger breakpoint (via the ``debugger;`` statement) in
- the compiled template output.
-
-.. _qweb-directive-js:
-
-.. function:: t-js=context BODY
-
- :param Name context:
- :param BODY: javascript code
-
- Injects the provided ``BODY`` javascript code into the compiled
- template, passing it the current template context using the name
- specified by ``context``.
<t t-name="fixed-literal">
<div t-att-foo="'bar'"/>
</t>
+ <result id="fixed-literal"><![CDATA[<div foo="bar"></div>]]></result>
+
<t t-name="fixed-variable">
<div t-att-foo="value"/>
</t>
+ <params id="fixed-variable">{"value": "ok"}</params>
+ <result id="fixed-variable"><![CDATA[<div foo="ok"></div>]]></result>
<t t-name="tuple-literal">
<div t-att="['foo', 'bar']"/>
</t>
+ <result id="tuple-literal"><![CDATA[<div foo="bar"></div>]]></result>
+
<t t-name="tuple-variable">
- <div t-att="att"/>
+ <div t-att="value"/>
</t>
+ <params id="tuple-variable">{"value": ["foo", "bar"]}</params>
+ <result id="tuple-variable"><![CDATA[<div foo="bar"></div>]]></result>
- <t t-name="format-literal">
- <div t-attf-foo="bar"/>
- </t>
- <t t-name="format-value">
- <div t-attf-foo="b#{value}r"/>
- </t>
- <t t-name="format-expression">
- <div t-attf-foo="#{value + 37}"/>
- </t>
- <t t-name="format-multiple">
- <div t-attf-foo="a #{value1} is #{value2} of #{value3} ]"/>
+ <t t-name="object">
+ <div t-att="value"/>
</t>
+ <params id="object">{"value": {"a": 1, "b": 2, "c": 3}}</params>
+ <result id="object"><![CDATA[<div a="1" b="2" c="3"></div>]]></result>
- <t t-name="format2-literal">
+ <t t-name="format-literal">
<div t-attf-foo="bar"/>
</t>
- <t t-name="format2-value">
+ <result id="format-literal"><![CDATA[<div foo="bar"></div>]]></result>
+
+ <t t-name="format-value">
<div t-attf-foo="b{{value}}r"/>
</t>
- <t t-name="format2-expression">
+ <params id="format-value">{"value": "a"}</params>
+ <result id="format-value"><![CDATA[<div foo="bar"></div>]]></result>
+
+ <t t-name="format-expression">
<div t-attf-foo="{{value + 37}}"/>
</t>
- <t t-name="format2-multiple">
+ <params id="format-expression">{"value": 5}</params>
+ <result id="format-expression"><![CDATA[<div foo="42"></div>]]></result>
+
+ <t t-name="format-multiple">
<div t-attf-foo="a {{value1}} is {{value2}} of {{value3}} ]"/>
</t>
+ <params id="format-multiple">{
+ "value1": 0,
+ "value2": 1,
+ "value3": 2
+ }</params>
+ <result id="format-multiple"><![CDATA[
+ <div foo="a 0 is 1 of 2 ]"></div>
+ ]]></result>
</templates>
<templates>
- <t t-name="basic-callee">ok</t>
- <t t-name="callee-printsbody"><t t-esc="__content__"/></t>
- <t t-name="callee-uses-foo"><t t-esc="foo"/></t>
- <t t-name="callee-sets-foo"><t t-set="foo" t-value="'ok'"/></t>
+ <t t-name="_basic-callee">ok</t>
+ <t t-name="_callee-printsbody"><t t-esc="__content__"/></t>
+ <t t-name="_callee-uses-foo"><t t-esc="foo"/></t>
<t t-name="basic-caller">
- <t t-call="basic-callee"/>
+ <t t-call="_basic-callee"/>
</t>
+ <result id="basic-caller">ok</result>
+
<t t-name="with-unused-body">
- <t t-call="basic-callee">WHEEE</t>
+ <t t-call="_basic-callee">WHEEE</t>
</t>
+ <result id="with-unused-body">ok</result>
+
<t t-name="with-unused-setbody">
- <t t-call="basic-callee">
+ <t t-call="_basic-callee">
<t t-set="qux" t-value="3"/>
</t>
</t>
+ <result id="with-unused-setbody">ok</result>
+
<t t-name="with-used-body">
- <t t-call="callee-printsbody">ok</t>
+ <t t-call="_callee-printsbody">ok</t>
</t>
+ <result id="with-used-body">ok</result>
+
<t t-name="with-used-setbody">
- <t t-call="callee-uses-foo">
+ <t t-call="_callee-uses-foo">
<t t-set="foo" t-value="'ok'"/>
</t>
</t>
- <t t-name="in-context-import">
- <t t-call="callee-sets-foo" t-import='*'/>
+ <result id="with-used-setbody">ok</result>
+
+ <t t-name="inherit-context">
+ <t t-set="foo" t-value="1"/>
+ <t t-call="_callee-uses-foo"/> - <t t-esc="foo"/>
+ </t>
+ <result id="inherit-context">1 - 1</result>
+
+ <t t-name="scoped-parameter">
+ <t t-call="_basic-callee">
+ <t t-set="foo" t-value="42"/>
+ </t>
+ <!-- should not print anything -->
<t t-esc="foo"/>
</t>
+ <result id="scoped-parameter">
+ ok
+ </result>
</templates>
<templates>
- <t t-name="literal-conditional">
- <t t-if="true">ok</t>
- </t>
- <t t-name="boolean-value-conditional">
- <t t-if="value">ok</t>
- </t>
- <t t-name="boolean-value-conditional-false">
- <t t-if="value">fail</t>
- </t>
-
- <t t-name="negify">
- <t t-if="!false">ok</t>
- </t>
- <t t-name="equality">
- <t t-if="1 == 1">ok</t>
- </t>
- <t t-name="difference">
- <t t-if="1 != 2">ok</t>
- </t>
- <t t-name="and">
- <t t-if="true and true">ok</t>
- </t>
- <t t-name="and-js">
- <t t-if="true && true">ok</t>
- </t>
- <t t-name="or">
- <t t-if="false or true">ok</t>
- </t>
- <t t-name="or-js">
- <t t-if="false || true">ok</t>
+ <t t-name="boolean-value-condition">
+ <t t-if="condition">ok</t>
</t>
+ <params id="boolean-value-condition">{"condition": true}</params>
+ <result id="boolean-value-condition">ok</result>
- <t t-name="greater">
- <t t-if="2 gt 1">ok</t>
- </t>
- <t t-name="greater-js">
- <t t-if="2 > 1">ok</t>
- </t>
- <t t-name="lower">
- <t t-if="1 lt 2">ok</t>
- </t>
- <t t-name="lower-js">
- <t t-if="1 < 2">ok</t>
+ <t t-name="boolean-value-condition-false">
+ <t t-if="condition">fail</t>
</t>
+ <params id="boolean-value-condition-false">{"condition": false}</params>
+ <result id="boolean-value-condition-false"/>
- <t t-name="greater-or-equal">
- <t t-if="2 gte 1">o</t><t t-if="2 gte 2">k</t>
- </t>
- <t t-name="greater-or-equal-js">
- <t t-if="2 >= 1">o</t><t t-if="2 >= 2">k</t>
- </t>
- <t t-name="lower-or-equal">
- <t t-if="1 lte 2">o</t><t t-if="2 lte 2">k</t>
- </t>
- <t t-name="lower-or-equal-js">
- <t t-if="1 <= 2">o</t><t t-if="2 <= 2">k</t>
+ <t t-name="boolean-value-condition-missing">
+ <t t-if="condition">fail</t>
</t>
+ <result id="boolean-value-condition-missing"/>
</templates>
<templates>
-<t t-name="jquery-extend">
- <ul><li>one</li></ul>
-</t>
-<t t-extend="jquery-extend">
- <t t-jquery="ul" t-operation="append"><li>3</li></t>
-</t>
-<t t-extend="jquery-extend">
- <t t-jquery="ul li:first-child" t-operation="replace"><li>2</li></t>
-</t>
-<t t-extend="jquery-extend">
- <t t-jquery="ul" t-operation="prepend"><li>1</li></t>
- <t t-jquery="ul" t-operation="before"><hr/></t>
- <t t-jquery="ul" t-operation="after"><hr/></t>
-</t>
-<t t-extend="jquery-extend">
- <t t-jquery="ul">
- this.attr('class', 'main');
- </t>
-</t>
-<t t-extend="jquery-extend">
- <t t-jquery="hr:eq(1)" t-operation="replace"><footer></footer></t>
-</t>
-<t t-extend="jquery-extend">
- <t t-jquery="footer" t-operation="inner"><b>[[end]]</b></t>
-</t>
+ <!-- js-only -->
+ <t t-name="jquery-extend">
+ <ul><li>one</li></ul>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul" t-operation="append"><li>3</li></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul li:first-child" t-operation="replace"><li>2</li></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul" t-operation="prepend"><li>1</li></t>
+ <t t-jquery="ul" t-operation="before"><hr/></t>
+ <t t-jquery="ul" t-operation="after"><hr/></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="ul">this.attr('class', 'main');</t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="hr:eq(1)" t-operation="replace"><footer></footer></t>
+ </t>
+ <t t-extend="jquery-extend">
+ <t t-jquery="footer" t-operation="inner"><b>[[end]]</b></t>
+ </t>
+ <result id="jquery-extend"><![CDATA[
+ <hr/><ul class="main"><li>1</li><li>2</li><li>3</li></ul><footer><b>[[end]]</b></footer>
+]]></result>
-<t t-name="jquery-extend-clone" t-extend="jquery-extend">
- <t t-jquery="ul" t-operation="append"><li>[[cloned template]]</li></t>
-</t>
+ <t t-name="jquery-extend-clone" t-extend="jquery-extend">
+ <t t-jquery="ul" t-operation="append"><li>[[cloned template]]</li></t>
+ </t>
+ <result id="jquery-extend-clone"><![CDATA[
+ <ul><li>one</li><li>[[cloned template]]</li></ul>
+]]></result>
</templates>
<templates xml:space="preserve">
- <t t-name="repetition-text-content" t-foreach="seq">*</t>
- <t t-name="repetition-dom-content" t-foreach="seq"><b/></t>
- <b t-name="repetition-self" t-foreach="seq"/>
+ <t t-name="iter-items">
+ <t t-foreach="[3, 2, 1]" t-as="item">
+[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
+ </t>
+ <result id="iter-items">
+[0: 3 3]
+[1: 2 2]
+[2: 1 1]
+ </result>
- <t t-name="scope-self" t-foreach="seq" t-esc="seq"/>
- <t t-name="scope-value" t-foreach="seq" t-esc="seq_value"/>
- <t t-name="scope-index" t-foreach="seq" t-esc="seq_index"/>
- <t t-name="scope-first" t-foreach="seq"><t t-esc="seq_first"/> </t>
- <t t-name="scope-last" t-foreach="seq"><t t-esc="seq_last"/> </t>
- <t t-name="scope-parity" t-foreach="seq"><t t-esc="seq_parity"/> </t>
- <t t-name="scope-size" t-foreach="seq"><t t-esc="seq_size"/> </t>
+ <t t-name="iter-position">
+ <t t-foreach="5" t-as="item">
+-<t t-if="item_first"> first</t><t t-if="item_last"> last</t> (<t t-esc="item_parity"/>)</t>
+ </t>
+ <result id="iter-position">
+- first (even)
+- (odd)
+- (even)
+- (odd)
+- last (even)
+ </result>
- <t t-name="aliasing" t-foreach="seq" t-as="item" t-esc="item"/>
- <t t-name="loopvars-aliasing"
- t-foreach="seq" t-as="item"><t t-esc="item_parity"/> </t>
+ <!-- test integer param -->
+ <t t-name="iter-int">
+ <t t-foreach="3" t-as="item">
+[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
+ </t>
+ <result id="iter-int">
+[0: 0 0]
+[1: 1 1]
+[2: 2 2]
+ </result>
+
+ <!-- test dict param -->
+ <t t-name="iter-dict">
+ <t t-foreach="value" t-as="item">
+[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/> - <t t-esc="item_parity"/>]</t>
+ </t>
+ <params id="iter-dict">{"value": {"a": 1, "b": 2, "c": 3}}</params>
+ <result id="iter-dict">
+[0: a 1 - even]
+[1: b 2 - odd]
+[2: c 3 - even]
+ </result>
</templates>
<t t-name="esc-literal">
<t t-esc="'ok'"/>
</t>
+ <result id="esc-literal">ok</result>
+
<t t-name="esc-variable">
- <t t-esc="ok"/>
+ <t t-esc="var"/>
</t>
+ <params id="esc-variable">{"var": "ok"}</params>
+ <result id="esc-variable">ok</result>
+
<t t-name="esc-toescape">
- <t t-esc="ok"/>
- </t>
- <!-- formatted esc -->
- <t t-name="escf-literal">
- <t t-escf="ok"/>
- </t>
- <t t-name="escf-variable">
- <t t-escf="{{ ok }}"/>
- </t>
- <t t-name="escf-toescape">
- <t t-escf="{{ ok }}"/>
- </t>
- <t t-name="escf-mix">
- <t t-escf="[{{ ok }}]"/>
+ <t t-esc="var"/>
</t>
+ <params id="esc-toescape"><![CDATA[{"var": "<ok>"}]]></params>
+ <result id="esc-toescape"><![CDATA[<ok>]]></result>
+
<!-- raw, evaluates and returns @t-raw directly (no escaping) -->
<t t-name="raw-literal">
<t t-raw="'ok'"/>
</t>
+ <result id="raw-literal">ok</result>
+
<t t-name="raw-variable">
- <t t-raw="ok"/>
+ <t t-raw="var"/>
</t>
+ <params id="raw-variable">{"var": "ok"}</params>
+ <result id="raw-variable">ok</result>
+
<t t-name="raw-notescaped">
- <t t-raw="ok"/>
- </t>
- <!-- formatted raw -->
- <t t-name="rawf-literal">
- <t t-rawf="ok"/>
- </t>
- <t t-name="rawf-variable">
- <t t-rawf="{{ ok }}"/>
- </t>
- <t t-name="rawf-notescaped">
- <t t-rawf="{{ ok }}"/>
+ <t t-raw="var"/>
</t>
+ <params id="raw-notescaped"><![CDATA[{"var": "<ok>"}]]></params>
+ <result id="raw-notescaped"><![CDATA[<ok>]]></result>
</templates>
<templates>
<t t-name="set-from-attribute-literal">
- <t t-set="value" t-value="'ok'"/><t t-esc="value"/>
+ <t t-set="value" t-value="'ok'"/>
+ <t t-esc="value"/>
</t>
+ <result id="set-from-attribute-literal">
+ ok
+ </result>
+
<t t-name="set-from-body-literal">
- <t t-set="value">ok</t><t t-esc="value"/>
+ <t t-set="value">ok</t>
+ <t t-esc="value"/>
</t>
+ <result id="set-from-body-literal">
+ ok
+ </result>
<t t-name="set-from-attribute-lookup">
- <t t-set="stuff" t-value="value"/><t t-esc="stuff"/>
+ <t t-set="stuff" t-value="value"/>
+ <t t-esc="stuff"/>
</t>
+ <params id="set-from-attribute-lookup">
+ {"value": "ok"}
+ </params>
+ <result id="set-from-attribute-lookup">
+ ok
+ </result>
+
<t t-name="set-from-body-lookup">
- <t t-set="stuff"><t t-esc="value"/></t><t t-esc="stuff"/>
+ <t t-set="stuff">
+ <t t-esc="value"/>
+ </t>
+ <t t-esc="stuff"/>
+ </t>
+ <params id="set-from-body-lookup">
+ {"value": "ok"}
+ </params>
+ <result id="set-from-body-lookup">
+ ok
+ </result>
+
+ <t t-name="set-empty-body">
+ <t t-set="stuff"/>
+ <t t-esc="stuff"/>
+ </t>
+ <result id="set-empty-body"/>
+
+ <t t-name="t-value-priority">
+ <t t-set="value" t-value="1">2</t>
+ <t t-esc="value"/>
</t>
+ <result id="t-value-priority">1</result>
</templates>
function render(template, context) {
return trim(QWeb.render(template, context)).toLowerCase();
}
- $(document).ready(function() {
- QUnit.module("Basic output tests", {
- setup: function () {
- QUnit.stop();
- QWeb.add_template('qweb-test-output.xml', function () {
- QUnit.start();
- });
- },
- teardown: function () {
- QWeb.templates = [];
- QWeb.tag = {};
- QWeb.att = {};
- }
- });
- QUnit.test("Basic escaped output", function (assert) {
- assert.equal(render('esc-literal', {}), "ok", "Render a literal string");
- assert.equal(render('esc-variable', {ok: 'ok'}), "ok", "Render a string variable");
- assert.equal(render('esc-toescape', {ok: '<ok>'}), "<ok>", "Render a string with data to escape");
- });
- QUnit.test("Formatted escaped output", function (assert) {
- assert.equal(render('escf-literal', {}), "ok", "Render a literal string");
- assert.equal(render('escf-variable', {ok: 'ok'}), "ok", "Render a string variable");
- assert.equal(render('escf-toescape', {ok: '<ok>'}), "<ok>", "Render a string with data to escape");
- assert.equal(render('escf-mix', {ok: 'ok'}), "[ok]", "Render a string with additions around the format");
- });
- QUnit.test("Basic unescaped output", function (assert) {
- assert.equal(render('raw-literal', {}), "ok", "Render a literal string");
- assert.equal(render('raw-variable', {ok: 'ok'}), "ok", "Render a string variable");
- assert.equal(render('raw-notescaped', {ok: '<ok>'}), "<ok>", "Render a string with data not escaped");
- });
- QUnit.test("Formatted unescaped output", function (assert) {
- assert.equal(render('rawf-literal', {}), "ok", "Render a literal string");
- assert.equal(render('rawf-variable', {ok: 'ok'}), "ok", "Render a string variable");
- assert.equal(render('rawf-notescaped', {ok: '<ok>'}), "<ok>", "Render a string with data not escaped");
- });
-
- QUnit.module("Context-setting tests", {
+ /**
+ * Loads the template file, and executes all the test template in a
+ * qunit module $title
+ */
+ function test(title, template) {
+ QUnit.module(title, {
setup: function () {
+ var self = this;
+ this.qweb = new QWeb2.Engine();
QUnit.stop();
- QWeb.add_template('qweb-test-set.xml', function () {
+ this.qweb.add_template(template, function (_, doc) {
+ self.doc = doc;
QUnit.start();
- });
- },
- teardown: function () {
- QWeb.templates = [];
- QWeb.tag = {};
- QWeb.att = {};
+ })
}
});
- QUnit.test("Set literal value", function (assert) {
- assert.equal(render('set-from-attribute-literal', {}), "ok",
- "Set a literal value via @t-value");
- assert.equal(render('set-from-body-literal', {}), "ok",
- "Set a literal value via @t-set body");
- });
- QUnit.test("Set value looked up from context", function (assert) {
- assert.equal(render('set-from-attribute-lookup', {value: 'ok'}), "ok",
- "Set a value looked up in context via @t-value");
- assert.equal(render('set-from-body-lookup', {value: 'ok'}), 'ok',
- "Set a value looked up in context via @t-set body and @t-esc");
- });
+ QUnit.test('autotest', function (assert) {
+ var templates = this.qweb.templates;
+ for (var template in templates) {
+ if (!templates.hasOwnProperty(template)) { continue; }
+ // ignore templates whose name starts with _, they're
+ // helpers/internal
+ if (/^_/.test(template)) { continue; }
- QUnit.module("Conditionals", {
- setup: function () {
- QUnit.stop();
- QWeb.add_template('qweb-test-conditionals.xml', function () {
- QUnit.start();
- });
- },
- teardown: function () {
- QWeb.templates = [];
- QWeb.tag = {};
- QWeb.att = {};
- }
- });
- QUnit.test('Basic (single boolean) conditionals', function (assert) {
- assert.equal(render('literal-conditional', {}), 'ok',
- "Test on a literal value");
- assert.equal(render('boolean-value-conditional', {value: true}), 'ok',
- "Test on a truthy variable value");
- assert.equal(render('boolean-value-conditional-false', {value: false}), '',
- "Test on a falsy variable value");
- });
- QUnit.test('Boolean expressions in conditionals', function (assert) {
- assert.equal(render('negify', {}), 'ok',
- "Negative");
- assert.equal(render('equality', {}), 'ok',
- "Equality");
- assert.equal(render('difference', {}), 'ok',
- "Difference");
- assert.equal(render('and', {}), 'ok',
- "Boolean and");
- assert.equal(render('and-js', {}), 'ok',
- "Boolean and via manually escaped JS operator");
- assert.equal(render('or', {}), 'ok',
- "Boolean or");
- assert.equal(render('or-js', {}), 'ok',
- "Boolean or using JS operator");
- });
- QUnit.test('Comparison boolean tests in conditionals', function (assert) {
- assert.equal(render('greater', {}), 'ok',
- "Greater");
- assert.equal(render('greater-js', {}), 'ok',
- "Greater, JS operator");
- assert.equal(render('lower', {}), 'ok',
- "Lower");
- assert.equal(render('lower-js', {}), 'ok',
- "Lower, JS operator");
- assert.equal(render('greater-or-equal', {}), 'ok',
- "Greater or Equal");
- assert.equal(render('greater-or-equal-js', {}), 'ok',
- "Greater or Equal, JS operator");
- assert.equal(render('lower-or-equal', {}), 'ok',
- "Lower or Equal");
- assert.equal(render('lower-or-equal-js', {}), 'ok',
- "Lower or Equal, JS operator");
- });
+ var params = this.doc.querySelector('params#' + template);
+ var args = params ? JSON.parse(params.textContent) : {};
- QUnit.module("Attributes manipulation", {
- setup: function () {
- QUnit.stop();
- QWeb.add_template('qweb-test-attributes.xml', function () {
- QUnit.start();
- });
- },
- teardown: function () {
- QWeb.templates = [];
- QWeb.tag = {};
- QWeb.att = {};
- }
- });
- QUnit.test('Fixed-name attributes', function (assert) {
- assert.equal(render('fixed-literal', {}), '<div foo="bar"></div>',
- "Fixed name and literal attribute value");
- assert.equal(render('fixed-variable', {value: 'ok'}), '<div foo="ok"></div>',
- "Fixed name and variable attribute value");
- });
- QUnit.test('Tuple-based attributes', function (assert) {
- assert.equal(render('tuple-literal', {}), '<div foo="bar"></div>',
- "Tuple-based literal attributes");
- assert.equal(render('tuple-variable', {att: ['foo', 'bar']}), '<div foo="bar"></div>',
- "Tuple-based variable attributes");
- });
- QUnit.test('Fixed name, formatted value attributes', function (assert) {
- assert.equal(render('format-literal', {}), '<div foo="bar"></div>',
- "Literal format");
- assert.equal(render('format-value', {value:'a'}), '<div foo="bar"></div>',
- "Valued format");
- assert.equal(
- render('format-expression', {value: 5}),
- '<div foo="42"></div>',
- "Format strings are evaluated expressions");
- assert.equal(render('format-multiple', {
- value1: 0,
- value2: 1,
- value3: 2,
- }),
- '<div foo="a 0 is 1 of 2 ]"></div>',
- "each format string should be evaluated independently");
- });
- QUnit.test('Fixed name, jinja-formatted', function (assert) {
- assert.equal(render('format2-literal', {}), '<div foo="bar"></div>',
- "Literal format");
- assert.equal(render('format2-value', {value:'a'}), '<div foo="bar"></div>',
- "Valued format");
- assert.equal(
- render('format2-expression', {value: 5}),
- '<div foo="42"></div>',
- "Format strings are evaluated expressions");
- assert.equal(render('format2-multiple', {
- value1: 0,
- value2: 1,
- value3: 2,
- }),
- '<div foo="a 0 is 1 of 2 ]"></div>',
- "each format string should be evaluated independently");
- });
-
- QUnit.module("Template calling (including)", {
- setup: function () {
- QUnit.stop();
- QWeb.add_template('qweb-test-call.xml', function () {
- QUnit.start();
- });
- },
- teardown: function () {
- QWeb.templates = [];
- QWeb.tag = {};
- QWeb.att = {};
- }
- });
- QUnit.test('Trivial call invocation', function (assert) {
- assert.equal(render('basic-caller', {}), 'ok',
- "Direct call of a second template");
- });
- QUnit.test('Call invocation with body', function (assert) {
- assert.equal(render('with-unused-body', {}), 'ok',
- "Call of a second template with body unused");
- assert.equal(render('with-unused-setbody', {}), 'ok',
- "Call of a second template with body directives unused");
- });
- QUnit.test('Call invocation with body (used by callee)', function (assert) {
- assert.equal(render('with-used-body', {}), 'ok',
- "Call of a second template with body used");
- });
- QUnit.test('Call invocation with parameters set (in body)', function (assert) {
- assert.equal(render('with-used-setbody', {}), 'ok',
- "Call of a second template with parameters");
- });
- QUnit.test('Call invocation in-context (via import)', function (assert) {
- assert.equal(render('in-context-import', {}), 'ok',
- "Call with t-import (calls in current context)");
- });
-
- QUnit.module("Foreach", {
- setup: function () {
- QUnit.stop();
- QWeb.add_template('qweb-test-foreach.xml', function () {
- QUnit.start();
- });
- },
- teardown: function () {
- QWeb.templates = [];
- QWeb.tag = {};
- QWeb.att = {};
- }
- });
- var seq = [4,3,2,1,0];
- QUnit.test('Basic foreach repetition', function (assert) {
- assert.equal(QWeb.render('repetition-text-content', {seq:seq}), '*****',
- "Repetition of text content via foreach");
- assert.equal(QWeb.render('repetition-dom-content', {seq:seq}).toLowerCase(), '<b></b><b></b><b></b><b></b><b></b>',
- "Repetition of node content via foreach");
- assert.equal(QWeb.render('repetition-self', {seq:seq}).toLowerCase(), '<b></b><b></b><b></b><b></b><b></b>',
- "A node with a foreach repeats itself");
- });
- QUnit.test("Foreach scope content", function (assert) {
- assert.equal(QWeb.render('scope-self', {seq:seq}), '43210',
- "each value of the sequence is available via the sequence name");
- assert.equal(QWeb.render('scope-value', {seq:seq}), '43210',
- "each value of the sequence is also via the _value");
- assert.equal(QWeb.render('scope-index', {seq:seq}), '01234',
- "the current 0-based index is available via _index");
- assert.equal(QWeb.render('scope-first', {seq:seq}), 'true false false false false ',
- "_first says whether the current item is the first of the sequence");
- assert.equal(QWeb.render('scope-last', {seq:seq}), 'false false false false true ',
- "_last says whether the current item is the last of the sequence");
- assert.equal(QWeb.render('scope-parity', {seq:seq}), 'even odd even odd even ',
- "the parity (odd/even) of the current row is available via _parity");
- assert.equal(QWeb.render('scope-size', {seq:seq}), '5 5 5 5 5 ',
- "the total length of the sequence is available through _size");
- });
- QUnit.test('Name aliasing via t-as', function (assert) {
- assert.equal(QWeb.render('aliasing', {seq:seq}), '43210',
- "the inner value can be re-bound via t-as");
- assert.equal(QWeb.render('loopvars-aliasing', {seq:seq}), 'even odd even odd even ',
- "inner loop variables should be rebound as well");
- });
-
- QUnit.module("Template inheritance tests", {
- setup: function () {
- QUnit.stop();
- QWeb.add_template('qweb-test-extend.xml', function () {
- QUnit.start();
- });
- },
- teardown: function () {
- QWeb.templates = [];
- QWeb.tag = {};
- QWeb.att = {};
+ var results = this.doc.querySelector('result#' + template);
+ assert.equal(
+ trim(this.qweb.render(template, args)),
+ trim(results.textContent),
+ template);
}
});
+ }
+ $(document).ready(function() {
+ test("Output", 'qweb-test-output.xml');
+ test("Context-setting", 'qweb-test-set.xml');
+ test("Conditionals", 'qweb-test-conditionals.xml');
+ test("Attributes manipulation", 'qweb-test-attributes.xml');
+ test("Template calling (to the faraway pages)",
+ 'qweb-test-call.xml');
+ test("Foreach", 'qweb-test-foreach.xml');
- QUnit.test("jQuery extend", function (assert) {
- assert.equal(render('jquery-extend', {}), '<hr/><ul class="main"><li>1</li><li>2</li><li>3</li></ul><footer><b>[[end]]</b></footer>',
- "Extend template with jQuery");
- assert.equal(render('jquery-extend-clone', {}), '<ul><li>one</li><li>[[cloned template]]</li></ul>',
- "Clone template");
- });
+ test('Template inheritance', 'qweb-test-extend.xml');
});
</script>
var QWeb2 = {
expressions_cache: {},
RESERVED_WORDS: 'true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date'.split(','),
- ACTIONS_PRECEDENCE: 'foreach,if,call,set,escf,esc,rawf,raw,js,debug,log'.split(','),
+ ACTIONS_PRECEDENCE: 'foreach,if,call,set,esc,raw,js,debug,log'.split(','),
WORD_REPLACEMENT: {
'and': '&&',
'or': '||',
if (callback) {
new_dict['__content__'] = callback(context, new_dict);
}
- var r = context.engine._render(template, new_dict);
- if (_import) {
- if (_import === '*') {
- this.extend(old_dict, new_dict, ['__caller__', '__template__']);
- } else {
- _import = _import.split(',');
- for (var i = 0, ilen = _import.length; i < ilen; i++) {
- var v = _import[i];
- old_dict[v] = new_dict[v];
- }
- }
- }
- return r;
+ return context.engine._render(template, new_dict);
},
foreach: function(context, enu, as, old_dict, callback) {
if (enu != null) {
+ var index, jlen, cur;
var size, new_dict = this.extend({}, old_dict);
new_dict[as + "_all"] = enu;
var as_value = as + "_value",
as_parity = as + "_parity";
if (size = enu.length) {
new_dict[as + "_size"] = size;
- for (var j = 0, jlen = enu.length; j < jlen; j++) {
- var cur = enu[j];
+ for (index = 0, jlen = enu.length; index < jlen; index++) {
+ cur = enu[index];
new_dict[as_value] = cur;
- new_dict[as_index] = j;
- new_dict[as_first] = j === 0;
- new_dict[as_last] = j + 1 === size;
- new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
+ new_dict[as_index] = index;
+ new_dict[as_first] = index === 0;
+ new_dict[as_last] = index + 1 === size;
+ new_dict[as_parity] = (index % 2 == 1 ? 'odd' : 'even');
if (cur.constructor === Object) {
this.extend(new_dict, cur);
}
}
this.foreach(context, _enu, as, old_dict, callback);
} else {
- var index = 0;
+ index = 0;
for (var k in enu) {
if (enu.hasOwnProperty(k)) {
- var v = enu[k];
- new_dict[as_value] = v;
+ cur = enu[k];
+ new_dict[as_value] = cur;
new_dict[as_index] = index;
new_dict[as_first] = index === 0;
- new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
+ new_dict[as_parity] = (index % 2 == 1 ? 'odd' : 'even');
new_dict[as] = k;
callback(context, new_dict);
index += 1;
this.indent();
},
compile_action_call : function(value) {
- var _import = this.actions['import'] || '';
if (this.children.length === 0) {
- return this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + "));");
+ return this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict));");
} else {
- this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + ", function(context, dict) {");
+ this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, null, function(context, dict) {");
this.bottom("}));");
this.indent();
this.top("var r = [];");
+ this.format_expression(value)
+ "));");
},
- compile_action_escf : function (value) {
- this.top("r.push(context.engine.tools.html_escape("
- + this.string_interpolation(value)
- + '));');
- },
compile_action_raw : function(value) {
this.top("r.push(" + (this.format_expression(value)) + ");");
},
- compile_action_rawf: function (value) {
- this.top('r.push(' + this.string_interpolation(value) + ');');
- },
compile_action_js : function(value) {
this.top("(function(" + value + ") {");
this.bottom("})(dict);");
External identifiers are in the form :samp:`{module}.{id}` (e.g.
``account.invoice_graph``). From within a module, the
:samp:`{module}.` prefix can be left out.
+
+ format string
+ inspired by `jinja variables`_, format strings allow more easily
+ mixing literal content and computed content (expressions): content
+ between ``{{`` and ``}}`` is interpreted as an expression and
+ evaluated, other content is interpreted as literal strings and
+ displayed as-is
+
+.. _jinja variables: http://jinja.pocoo.org/docs/dev/templates/#variables
guides
reference
modules
+
+.. todolist::
return stuff()
To *override* a controller, :ref:`inherit <python:tut-inheritance>` from its
-class and override relevant methods::
+class and override relevant methods, re-exposing them if necessary::
class Extension(MyController):
@route()
Widgets
=======
+.. qweb integration: ``template`` is an (optional) automatically rendered
+ template
+
RPC
===
+.. highlight:: xml
+
.. _reference/qweb:
====
QWeb
====
-Basics
-======
+QWeb is the primary templating_ engine used by Odoo\ [#othertemplates]_. It
+is an XML templating engine\ [#genshif]_ and used mostly to generate HTML_
+fragments and pages.
+
+Template directives are specified as XML attributes prefixed with ``t-``,
+for instance ``t-if`` for :ref:`reference/qweb/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::
+
+ <t t-if="condition">
+ <p>Test</p>
+ </t>
+
+will result in::
+
+ <p>Test</p>
+
+if ``condition`` is true, but::
+
+ <div t-if="condition">
+ <p>Test</p>
+ </div>
+
+will result in::
+
+ <div>
+ <p>Test</p>
+ </div>
.. _reference/qweb/output:
data output
------------
+===========
+
+QWeb has a primary output directive which automatically HTML-escape its
+content limiting XSS_ risks when displaying user-provided content: ``esc``.
+
+``esc`` takes an expression, evaluates it and prints the content::
+
+ <p><t t-esc="value"/></p>
+
+rendered with the value ``value`` set to ``42`` yields::
+
+ <p>42</p>
+
+There is one other output directive ``raw`` which behaves the same as
+respectively ``esc`` but *does not HTML-escape its output*. It can be useful
+to display separately constructed markup (e.g. from functions) or already
+sanitized user-provided markup.
.. _reference/qweb/conditionals:
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.
.. _reference/qweb/loops:
loops
------
+=====
+
+QWeb has an iteration directive ``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::
+
+ <t t-foreach="[1, 2, 3]" t-as="i">
+ <p><t t-esc="i"/></p>
+ </t>
+
+will be rendered as::
+
+ <p>1</p>
+ <p>2</p>
+ <p>3</p>
+
+Like conditions, ``foreach`` applies to the element bearing the directive's
+attribute, and
+
+::
+
+ <p t-foreach="[1, 2, 3]" t-as="i">
+ <t t-esc="i"/>
+ </p>
+
+is equivalent to the previous example.
+
+``foreach`` can iterate on an array (the current item will be the current
+value), a mapping (the current item will be the current key) or an integer
+(equivalent to iterating on an array between 0 inclusive and the provided
+integer exclusive).
+
+In addition to the name passed via ``t-as``, ``foreach`` provides a few other
+variables for various data points (``$as`` is the name passed to ``t-as``):
+
+:samp:`{$as}_all`
+ the object being iterated over
+:samp:`{$as}_value`
+ the current iteration value, identical to ``$as`` for lists and integers,
+ but for mappings it provides the value (where ``$as`` provides the key)
+:samp:`{$as}_index`
+ the current iteration index (the first item of the iteration has index 0)
+:samp:`{$as}_size`
+ the size of the collection if it is available
+:samp:`{$as}_first`
+ whether the current item is the first of the iteration (equivalent to
+ :samp:`{$as}_index == 0`)
+:samp:`{$as}_last`
+ whether the current item is the last of the iteration (equivalent to
+ :samp:`{$as}_index + 1 == {$as}_size`), requires the iteratee's size be
+ available
+:samp:`{$as}_parity`
+ either ``"even"`` or ``"odd"``, the parity of the current iteration round
+:samp:`{$as}_even`
+ a boolean flag indicating that the current iteration round is on an even
+ index
+:samp:`{$as}_odd`
+ a boolean flag indicating that the current iteration round is on an odd
+ index
+
+.. _reference/qweb/attributes:
+
+attributes
+==========
+
+QWeb can compute attributes on-the-fly and set the result of the computation
+on the output node. This is done via the ``t-att`` (attribute) directive which
+exists in 3 different forms:
+
+:samp:`t-att-{$name}`
+ an attribute called ``$name`` is created, the attribute value is evaluated
+ and the result is set as the attribute's value::
+
+ <div t-att-a="42"/>
+
+ will be rendered as::
+
+ <div a="42"></div>
+:samp:`t-attf-{$name}`
+ same as previous, but the parameter is a :term:`format string`
+ instead of just an expression, often useful to mix literal and non-literal
+ string (e.g. classes)::
+
+ <t t-foreach="[1, 2, 3]" t-as="item">
+ <li t-attf-class="row {{ item_parity }}"><t t-esc="item"/></li>
+ </t>
+
+ will be rendered as::
+
+ <li class="row even">1</li>
+ <li class="row odd">2</li>
+ <li class="row even">3</li>
+:samp:`t-att=mapping`
+ if the parameter is a mapping, each (key, value) pair generates a new
+ attribute and its value::
+
+ <div t-att="{'a': 1, 'b': 2}"/>
+
+ will be rendered as::
+
+ <div a="1" b="2"></div>
+:samp:`t-att=pair`
+ if the parameter is a pair (tuple or array of 2 element), the first
+ item of the pair is the name of the attribute and the second item is the
+ value::
+
+ <div t-att="['a', 'b']"/>
+
+ will be rendered as::
+
+ <div a="b"></div>
+
+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 ``set`` directive, which takes the name of the variable
+to create. 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-esc="foo"/>
+
+ will print ``3``
+* if there is no ``t-value`` attribute, the node's body is rendered and set
+ as the variable's value::
+
+ <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 ``esc`` directive)
+
+ .. note:: using the result of this operation is a significant use-case for
+ the ``raw`` directive.
+
+calling 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::
+
+ <t t-call="other-template"/>
+
+This calls the named template with the execution context of the parent, if
+``other_template`` is defined as::
+
+ <p><t t-value="var"/></p>
+
+the call above will be rendered as ``<p/>`` (no content), but::
+
+ <t t-set="var" t-value="1"/>
+ <t t-call="other-template"/>
+
+will be rendered as ``<p>1</p>``.
+
+However this has the problem of being visible from outside the ``t-call``.
+Alternatively, content set in the body of the ``call`` directive will be
+evaluated *before* calling the sub-template, and can alter a local context::
+
+ <t t-call="other-template">
+ <t t-set="var" t-value="1"/>
+ </t>
+ <!-- "var" does not exist here -->
Python
======
Javascript
==========
-loading
+Exclusive directives
+--------------------
+
+The Javascript qweb implementation provides specific directives to handle
+defining and overloading/altering templates:
+
+defining templates
+''''''''''''''''''
+
+The ``t-name`` directive can only be placed at the top-level of a template
+file (direct children to the document root)::
+
+ <templates>
+ <t t-name="template-name">
+ <!-- template code -->
+ </t>
+ </templates>
+
+It takes no other parameter, but can be used with a ``<t>`` element or any
+other. With a ``<t>`` element, the ``<t>`` should have a single child.
+
+The template name is an arbitrary string, although when multiple templates
+are related (e.g. called sub-templates) it is customary to use dot-separated
+names to indicate hierarchical relationships.
+
+template inheritance
+''''''''''''''''''''
+
+Template inheritance is used to alter existing templates in-place, e.g. to
+add information to templates created by an other modules.
+
+Template inheritance is performed via the ``t-extend`` directive which takes
+the name of the template to alter as parameter.
+
+The alteration is then performed with any number of ``t-jquery``
+sub-directives::
+
+ <t t-extends="base.template">
+ <t t-jquery="ul" t-operation="append">
+ <li>new element</li>
+ </t>
+ </t>
+
+The ``t-jquery`` directives takes a `CSS selector`_. This selector is used
+on the extended template to select *context nodes* to which the specified
+``t-operation`` is applied:
+
+``append``
+ the node's body is appended at the end of the context node (after the
+ context node's last child)
+``prepend``
+ the node's body is prepended to the context node (inserted before the
+ context node's first child)
+``before``
+ the node's body is inserted right before the context node
+``after``
+ the node's body is inserted right after the context node
+``inner``
+ the node's body replaces the context node's children
+``replace``
+ the node's body is used to replace the context node itself
+No operation
+ if no ``t-operation`` is specified, the template body is interpreted as
+ javascript code and executed with the context node as ``this``
+
+ .. warning:: while much more powerful than other operations, this mode is
+ also much harder to debug and maintain, it is recommended to
+ avoid it
+
+debugging
+---------
+
+The javascript QWeb implementation provides a few debugging hooks:
+
+``t-log``
+ takes an expression parameter, evaluates the expression during rendering
+ and logs its result with ``console.log``
+``t-debug``
+ triggers a debugger breakpoint during template rendering
+``t-js``
+ the node's body is javascript code executed during template rendering.
+ Takes a ``context`` parameter, which is the name under which the rendering
+ context will be available in the ``t-js``'s body
+
+Helpers
-------
+
+.. js:attribute:: openerp.qweb
+
+ An instance of :js:class:`QWeb2.Engine` with all module-defined template
+ files loaded, and references to standard helper objects ``_``
+ (underscore), ``_t`` (translation function) and JSON_.
+
+ :js:func:`openerp.qweb.render <QWeb2.Engine.render>` can be used to
+ easily render basic module templates
+
+API
+---
+
+.. js:class:: QWeb2.Engine
+
+ The QWeb "renderer", handles most of QWeb's logic (loading,
+ parsing, compiling and rendering templates).
+
+ OpenERP Web instantiates one for the user, and sets it to
+ ``instance.web.qweb``. It also loads all the template files of the
+ various modules into that QWeb instance.
+
+ A :js:class:`QWeb2.Engine` also serves as a "template namespace".
+
+ .. js:function:: QWeb2.Engine.render(template[, context])
+
+ Renders a previously loaded template to a String, using
+ ``context`` (if provided) to find the variables accessed
+ during template rendering (e.g. strings to display).
+
+ :param String template: the name of the template to render
+ :param Object context: the basic namespace to use for template
+ rendering
+ :returns: String
+
+ The engine exposes an other method which may be useful in some
+ cases (e.g. if you need a separate template namespace with, in
+ OpenERP Web, Kanban views get their own :js:class:`QWeb2.Engine`
+ instance so their templates don't collide with more general
+ "module" templates):
+
+ .. js:function:: QWeb2.Engine.add_template(templates)
+
+ Loads a template file (a collection of templates) in the QWeb
+ instance. The templates can be specified as:
+
+ An XML string
+ QWeb will attempt to parse it to an XML document then load
+ it.
+
+ A URL
+ QWeb will attempt to download the URL content, then load
+ the resulting XML string.
+
+ A ``Document`` or ``Node``
+ QWeb will traverse the first level of the document (the
+ child nodes of the provided root) and load any named
+ template or template override.
+
+ :type templates: String | Document | Node
+
+ A :js:class:`QWeb2.Engine` also exposes various attributes for
+ behavior customization:
+
+ .. js:attribute:: QWeb2.Engine.prefix
+
+ Prefix used to recognize directives during parsing. A string. By
+ default, ``t``.
+
+ .. js:attribute:: QWeb2.Engine.debug
+
+ Boolean flag putting the engine in "debug mode". Normally,
+ QWeb intercepts any error raised during template execution. In
+ debug mode, it leaves all exceptions go through without
+ intercepting them.
+
+ .. js:attribute:: QWeb2.Engine.jQuery
+
+ The jQuery instance used during :ref:`template inheritance
+ <qweb-directives-inheritance>` processing. Defaults to
+ ``window.jQuery``.
+
+ .. js:attribute:: QWeb2.Engine.preprocess_node
+
+ A ``Function``. If present, called before compiling each DOM
+ node to template code. In OpenERP Web, this is used to
+ automatically translate text content and some attributes in
+ templates. Defaults to ``null``.
+
+.. [#genshif] it is similar in that to Genshi_, although it does not use (and
+ has no support for) `XML namespaces`_
+
+.. [#othertemplates] although it uses a few others, either for historical
+ reasons or because they remain better fits for the
+ use case. Odoo 8.0 still depends on Jinja_ and Mako_.
+
+.. _templating:
+ http://en.wikipedia.org/wiki/Template_processor
+
+.. _Jinja: http://jinja.pocoo.org
+.. _Mako: http://www.makotemplates.org
+.. _Genshi: http://genshi.edgewall.org
+.. _XML namespaces: http://en.wikipedia.org/wiki/XML_namespace
+.. _HTML: http://en.wikipedia.org/wiki/HTML
+.. _XSS: http://en.wikipedia.org/wiki/Cross-site_scripting
+.. _JSON: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
+.. _CSS selector: http://api.jquery.com/category/selectors/
e.set('data-oe-xpath', node_path)
if not e.get('data-oe-model'): return
- if set(('t-esc', 't-escf', 't-raw', 't-rawf')).intersection(e.attrib):
+ if {'t-esc', 't-raw'}.intersection(e.attrib):
# nodes which fully generate their content and have no reason to
# be branded because they can not sensibly be edited
self._pop_view_branding(e)