[IMP] qweb doc, tests
* document qweb based (mostly) on JS version * convert JS qweb tests to (mostly) language-independent XML so they can be used for JS and Python implementations * add some more tests (e.g. precedence between t-value and body in t-set) * remove ``t-import`` * fix parity in foreach(dict) (and rename some variables to make array and object versions more similar)
This commit is contained in:
parent
bed6b01c53
commit
494dcbd0e3
|
@ -37,7 +37,6 @@ Javascript
|
|||
widget
|
||||
rpc
|
||||
async
|
||||
qweb
|
||||
client_action
|
||||
testing
|
||||
|
||||
|
|
|
@ -1,529 +0,0 @@
|
|||
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``.
|
|
@ -2,40 +2,57 @@
|
|||
<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="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="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>
|
||||
<result id="format-literal"><![CDATA[<div foo="bar"></div>]]></result>
|
||||
|
||||
<t t-name="format2-literal">
|
||||
<div t-attf-foo="bar"/>
|
||||
</t>
|
||||
<t t-name="format2-value">
|
||||
<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>
|
||||
|
|
|
@ -1,30 +1,51 @@
|
|||
<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>
|
||||
|
|
|
@ -1,59 +1,18 @@
|
|||
<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-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="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-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">
|
||||
<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 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>
|
||||
|
|
|
@ -1,32 +1,37 @@
|
|||
<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');
|
||||
<!-- js-only -->
|
||||
<t t-name="jquery-extend">
|
||||
<ul><li>one</li></ul>
|
||||
</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>
|
||||
<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>
|
||||
|
|
|
@ -1,17 +1,46 @@
|
|||
<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>
|
||||
|
|
|
@ -3,44 +3,36 @@
|
|||
<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>
|
||||
|
|
|
@ -1,15 +1,53 @@
|
|||
<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>
|
||||
|
|
|
@ -15,286 +15,52 @@
|
|||
function render(template, context) {
|
||||
return trim(QWeb.render(template, context)).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
this.qweb.add_template(template, function (_, doc) {
|
||||
self.doc = doc;
|
||||
QUnit.start();
|
||||
})
|
||||
}
|
||||
});
|
||||
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; }
|
||||
|
||||
var params = this.doc.querySelector('params#' + template);
|
||||
var args = params ? JSON.parse(params.textContent) : {};
|
||||
|
||||
var results = this.doc.querySelector('result#' + template);
|
||||
assert.equal(
|
||||
trim(this.qweb.render(template, args)),
|
||||
trim(results.textContent),
|
||||
template);
|
||||
}
|
||||
});
|
||||
}
|
||||
$(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 = {};
|
||||
}
|
||||
});
|
||||
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("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", {
|
||||
setup: function () {
|
||||
QUnit.stop();
|
||||
QWeb.add_template('qweb-test-set.xml', function () {
|
||||
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.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");
|
||||
});
|
||||
|
||||
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 = {};
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
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': '||',
|
||||
|
@ -139,22 +139,11 @@ var QWeb2 = {
|
|||
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",
|
||||
|
@ -164,13 +153,13 @@ var QWeb2 = {
|
|||
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);
|
||||
}
|
||||
|
@ -184,14 +173,14 @@ var QWeb2 = {
|
|||
}
|
||||
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;
|
||||
|
@ -704,11 +693,10 @@ QWeb2.Element = (function() {
|
|||
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 = [];");
|
||||
|
@ -743,17 +731,9 @@ QWeb2.Element = (function() {
|
|||
+ 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);");
|
||||
|
|
|
@ -12,3 +12,12 @@
|
|||
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
|
||||
|
|
|
@ -15,3 +15,5 @@ odoo developer documentation
|
|||
guides
|
||||
reference
|
||||
modules
|
||||
|
||||
.. todolist::
|
||||
|
|
|
@ -59,7 +59,7 @@ and defining methods decorated with :func:`~openerp.http.route`::
|
|||
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()
|
||||
|
|
|
@ -5,6 +5,9 @@ Javascript
|
|||
Widgets
|
||||
=======
|
||||
|
||||
.. qweb integration: ``template`` is an (optional) automatically rendered
|
||||
template
|
||||
|
||||
RPC
|
||||
===
|
||||
|
||||
|
|
|
@ -1,26 +1,273 @@
|
|||
.. 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
|
||||
======
|
||||
|
@ -33,5 +280,196 @@ Bundles
|
|||
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/
|
||||
|
|
|
@ -894,7 +894,7 @@ class view(osv.osv):
|
|||
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)
|
||||
|
|
Loading…
Reference in New Issue