[IMP] qweb doc, tests
authorXavier Morel <xmo@openerp.com>
Tue, 9 Sep 2014 07:17:32 +0000 (09:17 +0200)
committerXavier Morel <xmo@openerp.com>
Mon, 6 Oct 2014 17:13:43 +0000 (19:13 +0200)
* 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)

17 files changed:
addons/web/doc/index.rst
addons/web/doc/qweb.rst [deleted file]
addons/web/static/lib/qweb/qweb-test-attributes.xml
addons/web/static/lib/qweb/qweb-test-call.xml
addons/web/static/lib/qweb/qweb-test-conditionals.xml
addons/web/static/lib/qweb/qweb-test-extend.xml
addons/web/static/lib/qweb/qweb-test-foreach.xml
addons/web/static/lib/qweb/qweb-test-output.xml
addons/web/static/lib/qweb/qweb-test-set.xml
addons/web/static/lib/qweb/qweb-test.js.html
addons/web/static/lib/qweb/qweb2.js
doc/glossary.rst
doc/index.rst
doc/reference/http.rst
doc/reference/javascript.rst
doc/reference/qweb.rst
openerp/addons/base/ir/ir_ui_view.py

index 35cd3b2..511a271 100644 (file)
@@ -37,7 +37,6 @@ Javascript
     widget
     rpc
     async
-    qweb
     client_action
     testing
 
diff --git a/addons/web/doc/qweb.rst b/addons/web/doc/qweb.rst
deleted file mode 100644 (file)
index a8225a7..0000000
+++ /dev/null
@@ -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``.
index d03c20e..bf6070f 100644 (file)
@@ -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="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>
index 3f7bd60..f71aca0 100644 (file)
@@ -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>
index 43d1657..1d038ee 100644 (file)
@@ -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 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 &amp;&amp; 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 &gt; 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 &lt; 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 &gt;= 1">o</t><t t-if="2 &gt;= 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 &lt;= 2">o</t><t t-if="2 &lt;= 2">k</t>
+    <t t-name="boolean-value-condition-missing">
+        <t t-if="condition">fail</t>
     </t>
+    <result id="boolean-value-condition-missing"/>
 </templates>
index 35181eb..007caef 100644 (file)
@@ -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');
-    </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>
index 8e33561..9a1e8ec 100644 (file)
@@ -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>
index 8725bb0..f91196a 100644 (file)
@@ -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[&lt;ok&gt;]]></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>
index 6a7b4f5..945fd4a 100644 (file)
@@ -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>
index e6095a6..aac579d 100644 (file)
         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>'}), "&lt;ok&gt;", "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>'}), "&lt;ok&gt;", "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>
 
index 08208fb..e080476 100644 (file)
@@ -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);");
index af197a2..d22042b 100644 (file)
         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
index 5440162..f83680e 100644 (file)
@@ -15,3 +15,5 @@ odoo developer documentation
     guides
     reference
     modules
+
+.. todolist::
index c179691..e36ddf9 100644 (file)
@@ -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()
index ae7b02c..07f38b7 100644 (file)
@@ -5,6 +5,9 @@ Javascript
 Widgets
 =======
 
+.. qweb integration: ``template`` is an (optional) automatically rendered
+   template
+
 RPC
 ===
 
index c993c0c..8e8a213 100644 (file)
+.. 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 ``&lt;li&gt;ok&lt;/li&gt;`` (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/
index 61e1840..540832d 100644 (file)
@@ -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)