[MERGE] Forward-port of branch 13.0 to 14.0
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 19 KiB |
BIN
content/developer/reference/javascript/images/mobile_working.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 13 KiB |
BIN
content/developer/reference/javascript/images/runner.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
content/developer/reference/javascript/images/runner2.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
content/developer/reference/javascript/images/snackbar.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
content/developer/reference/javascript/images/tests2.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
content/developer/reference/javascript/images/tests3.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
content/developer/reference/javascript/images/toast.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
544
content/developer/reference/javascript/javascript_cheatsheet.rst
Normal file
@@ -0,0 +1,544 @@
|
||||
|
||||
.. _reference/jscs:
|
||||
|
||||
=====================
|
||||
Javascript Cheatsheet
|
||||
=====================
|
||||
|
||||
There are many ways to solve a problem in JavaScript, and in Odoo. However, the
|
||||
Odoo framework was designed to be extensible (this is a pretty big constraint),
|
||||
and some common problems have a nice standard solution. The standard solution
|
||||
has probably the advantage of being easy to understand for an odoo developers,
|
||||
and will probably keep working when Odoo is modified.
|
||||
|
||||
This document tries to explain the way one could solve some of these issues.
|
||||
Note that this is not a reference. This is just a random collection of recipes,
|
||||
or explanations on how to proceed in some cases.
|
||||
|
||||
|
||||
First of all, remember that the first rule of customizing odoo with JS is:
|
||||
*try to do it in python*. This may seem strange, but the python framework is
|
||||
quite extensible, and many behaviours can be done simply with a touch of xml or
|
||||
python. This has usually a lower cost of maintenance than working with JS:
|
||||
|
||||
- the JS framework tends to change more, so JS code needs to be more frequently
|
||||
updated
|
||||
- it is often more difficult to implement a customized behaviour if it needs to
|
||||
communicate with the server and properly integrate with the javascript framework.
|
||||
There are many small details taken care by the framework that customized code
|
||||
needs to replicate. For example, responsiveness, or updating the url, or
|
||||
displaying data without flickering.
|
||||
|
||||
|
||||
.. note:: This document does not really explain any concepts. This is more a
|
||||
cookbook. For more details, please consult the javascript reference
|
||||
page (see :doc:`javascript_reference`)
|
||||
|
||||
Creating a new field widget
|
||||
===========================
|
||||
|
||||
This is probably a really common usecase: we want to display some information in
|
||||
a form view in a really specific (maybe business dependent) way. For example,
|
||||
assume that we want to change the text color depending on some business condition.
|
||||
|
||||
This can be done in three steps: creating a new widget, registering it in the
|
||||
field registry, then adding the widget to the field in the form view
|
||||
|
||||
- creating a new widget:
|
||||
This can be done by extending a widget:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var FieldChar = require('web.basic_fields').FieldChar;
|
||||
|
||||
var CustomFieldChar = FieldChar.extend({
|
||||
_renderReadonly: function () {
|
||||
// implement some custom logic here
|
||||
},
|
||||
});
|
||||
|
||||
- registering it in the field registry:
|
||||
The web client needs to know the mapping between a widget name and its
|
||||
actual class. This is done by a registry:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
|
||||
fieldRegistry.add('my-custom-field', CustomFieldChar);
|
||||
|
||||
- adding the widget in the form view
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="somefield" widget="my-custom-field"/>
|
||||
|
||||
Note that only the form, list and kanban views use this field widgets registry.
|
||||
These views are tightly integrated, because the list and kanban views can
|
||||
appear inside a form view).
|
||||
|
||||
Modifying an existing field widget
|
||||
==================================
|
||||
|
||||
Another use case is that we want to modify an existing field widget. For
|
||||
example, the voip addon in odoo need to modify the FieldPhone widget to add the
|
||||
possibility to easily call the given number on voip. This is done by *including*
|
||||
the FieldPhone widget, so there is no need to change any existing form view.
|
||||
|
||||
Field Widgets (instances of (subclass of) AbstractField) are like every other
|
||||
widgets, so they can be monkey patched. This looks like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var basic_fields = require('web.basic_fields');
|
||||
var Phone = basic_fields.FieldPhone;
|
||||
|
||||
Phone.include({
|
||||
events: _.extend({}, Phone.prototype.events, {
|
||||
'click': '_onClick',
|
||||
}),
|
||||
|
||||
_onClick: function (e) {
|
||||
if (this.mode === 'readonly') {
|
||||
e.preventDefault();
|
||||
var phoneNumber = this.value;
|
||||
// call the number on voip...
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Note that there is no need to add the widget to the registry, since it is already
|
||||
registered.
|
||||
|
||||
Modifying a main widget from the interface
|
||||
==========================================
|
||||
|
||||
Another common usecase is the need to customize some elements from the user
|
||||
interface. For example, adding a message in the home menu. The usual process
|
||||
in this case is again to *include* the widget. This is the only way to do it,
|
||||
since there are no registries for those widgets.
|
||||
|
||||
This is usually done with code looking like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var HomeMenu = require('web_enterprise.HomeMenu');
|
||||
|
||||
HomeMenu.include({
|
||||
render: function () {
|
||||
this._super();
|
||||
// do something else here...
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
Creating a new view (from scratch)
|
||||
==================================
|
||||
|
||||
Creating a new view is a more advanced topic. This cheatsheet will only
|
||||
highlight the steps that will probably need to be done (in no particular order):
|
||||
|
||||
- adding a new view type to the field ``type`` of ``ir.ui.view``::
|
||||
|
||||
class View(models.Model):
|
||||
_inherit = 'ir.ui.view'
|
||||
|
||||
type = fields.Selection(selection_add=[('map', "Map")])
|
||||
|
||||
- adding the new view type to the field ``view_mode`` of ``ir.actions.act_window.view``::
|
||||
|
||||
class ActWindowView(models.Model):
|
||||
_inherit = 'ir.actions.act_window.view'
|
||||
|
||||
view_mode = fields.Selection(selection_add=[('map', "Map")])
|
||||
|
||||
|
||||
- creating the four main pieces which makes a view (in JavaScript):
|
||||
we need a view (a subclass of ``AbstractView``, this is the factory), a
|
||||
renderer (from ``AbstractRenderer``), a controller (from ``AbstractController``)
|
||||
and a model (from ``AbstractModel``). I suggest starting by simply
|
||||
extending the superclasses:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var AbstractController = require('web.AbstractController');
|
||||
var AbstractModel = require('web.AbstractModel');
|
||||
var AbstractRenderer = require('web.AbstractRenderer');
|
||||
var AbstractView = require('web.AbstractView');
|
||||
|
||||
var MapController = AbstractController.extend({});
|
||||
var MapRenderer = AbstractRenderer.extend({});
|
||||
var MapModel = AbstractModel.extend({});
|
||||
|
||||
var MapView = AbstractView.extend({
|
||||
config: {
|
||||
Model: MapModel,
|
||||
Controller: MapController,
|
||||
Renderer: MapRenderer,
|
||||
},
|
||||
});
|
||||
|
||||
- adding the view to the registry:
|
||||
As usual, the mapping between a view type and the actual class needs to be
|
||||
updated:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
viewRegistry.add('map', MapView);
|
||||
|
||||
- implementing the four main classes:
|
||||
The ``View`` class needs to parse the ``arch`` field and setup the other
|
||||
three classes. The ``Renderer`` is in charge of representing the data in
|
||||
the user interface, the ``Model`` is supposed to talk to the server, to
|
||||
load data and process it. And the ``Controller`` is there to coordinate,
|
||||
to talk to the web client, ...
|
||||
|
||||
- creating some views in the database:
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="customer_map_view" model="ir.ui.view">
|
||||
<field name="name">customer.map.view</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="arch" type="xml">
|
||||
<map latitude="partner_latitude" longitude="partner_longitude">
|
||||
<field name="name"/>
|
||||
</map>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
Customizing an existing view
|
||||
============================
|
||||
|
||||
Assume we need to create a custom version of a generic view. For example, a
|
||||
kanban view with some extra *ribbon-like* widget on top (to display some
|
||||
specific custom information). In that case, this can be done with 3 steps:
|
||||
extend the kanban view (which also probably mean extending controllers/renderers
|
||||
and/or models), then registering the view in the view registry, and finally,
|
||||
using the view in the kanban arch (a specific example is the helpdesk dashboard).
|
||||
|
||||
- extending a view:
|
||||
Here is what it could look like:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var HelpdeskDashboardRenderer = KanbanRenderer.extend({
|
||||
...
|
||||
});
|
||||
|
||||
var HelpdeskDashboardModel = KanbanModel.extend({
|
||||
...
|
||||
});
|
||||
|
||||
var HelpdeskDashboardController = KanbanController.extend({
|
||||
...
|
||||
});
|
||||
|
||||
var HelpdeskDashboardView = KanbanView.extend({
|
||||
config: _.extend({}, KanbanView.prototype.config, {
|
||||
Model: HelpdeskDashboardModel,
|
||||
Renderer: HelpdeskDashboardRenderer,
|
||||
Controller: HelpdeskDashboardController,
|
||||
}),
|
||||
});
|
||||
|
||||
- adding it to the view registry:
|
||||
as usual, we need to inform the web client of the mapping between the name
|
||||
of the views and the actual class.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var viewRegistry = require('web.view_registry');
|
||||
viewRegistry.add('helpdesk_dashboard', HelpdeskDashboardView);
|
||||
|
||||
- using it in an actual view:
|
||||
we now need to inform the web client that a specific ``ir.ui.view`` needs to
|
||||
use our new class. Note that this is a web client specific concern. From
|
||||
the point of view of the server, we still have a kanban view. The proper
|
||||
way to do this is by using a special attribute ``js_class`` (which will be
|
||||
renamed someday into ``widget``, because this is really not a good name) on
|
||||
the root node of the arch:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="helpdesk_team_view_kanban" model="ir.ui.view" >
|
||||
...
|
||||
<field name="arch" type="xml">
|
||||
<kanban js_class="helpdesk_dashboard">
|
||||
...
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
.. note::
|
||||
|
||||
Note: you can change the way the view interprets the arch structure. However,
|
||||
from the server point of view, this is still a view of the same base type,
|
||||
subjected to the same rules (rng validation, for example). So, your views still
|
||||
need to have a valid arch field.
|
||||
|
||||
Promises and asynchronous code
|
||||
===============================
|
||||
|
||||
For a very good and complete introduction to promises, please read this excellent article https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/async%20%26%20performance/ch3.md
|
||||
|
||||
Creating new Promises
|
||||
-----------------------
|
||||
- turn a constant into a promise
|
||||
There are 2 static functions on Promise that create a resolved or rejected promise based on a constant:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var p = Promise.resolve({blabla: '1'}); // creates a resolved promise
|
||||
p.then(function (result) {
|
||||
console.log(result); // --> {blabla: '1'};
|
||||
});
|
||||
|
||||
|
||||
var p2 = Promise.reject({error: 'error message'}); // creates a rejected promise
|
||||
p2.catch(function (reason) {
|
||||
console.log(reason); // --> {error: 'error message');
|
||||
});
|
||||
|
||||
|
||||
.. note:: Note that even if the promises are created already resolved or rejected, the `then` or `catch` handlers will still be called asynchronously.
|
||||
|
||||
|
||||
- based on an already asynchronous code
|
||||
Suppose that in a function you must do a rpc, and when it is completed set the result on this.
|
||||
The `this._rpc` is a function that returns a `Promise`.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function callRpc() {
|
||||
var self = this;
|
||||
return this._rpc(...).then(function (result) {
|
||||
self.myValueFromRpc = result;
|
||||
});
|
||||
}
|
||||
|
||||
- for callback based function
|
||||
Suppose that you were using a function `this.close` that takes as parameter a callback that is called when the closing is finished.
|
||||
Now suppose that you are doing that in a method that must send a promise that is resolved when the closing is finished.
|
||||
|
||||
.. code-block:: javascript
|
||||
:linenos:
|
||||
|
||||
function waitForClose() {
|
||||
var self = this;
|
||||
return new Promise (function(resolve, reject) {
|
||||
self.close(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
* line 2: we save the `this` into a variable so that in an inner function, we can access the scope of our component
|
||||
* line 3: we create and return a new promise. The constructor of a promise takes a function as parameter. This function itself has 2 parameters that we called here `resolve` and `reject`
|
||||
- `resolve` is a function that, when called, puts the promise in the resolved state.
|
||||
- `reject` is a function that, when called, puts the promise in the rejected state. We do not use reject here and it can be omitted.
|
||||
* line 4: we are calling the function close on our object. It takes a function as parameter (the callback) and it happens that resolve is already a function, so we can pass it directly. To be clearer, we could have written:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
return new Promise (function (resolve) {
|
||||
self.close(function () {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
- creating a promise generator (calling one promise after the other *in sequence* and waiting for the last one)
|
||||
Suppose that you need to loop over an array, do an operation *in sequence* and resolve a promise when the last operation is done.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function doStuffOnArray(arr) {
|
||||
var done = Promise.resolve();
|
||||
arr.forEach(function (item) {
|
||||
done = done.then(function () {
|
||||
return item.doSomethingAsynchronous();
|
||||
});
|
||||
});
|
||||
return done;
|
||||
}
|
||||
|
||||
This way, the promise you return is effectively the last promise.
|
||||
- creating a promise, then resolving it outside the scope of its definition (anti-pattern)
|
||||
.. note:: we do not recommend using this, but sometimes it is useful. Think carefully for alternatives first...
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
...
|
||||
var resolver, rejecter;
|
||||
var prom = new Promise(function (resolve, reject){
|
||||
resolver = resolve;
|
||||
rejecter = reject;
|
||||
});
|
||||
...
|
||||
|
||||
resolver("done"); // will resolve the promise prom with the result "done"
|
||||
rejecter("error"); // will reject the promise prom with the reason "error"
|
||||
|
||||
Waiting for Promises
|
||||
--------------------
|
||||
- waiting for a number of Promises
|
||||
if you have multiple promises that all need to be waited, you can convert them into a single promise that will be resolved when all the promises are resolved using Promise.all(arrayOfPromises).
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var prom1 = doSomethingThatReturnsAPromise();
|
||||
var prom2 = Promise.resolve(true);
|
||||
var constant = true;
|
||||
|
||||
var all = Promise.all([prom1, prom2, constant]); // all is a promise
|
||||
// results is an array, the individual results correspond to the index of their
|
||||
// promise as called in Promise.all()
|
||||
all.then(function (results) {
|
||||
var prom1Result = results[0];
|
||||
var prom2Result = results[1];
|
||||
var constantResult = results[2];
|
||||
});
|
||||
return all;
|
||||
|
||||
|
||||
- waiting for a part of a promise chain, but not another part
|
||||
If you have an asynchronous process that you want to wait to do something, but you also want to return to the caller before that something is done.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function returnAsSoonAsAsyncProcessIsDone() {
|
||||
var prom = AsyncProcess();
|
||||
prom.then(function (resultOfAsyncProcess) {
|
||||
return doSomething();
|
||||
});
|
||||
/* returns prom which will only wait for AsyncProcess(),
|
||||
and when it will be resolved, the result will be the one of AsyncProcess */
|
||||
return prom;
|
||||
}
|
||||
|
||||
Error handling
|
||||
--------------
|
||||
|
||||
- in general in promises
|
||||
The general idea is that a promise should not be rejected for control flow, but should only be rejected for errors.
|
||||
When that is the case, you would have multiple resolutions of your promise with, for instance status codes that you would have to check in the `then` handlers and a single `catch` handler at the end of the promise chain.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function a() {
|
||||
x.y(); // <-- this is an error: x is undefined
|
||||
return Promise.resolve(1);
|
||||
}
|
||||
function b() {
|
||||
return Promise.reject(2);
|
||||
}
|
||||
|
||||
a().catch(console.log); // will log the error in a
|
||||
a().then(b).catch(console.log); // will log the error in a, the then is not executed
|
||||
b().catch(console.log); // will log the rejected reason of b (2)
|
||||
Promise.resolve(1)
|
||||
.then(b) // the then is executed, it executes b
|
||||
.then(...) // this then is not executed
|
||||
.catch(console.log); // will log the rejected reason of b (2)
|
||||
|
||||
|
||||
|
||||
- in Odoo specifically
|
||||
In Odoo, it happens that we use promise rejection for control flow, like in mutexes and other concurrency primitives defined in module `web.concurrency`
|
||||
We also want to execute the catch for *business* reasons, but not when there is a coding error in the definition of the promise or of the handlers.
|
||||
For this, we have introduced the concept of `guardedCatch`. It is called like `catch` but not when the rejected reason is an error
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function blabla() {
|
||||
if (someCondition) {
|
||||
return Promise.reject("someCondition is truthy");
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
var promise = blabla();
|
||||
promise.then(function (result) { console.log("everything went fine"); })
|
||||
// this will be called if blabla returns a rejected promise, but not if it has an error
|
||||
promise.guardedCatch(function (reason) { console.log(reason); });
|
||||
|
||||
// ...
|
||||
|
||||
var anotherPromise =
|
||||
blabla().then(function () { console.log("everything went fine"); })
|
||||
// this will be called if blabla returns a rejected promise,
|
||||
// but not if it has an error
|
||||
.guardedCatch(console.log);
|
||||
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var promiseWithError = Promise.resolve().then(function () {
|
||||
x.y(); // <-- this is an error: x is undefined
|
||||
});
|
||||
promiseWithError.guardedCatch(function (reason) {console.log(reason);}); // will not be called
|
||||
promiseWithError.catch(function (reason) {console.log(reason);}); // will be called
|
||||
|
||||
|
||||
|
||||
Testing asynchronous code
|
||||
--------------------------
|
||||
|
||||
- using promises in tests
|
||||
In the tests code, we support the latest version of Javascript, including primitives like `async` and `await`. This makes using and waiting for promises very easy.
|
||||
Most helper methods also return a promise (either by being marked `async` or by returning a promise directly.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
QUnit.test("My test", async function (assert) {
|
||||
// making the function async has 2 advantages:
|
||||
// 1) it always returns a promise so you don't need to define `var done = assert.async()`
|
||||
// 2) it allows you to use the `await`
|
||||
assert.expect(1);
|
||||
|
||||
var form = await testUtils.createView({ ... });
|
||||
await testUtils.form.clickEdit(form);
|
||||
await testUtils.form.click('jquery selector');
|
||||
assert.containsOnce('jquery selector');
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("My test - no async - no done", function (assert) {
|
||||
// this function is not async, but it returns a promise.
|
||||
// QUnit will wait for for this promise to be resolved.
|
||||
assert.expect(1);
|
||||
|
||||
return testUtils.createView({ ... }).then(function (form) {
|
||||
return testUtils.form.clickEdit(form).then(function () {
|
||||
return testUtils.form.click('jquery selector').then(function () {
|
||||
assert.containsOnce('jquery selector');
|
||||
form.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
QUnit.test("My test - no async", function (assert) {
|
||||
// this function is not async and does not return a promise.
|
||||
// we have to use the done function to signal QUnit that the test is async and will be finished inside an async callback
|
||||
assert.expect(1);
|
||||
var done = assert.async();
|
||||
|
||||
testUtils.createView({ ... }).then(function (form) {
|
||||
testUtils.form.clickEdit(form).then(function () {
|
||||
testUtils.form.click('jquery selector').then(function () {
|
||||
assert.containsOnce('jquery selector');
|
||||
form.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
as you can see, the nicer form is to use `async/await` as it is clearer and shorter to write.
|
||||
2424
content/developer/reference/javascript/javascript_reference.rst
Normal file
211
content/developer/reference/javascript/mobile.rst
Normal file
@@ -0,0 +1,211 @@
|
||||
|
||||
.. _reference/mobile:
|
||||
|
||||
==================
|
||||
Mobile JavaScript
|
||||
==================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
In Odoo 10.0 we released a mobile app which allows you to access all **Odoo apps**
|
||||
(even your customized modules).
|
||||
|
||||
The application is a combination of **Odoo Web** and **Native Mobile
|
||||
components**. In other words it is a Odoo Web instance loaded inside a native, mobile, WebView container.
|
||||
|
||||
This page documents how you can access mobile native components like Camera,
|
||||
Vibration, Notification and Toast through Odoo Web (via JavaScript). For this, you
|
||||
do not need to be a mobile developer, if you know Odoo JavaScript API you can
|
||||
access all available mobile features.
|
||||
|
||||
.. warning:: These features work with **Odoo Enterprise 10.0+** only
|
||||
|
||||
How does it work?
|
||||
=================
|
||||
|
||||
Internal workings of the mobile application:
|
||||
|
||||
.. image:: images/mobile_working.jpg
|
||||
|
||||
Of course, it is a web page that loads on a Mobile Native Web container. But it
|
||||
is integrated in such a way that you can access native resources from your web
|
||||
JavaScript.
|
||||
|
||||
WebPages (Odoo Web) is on the top of each layer, where the second layer is a Bridge
|
||||
between Odoo Web (JS) and the native mobile components.
|
||||
|
||||
When any call from JavaScript is triggered it passes through Bridge and Bridge
|
||||
passes it to the native invoker to perform that action.
|
||||
|
||||
When the native component has done its work, it is passed to the Bridge again and
|
||||
you get the output in JavaScript.
|
||||
|
||||
Process time taken by the Native component depends on what you are requesting
|
||||
from the Native resources. For example the Camera or GPS Location.
|
||||
|
||||
How to use it?
|
||||
==============
|
||||
|
||||
Just like the Odoo Web Framework, the Mobile API can be used anywhere by getting the object from
|
||||
**web_mobile.rpc**
|
||||
|
||||
.. image:: images/odoo_mobile_api.png
|
||||
|
||||
The mobile RPC object provides a list of methods that are available (this only works with the mobile
|
||||
app).
|
||||
|
||||
Check if the method is available and then execute it.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
.. note:: Each of the methods returns a JQuery Deferred object which returns
|
||||
a data JSON dictionary
|
||||
|
||||
Show Toast in device
|
||||
.....................
|
||||
|
||||
.. js:function:: showToast
|
||||
|
||||
:param object args: **message** text to display
|
||||
|
||||
A toast provides simple feedback about an operation in a small popup. It only
|
||||
fills the amount of space required for the message and the current activity
|
||||
remains visible and interactive.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
mobile.methods.showToast({'message': 'Message sent'});
|
||||
|
||||
.. image:: images/toast.png
|
||||
|
||||
|
||||
Vibrating device
|
||||
................
|
||||
|
||||
|
||||
.. js:function:: vibrate
|
||||
|
||||
:param object args: Vibrates constantly for the specified period of time
|
||||
(in milliseconds).
|
||||
|
||||
Vibrate mobile device with given duration.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
mobile.methods.vibrate({'duration': 100});
|
||||
|
||||
Show snackbar with action
|
||||
.........................
|
||||
|
||||
.. js:function:: showSnackBar
|
||||
|
||||
:param object args: (*required*) **Message** to show in snackbar and action **button label** in Snackbar (optional)
|
||||
:returns: ``True`` if the user clicks on the Action button, ``False`` if SnackBar auto dismissed after some time.
|
||||
|
||||
Snackbars provide lightweight feedback about an operation. They show a brief
|
||||
message at the bottom of the screen on mobile or in the lower left corner on larger devices.
|
||||
Snackbars appear above all the other elements on the screen and only one can be
|
||||
displayed at a time.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
mobile.methods.showSnackBar({'message': 'Message is deleted', 'btn_text': 'Undo'}).then(function(result){
|
||||
if(result){
|
||||
// Do undo operation
|
||||
}else{
|
||||
// Snack Bar dismissed
|
||||
}
|
||||
});
|
||||
|
||||
.. image:: images/snackbar.png
|
||||
|
||||
Showing notification
|
||||
.....................
|
||||
|
||||
.. js:function:: showNotification
|
||||
|
||||
:param object args: **title** (first row) of the notification, **message** (second row) of the notification, in a standard notification.
|
||||
|
||||
A notification is a message you can display to the user outside of your
|
||||
application's normal UI. When you tell the system to issue a notification, it
|
||||
first appears as an icon in the notification area. To see the details of the
|
||||
notification, the user opens the notification drawer. Both the notification
|
||||
area and the notification drawer are system-controlled areas that the user can
|
||||
view at any time.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
mobile.showNotification({'title': 'Simple Notification', 'message': 'This is a test for a simple notification'})
|
||||
|
||||
.. image:: images/mobile_notification.png
|
||||
|
||||
|
||||
Create contact in device
|
||||
.........................
|
||||
|
||||
.. js:function:: addContact
|
||||
|
||||
:param object args: Dictionary with contact details. Possible keys (name, mobile, phone, fax, email, website, street, street2, country_id, state_id, city, zip, parent_id, function and image)
|
||||
|
||||
Create a new device contact with the given contact details.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var contact = {
|
||||
'name': 'Michel Fletcher',
|
||||
'mobile': '9999999999',
|
||||
'phone': '7954856587',
|
||||
'fax': '765898745',
|
||||
'email': 'michel.fletcher@agrolait.example.com',
|
||||
'website': 'http://www.agrolait.com',
|
||||
'street': '69 rue de Namur',
|
||||
'street2': false,
|
||||
'country_id': [21, 'Belgium'],
|
||||
'state_id': false,
|
||||
'city': 'Wavre',
|
||||
'zip': '1300',
|
||||
'parent_id': [8, 'Agrolait'],
|
||||
'function': 'Analyst',
|
||||
'image': '<<BASE 64 Image Data>>'
|
||||
}
|
||||
|
||||
mobile.methods.addContact(contact);
|
||||
|
||||
.. image:: images/mobile_contact_create.png
|
||||
|
||||
Scanning barcodes
|
||||
..................
|
||||
|
||||
.. js:function:: scanBarcode
|
||||
|
||||
:returns: Scanned ``code`` from any barcode
|
||||
|
||||
The barcode API detects barcodes in real-time, on the device, in any orientation.
|
||||
|
||||
The barcode API can read the following barcode formats:
|
||||
|
||||
* 1D barcodes: EAN-13, EAN-8, UPC-A, UPC-E, Code-39, Code-93, Code-128, ITF, Codabar
|
||||
* 2D barcodes: QR Code, Data Matrix, PDF-417, AZTEC
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
mobile.methods.scanBarcode().then(function(code){
|
||||
if(code){
|
||||
// Perform operation with the scanned code
|
||||
}
|
||||
});
|
||||
|
||||
Switching account in device
|
||||
...........................
|
||||
|
||||
.. js:function:: switchAccount
|
||||
|
||||
Use switchAccount to switch from one account to another on the device.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
mobile.methods.switchAccount();
|
||||
|
||||
.. image:: images/mobile_switch_account.png
|
||||
687
content/developer/reference/javascript/qweb.rst
Normal file
@@ -0,0 +1,687 @@
|
||||
|
||||
.. highlight:: xml
|
||||
|
||||
.. _reference/qweb:
|
||||
|
||||
==============
|
||||
QWeb Templates
|
||||
==============
|
||||
|
||||
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.
|
||||
|
||||
Extra conditional branching directives ``t-elif`` and ``t-else`` are also
|
||||
available::
|
||||
|
||||
<div>
|
||||
<p t-if="user.birthday == today()">Happy birthday!</p>
|
||||
<p t-elif="user.login == 'root'">Welcome master!</p>
|
||||
<p t-else="">Welcome!</p>
|
||||
</div>
|
||||
|
||||
|
||||
.. _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) or a mapping (the current item will be the current key). Iterating on an
|
||||
integer (equivalent to iterating on an array between 0 inclusive and the
|
||||
provided integer exclusive) is still supported but deprecated.
|
||||
|
||||
In addition to the name passed via ``t-as``, ``foreach`` provides a few other
|
||||
variables for various data points:
|
||||
|
||||
.. warning:: ``$as`` will be replaced by the name passed to ``t-as``
|
||||
|
||||
:samp:`{$as}_all` (deprecated)
|
||||
the object being iterated over
|
||||
|
||||
.. note:: This variable is only available on JavaScript QWeb, not Python.
|
||||
|
||||
: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` (deprecated)
|
||||
either ``"even"`` or ``"odd"``, the parity of the current iteration round
|
||||
:samp:`{$as}_even` (deprecated)
|
||||
a boolean flag indicating that the current iteration round is on an even
|
||||
index
|
||||
:samp:`{$as}_odd` (deprecated)
|
||||
a boolean flag indicating that the current iteration round is on an odd
|
||||
index
|
||||
|
||||
These extra variables provided and all new variables created into the
|
||||
``foreach`` are only available in the scope of the``foreach``. If the
|
||||
variable exists outside the context of the ``foreach``, the value is copied
|
||||
at the end of the foreach into the global context.
|
||||
|
||||
::
|
||||
|
||||
<t t-set="existing_variable" t-value="False"/>
|
||||
<!-- existing_variable now False -->
|
||||
|
||||
<p t-foreach="[1, 2, 3]" t-as="i">
|
||||
<t t-set="existing_variable" t-value="True"/>
|
||||
<t t-set="new_variable" t-value="True"/>
|
||||
<!-- existing_variable and new_variable now True -->
|
||||
</p>
|
||||
|
||||
<!-- existing_variable always True -->
|
||||
<!-- new_variable undefined -->
|
||||
|
||||
.. _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_index % 2 === 0) ? 'even' : 'odd' }}">
|
||||
<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.
|
||||
|
||||
.. _reference/qweb/sub-templates:
|
||||
|
||||
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 -->
|
||||
|
||||
The body of the ``call`` directive can be arbitrarily complex (not just
|
||||
``set`` directives), and its rendered form will be available within the called
|
||||
template as a magical ``0`` variable::
|
||||
|
||||
<div>
|
||||
This template was called with content:
|
||||
<t t-raw="0"/>
|
||||
</div>
|
||||
|
||||
being called thus::
|
||||
|
||||
<t t-call="other-template">
|
||||
<em>content</em>
|
||||
</t>
|
||||
|
||||
will result in::
|
||||
|
||||
<div>
|
||||
This template was called with content:
|
||||
<em>content</em>
|
||||
</div>
|
||||
|
||||
Python
|
||||
======
|
||||
|
||||
Exclusive directives
|
||||
--------------------
|
||||
|
||||
Asset bundles
|
||||
'''''''''''''
|
||||
|
||||
.. todo:: have fme write these up because I've no idea how they work
|
||||
|
||||
"smart records" fields formatting
|
||||
'''''''''''''''''''''''''''''''''
|
||||
|
||||
The ``t-field`` directive can only be used when performing field access
|
||||
(``a.b``) on a "smart" record (result of the ``browse`` method). It is able
|
||||
to automatically format based on field type, and is integrated in the
|
||||
website's rich text editing.
|
||||
|
||||
``t-options`` can be used to customize fields, the most common option
|
||||
is ``widget``, other options are field- or widget-dependent.
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
``t-debug``
|
||||
invokes a debugger using PDB's ``set_trace`` API. The parameter should
|
||||
be the name of a module, on which a ``set_trace`` method is called::
|
||||
|
||||
<t t-debug="pdb"/>
|
||||
|
||||
is equivalent to ``importlib.import_module("pdb").set_trace()``
|
||||
|
||||
Helpers
|
||||
-------
|
||||
|
||||
Request-based
|
||||
'''''''''''''
|
||||
|
||||
Most Python-side uses of QWeb are in controllers (and during HTTP requests),
|
||||
in which case templates stored in the database (as
|
||||
:ref:`views <reference/views/qweb>`) can be trivially rendered by calling
|
||||
:meth:`odoo.http.HttpRequest.render`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
response = http.request.render('my-template', {
|
||||
'context_value': 42
|
||||
})
|
||||
|
||||
This automatically creates a :class:`~odoo.http.Response` object which can
|
||||
be returned from the controller (or further customized to suit).
|
||||
|
||||
View-based
|
||||
''''''''''
|
||||
|
||||
At a deeper level than the previous helper is the ``render`` method on
|
||||
``ir.ui.view``:
|
||||
|
||||
.. py:method:: render(cr, uid, id[, values][, engine='ir.qweb][, context])
|
||||
|
||||
Renders a QWeb view/template by database id or :term:`external id`.
|
||||
Templates are automatically loaded from ``ir.ui.view`` records.
|
||||
|
||||
Sets up a number of default values in the rendering context:
|
||||
|
||||
``request``
|
||||
the current :class:`~odoo.http.WebRequest` object, if any
|
||||
``debug``
|
||||
whether the current request (if any) is in ``debug`` mode
|
||||
:func:`quote_plus <werkzeug.urls.url_quote_plus>`
|
||||
url-encoding utility function
|
||||
:mod:`json`
|
||||
the corresponding standard library module
|
||||
:mod:`time`
|
||||
the corresponding standard library module
|
||||
:mod:`datetime`
|
||||
the corresponding standard library module
|
||||
`relativedelta <https://labix.org/python-dateutil#head-ba5ffd4df8111d1b83fc194b97ebecf837add454>`_
|
||||
see module
|
||||
``keep_query``
|
||||
the ``keep_query`` helper function
|
||||
|
||||
:param values: context values to pass to QWeb for rendering
|
||||
:param str engine: name of the Odoo model to use for rendering, can be
|
||||
used to expand or customize QWeb locally (by creating
|
||||
a "new" qweb based on ``ir.qweb`` with alterations)
|
||||
|
||||
.. _reference/qweb/javascript:
|
||||
|
||||
.. todo:: the members below are no longer relevant, section to rewrite
|
||||
|
||||
.. API
|
||||
.. ---
|
||||
|
||||
.. It is also possible to use the ``ir.qweb`` model directly (and extend it, and
|
||||
.. inherit from it):
|
||||
|
||||
.. .. automodule:: odoo.addons.base.ir.ir_qweb
|
||||
.. :members: QWeb, QWebContext, FieldConverter, QwebWidget
|
||||
|
||||
Javascript
|
||||
==========
|
||||
|
||||
Exclusive directives
|
||||
--------------------
|
||||
|
||||
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 either:
|
||||
- Alter existing templates in-place, e.g. to add information to templates
|
||||
created by other modules.
|
||||
- Create a new template from a given parent template
|
||||
|
||||
Template inheritance is performed via the use of two directives:
|
||||
- ``t-inherit`` which is the name of the template to inherit from,
|
||||
- ``t-inherit-mode`` which is the behaviour of the inheritance: it can either be
|
||||
set to ``primary`` to create a new child template from the parented one or
|
||||
to ``extension`` to alter the parent template in place.
|
||||
|
||||
An optional ``t-name`` directive can also be specified. It will be the name of
|
||||
the newly created template if used in primary mode, else it will be added as a
|
||||
comment on the transformed template to help retrace inheritances.
|
||||
|
||||
For the inheritance itself, the changes are done using xpaths directives.
|
||||
See the XPATH_ documentation for the complete set of available instructions.
|
||||
|
||||
Primary inheritance (child template)::
|
||||
|
||||
<t t-name="child.template" t-inherit="base.template" t-inherit-mode="primary">
|
||||
<xpath expr="//ul" position="inside">
|
||||
<li>new element</li>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
Extension inheritance (in-place transformation)::
|
||||
|
||||
<t t-inherit="base.template" t-inherit-mode="extension">
|
||||
<xpath expr="//tr[1]" position="after">
|
||||
<tr><td>new cell</td></tr>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
Old inheritance mechanism (deprecated)
|
||||
''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Template inheritance is performed via the ``t-extend`` directive which takes
|
||||
the name of the template to alter as parameter.
|
||||
|
||||
The directive ``t-extend`` will act as a primary inheritance when combined with
|
||||
``t-name`` and as an extension one when used alone.
|
||||
|
||||
In both cases the alteration is then performed with any number of ``t-jquery``
|
||||
sub-directives::
|
||||
|
||||
<t t-extend="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
|
||||
``attributes``
|
||||
the nodes's body should be any number of ``attribute`` elements,
|
||||
each with a ``name`` attribute and some textual content, the named
|
||||
attribute of the context node will be set to the specified value
|
||||
(either replaced if it already existed or added if not)
|
||||
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 t-set="foo" t-value="42"/>
|
||||
<t t-log="foo"/>
|
||||
|
||||
will print ``42`` to the console
|
||||
``t-debug``
|
||||
triggers a debugger breakpoint during template rendering::
|
||||
|
||||
<t t-if="a_test">
|
||||
<t t-debug="">
|
||||
</t>
|
||||
|
||||
will stop execution if debugging is active (exact condition depend on the
|
||||
browser and its development tools)
|
||||
``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::
|
||||
|
||||
<t t-set="foo" t-value="42"/>
|
||||
<t t-js="ctx">
|
||||
console.log("Foo is", ctx.foo);
|
||||
</t>
|
||||
|
||||
Helpers
|
||||
-------
|
||||
|
||||
.. js:attribute:: core.qweb
|
||||
|
||||
(core is the ``web.core`` module) 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:`core.qweb.render <QWeb2.Engine.render>` can be used to
|
||||
easily render basic module templates
|
||||
|
||||
.. _reference/qweb/api:
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
.. js:class:: QWeb2.Engine
|
||||
|
||||
The QWeb "renderer", handles most of QWeb's logic (loading,
|
||||
parsing, compiling and rendering templates).
|
||||
|
||||
Odoo Web instantiates one for the user in the core module, and
|
||||
exports it to ``core.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
|
||||
Odoo 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 template 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 Odoo 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 9.0 still depends on Jinja_ and Mako_.
|
||||
|
||||
.. _templating:
|
||||
https://en.wikipedia.org/wiki/Template_processor
|
||||
|
||||
.. _Jinja: http://jinja.pocoo.org
|
||||
.. _Mako: https://www.makotemplates.org
|
||||
.. _Genshi: https://genshi.edgewall.org
|
||||
.. _XML namespaces: https://en.wikipedia.org/wiki/XML_namespace
|
||||
.. _HTML: https://en.wikipedia.org/wiki/HTML
|
||||
.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting
|
||||
.. _JSON: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
|
||||
.. _CSS selector: https://api.jquery.com/category/selectors/
|
||||
.. _XPATH: https://developer.mozilla.org/en-US/docs/Web/XPath
|
||||