1 =============================
2 Building Interface Extensions
3 =============================
5 .. highlight:: javascript
9 This guide is about creating modules for Odoo's web client.
11 To create websites with Odoo, see :doc:`website`; to add business capabilities
12 or extend existing business systems of Odoo, see :doc:`backend`.
16 This guide assumes knowledge of:
18 * Javascript basics and good practices
22 It also requires an installed Odoo, and Git_.
28 Let's start with a simple Odoo module holding basic web component
29 configuration and letting us test the web framework.
31 The example module is available online and can be downloaded using the
34 .. code-block:: console
36 $ git clone http://github.com/odoo/petstore
38 This will create a ``petstore`` folder wherever you executed the command.
39 You then need to add that folder to Odoo's
40 :option:`addons path <odoo.py --addons-path>`, create a new database and
41 install the ``oepetstore`` module.
43 If you browse the ``petstore`` folder, you should see the following content:
51 | |-- crazy_circle.jpg
55 |-- oepetstore.message_of_the_day.csv
69 The module already holds various server customizations. We'll come back to
70 these later, for now let's focus on the web-related content, in the ``static``
73 Files used in the "web" side of an Odoo module must be placed in a ``static``
74 folder so they are available to a web browser, files outside that folder can
75 not be fetched by browsers. The ``src/css``, ``src/js`` and ``src/xml``
76 sub-folders are conventional and not strictly necessary.
78 ``oepetstore/static/css/petstore.css``
79 currently empty, will hold the CSS_ for pet store content
80 ``oepetstore/static/xml/petstore.xml``
81 Mostly empty, will hold :ref:`reference/qweb` templates
82 ``oepetstore/static/js/petstore.js``
83 The most important (and interesting) part, contains the logic of the
84 application (or at least its web-browser side) as javascript. It should
87 openerp.oepetstore = function(instance, local) {
88 var _t = instance.web._t,
89 _lt = instance.web._lt;
90 var QWeb = instance.web.qweb;
92 local.HomePage = instance.Widget.extend({
94 console.log("pet store home page loaded");
98 instance.web.client_actions.add(
99 'petstore.homepage', 'instance.oepetstore.HomePage');
102 Which only prints a small message in the browser's console.
106 All JavaScript files are concatenated and :term:`minified` to improve
107 application load time.
109 One of the drawback is debugging becomes more difficult as
110 individual files disappear and the code is made significantly less
111 readable. It is possible to disable this process by enabling the
112 "developer mode": log into your Odoo instance (user *admin* password
113 *admin* by default) open the user menu (in the top-right corner of the
114 Odoo screen) and select :guilabel:`About Odoo` then :guilabel:`Activate
117 .. image:: web/about_odoo.png
120 .. image:: web/devmode.png
123 This will reload the web client with optimizations disabled, making
124 development and debugging significantly more comfortable.
126 .. todo:: qweb files hooked via __openerp__.py, but js and CSS use bundles
128 Odoo JavaScript Module
129 ======================
131 Javascript doesn't have built-in modules. As a result variables defined in
132 different files are all mashed together and may conflict. This has given rise
133 to various module patterns used to build clean namespaces and limit risks of
136 The Odoo framework uses one such pattern to define modules within web addons,
137 in order to both namespace code and correctly order its loading.
139 ``oepetstore/static/js/petstore.js`` contains a module declaration::
141 openerp.oepetstore = function(instance, local) {
145 In Odoo web, modules are declared as functions set on the global ``openerp``
146 variable. The function's name must be the same as the addon (in this case
147 ``oepetstore``) so the framework can find it, and automatically initialize it.
149 When the web client decides to load your module, it'll call the root function
150 and provide two parameters:
152 * the first parameter is the current instance of the Odoo web client, it gives
153 access to various capabilities defined by the Odoo (translations,
154 network services) as well as objects defined by the core or by other
156 * the second parameter is your own local namespace automatically created by
157 the web client. Objects and variables which should be accessible from
158 outside your module (either because the Odoo web client needs to call them
159 or because others may want to customize them) should be set inside that
165 Much as modules, and contrary to most object-oriented languages, javascript
166 does not build in *classes*\ [#classes]_ although it provides roughly
167 equivalent (if lower-level and more verbose) mechanisms.
169 For simplicity and developer-friendliness purposes, Odoo web provides a class
170 system based on John Resig's `Simple JavaScript Inheritance`_.
172 New classes are defined by calling the :func:`~openerp.web.Class.extend`
173 method of :class:`openerp.web.Class`::
175 var MyClass = instance.web.Class.extend({
176 say_hello: function() {
177 console.log("hello");
181 The :func:`~openerp.web.Class.extend` method takes a dictionary describing
182 the new class's content (methods and static attributes). In this case, it will
183 only have a ``say_hello`` method which takes no parameters.
185 Classes are instantiated using the ``new`` operator::
187 var my_object = new MyClass();
188 my_object.say_hello();
189 // print "hello" in the console
191 And attributes of the instance can be accessed via ``this``::
193 var MyClass = instance.web.Class.extend({
194 say_hello: function() {
195 console.log("hello", this.name);
199 var my_object = new MyClass();
200 my_object.name = "Bob";
201 my_object.say_hello();
202 // print "hello Bob" in the console
204 Classes can provide an initializer to perform the initial setup of the
205 instance, by defining an ``init()`` method. The initializer receives the
206 parameters passed when using the ``new`` operator::
208 var MyClass = instance.web.Class.extend({
209 init: function(name) {
212 say_hello: function() {
213 console.log("hello", this.name);
217 var my_object = new MyClass("Bob");
218 my_object.say_hello();
219 // print "hello Bob" in the console
221 It is also possible to create subclasses from existing (used-defined) classes
222 by calling :func:`~openerp.web.Class.extend` on the parent class, as is done
223 to subclass :class:`~openerp.web.Class`::
225 var MySpanishClass = MyClass.extend({
226 say_hello: function() {
227 console.log("hola", this.name);
231 var my_object = new MySpanishClass("Bob");
232 my_object.say_hello();
233 // print "hola Bob" in the console
235 When overriding a method using inheritance, you can use ``this._super()`` to
236 call the original method::
238 var MySpanishClass = MyClass.extend({
239 say_hello: function() {
241 console.log("translation in Spanish: hola", this.name);
245 var my_object = new MySpanishClass("Bob");
246 my_object.say_hello();
247 // print "hello Bob \n translation in Spanish: hola Bob" in the console
251 ``_super`` is not a standard method, it is set on-the-fly to the next
252 method in the current inheritance chain, if any. It is only defined
253 during the *synchronous* part of a method call, for use in asynchronous
254 handlers (after network calls or in ``setTimeout`` callbacks) a reference
255 to its value should be retained, it should not be accessed via ``this``::
257 // broken, will generate an error
258 say_hello: function () {
259 setTimeout(function () {
265 say_hello: function () {
266 // don't forget .bind()
267 var _super = this._super.bind(this);
268 setTimeout(function () {
276 The Odoo web client bundles jQuery_ for easy DOM manipulation. It is useful
277 and provides a better API than standard `W3C DOM`_\ [#dombugs]_, but
278 insufficient to structure complex applications leading to difficult
281 Much like object-oriented desktop UI toolkits (e.g. Qt_, Cocoa_ or GTK_),
282 Odoo Web makes specific components responsible for sections of a page. In
283 Odoo web, the base for such components is the :class:`~openerp.Widget`
284 class, a component specialized in handling a page section and displaying
285 information for the user.
290 The initial demonstration module already provides a basic widget::
292 local.HomePage = instance.Widget.extend({
294 console.log("pet store home page loaded");
298 It extends :class:`~openerp.Widget` and overrides the standard method
299 :func:`~openerp.Widget.start`, which — much like the previous ``MyClass``
300 — does little for now.
302 This line at the end of the file::
304 instance.web.client_actions.add(
305 'petstore.homepage', 'instance.oepetstore.HomePage');
307 registers our basic widget as a client action. Client actions will be
308 explained later in the guide, for now this is just what allows our widget to
309 be called and displayed when we select the
310 :menuselection:`Pet Store --> Pet Store --> Home Page` menu.
314 because the widget will be called from outside our module, the web client
315 needs its "fully qualified" name, not the local version.
320 Widgets have a number of methods and features, but the basics are simple:
323 * format the widget's data
326 The ``HomePage`` widget already has a :func:`~openerp.Widget.start`
327 method. That method is part of the normal widget lifecycle and automatically
328 called once the widget is inserted in the page. We can use it to display some
331 All widgets have a :attr:`~openerp.Widget.$el` which represents the
332 section of page they're in charge of (as a jQuery_ object). Widget content
333 should be inserted there. By default, :attr:`~openerp.Widget.$el` is an
334 empty ``<div>`` element.
336 A ``<div>`` element is usually invisible for the user if it does not
337 have any content (or specific styles giving it a size) which is why nothing
338 is displayed on the page when ``HomePage`` is launched.
340 Let's add some content to the widget's root element, using jQuery::
342 local.HomePage = instance.Widget.extend({
344 this.$el.append("<div>Hello dear Odoo user!</div>");
348 That message will now appear when you open :menuselection:`Pet Store
349 --> Pet Store --> Home Page`
353 to refresh the javascript code loaded in Odoo Web, you will need to reload
354 the page. There is no need to restart the Odoo server
356 The ``HomePage`` widget is used by Odoo Web and managed automatically, to
357 learn how to use a widget "from scratch" let's create a new one::
359 local.GreetingsWidget = instance.Widget.extend({
361 this.$el.append("<div>We are so happy to see you again in this menu!</div>");
365 We can now add our ``GreetingsWidget`` to the ``HomePage`` by using the
366 ``GreetingsWidget``'s :func:`~openerp.Widget.appendTo` method::
368 local.HomePage = instance.Widget.extend({
370 this.$el.append("<div>Hello dear Odoo user!</div>");
371 var greeting = new local.GreetingsWidget(this);
372 return greeting.appendTo(this.$el);
376 * ``HomePage`` first adds its own content to its DOM root
377 * ``HomePage`` then instantiates ``GreetingsWidget``
378 * Finally it tells ``GreetingsWidget`` where to insert itself, delegating part
379 of its :attr:`~openerp.Widget.$el` to the ``GreetingsWidget``.
381 When the :func:`~openerp.Widget.appendTo` method is called, it asks the
382 widget to insert itself at the specified position and to display its content.
383 The :func:`~openerp.Widget.start` method will be called during the call
384 to :func:`~openerp.Widget.appendTo`.
386 To see what happens under the displayed interface, we will use the browser's
387 DOM Explorer. But first let's alter our widgets slightly so we can more easily
388 find where they are, by :attr:`adding a class to their root elements
389 <openerp.Widget.className>`::
391 local.HomePage = instance.Widget.extend({
392 className: 'oe_petstore_homepage',
395 local.GreetingsWidget = instance.Widget.extend({
396 className: 'oe_petstore_greetings',
400 If you can find the relevant section of the DOM (right-click on the text
401 then :guilabel:`Inspect Element`), it should look like this:
405 <div class="oe_petstore_homepage">
406 <div>Hello dear Odoo user!</div>
407 <div class="oe_petstore_greetings">
408 <div>We are so happy to see you again in this menu!</div>
412 Which clearly shows the two ``<div>`` elements automatically created by
413 :class:`~openerp.Widget`, because we added some classes on them.
415 We can also see the two message-holding divs we added ourselves
417 Finally, note the ``<div class="oe_petstore_greetings">`` element which
418 represents the ``GreetingsWidget`` instance is *inside* the
419 ``<div class="oe_petstore_homepage">`` which represents the ``HomePage``
420 instance, since we appended
422 Widget Parents and Children
423 ---------------------------
425 In the previous part, we instantiated a widget using this syntax::
427 new local.GreetingsWidget(this);
429 The first argument is ``this``, which in that case was a ``HomePage``
430 instance. This tells the widget being created which other widget is its
433 As we've seen, widgets are usually inserted in the DOM by another widget and
434 *inside* that other widget's root element. This means most widgets are "part"
435 of another widget, and exist on behalf of it. We call the container the
436 *parent*, and the contained widget the *child*.
438 Due to multiple technical and conceptual reasons, it is necessary for a widget
439 to know who is his parent and who are its children.
441 :func:`~openerp.Widget.getParent`
442 can be used to get the parent of a widget::
444 local.GreetingsWidget = instance.Widget.extend({
446 console.log(this.getParent().$el );
447 // will print "div.oe_petstore_homepage" in the console
451 :func:`~openerp.Widget.getChildren`
452 can be used to get a list of its children::
454 local.HomePage = instance.Widget.extend({
456 var greeting = new local.GreetingsWidget(this);
457 greeting.appendTo(this.$el);
458 console.log(this.getChildren()[0].$el);
459 // will print "div.oe_petstore_greetings" in the console
463 When overriding the :func:`~openerp.Widget.init` method of a widget it is
464 *of the utmost importance* to pass the parent to the ``this._super()`` call,
465 otherwise the relation will not be set up correctly::
467 local.GreetingsWidget = instance.Widget.extend({
468 init: function(parent, name) {
474 Finally, if a widget does not have a parent (e.g. because it's the root
475 widget of the application), ``null`` can be provided as parent::
477 new local.GreetingsWidget(null);
482 If you can display content to your users, you should also be able to erase
483 it. This is done via the :func:`~openerp.Widget.destroy` method::
487 When a widget is destroyed it will first call
488 :func:`~openerp.Widget.destroy` on all its children. Then it erases itself
489 from the DOM. If you have set up permanent structures in
490 :func:`~openerp.Widget.init` or :func:`~openerp.Widget.start` which
491 must be explicitly cleaned up (because the garbage collector will not handle
492 them), you can override :func:`~openerp.Widget.destroy`.
496 when overriding :func:`~openerp.Widget.destroy`, ``_super()``
497 *must always* be called otherwise the widget and its children are not
498 correctly cleaned up leaving possible memory leaks and "phantom events",
499 even if no error is displayed
501 The QWeb Template Engine
502 ========================
504 In the previous section we added content to our widgets by directly
505 manipulating (and adding to) their DOM::
507 this.$el.append("<div>Hello dear Odoo user!</div>");
509 This allows generating and displaying any type of content, but tends to
510 rapidly get unwieldy when generating significant amounts of DOM (lots of
511 duplication, quoting issues, ...)
513 As many other environments, Odoo's solution is to use a `template engine`_.
514 Odoo's template engine is called :ref:`reference/qweb`.
516 QWeb is an XML-based templating language, similar to `Genshi
517 <http://en.wikipedia.org/wiki/Genshi_(templating_language)>`_, `Thymeleaf
518 <http://en.wikipedia.org/wiki/Thymeleaf>`_ or `Facelets
519 <http://en.wikipedia.org/wiki/Facelets>`_. It has the following
522 * It's implemented fully in JavaScript and rendered in the browser
523 * Each template file (XML files) contains multiple templates
524 * It has special support in Odoo Web's :class:`~openerp.Widget`, though it
525 can be used outside of Odoo's web client (and it's possible to use
526 :class:`~openerp.Widget` without relying on QWeb)
530 The rationale behind using QWeb instead of existing javascript template
531 engines is the extensibility of pre-existing (third-party) templates, much
532 like Odoo :ref:`views <reference/views>`.
534 Most javascript template engines are text-based which precludes easy
535 structural extensibility where an XML-based templating engine can be
536 generically altered using e.g. XPath or CSS and a tree-alteration DSL (or
537 even just XSLT). This flexibility and extensibility is a core
538 characteristic of Odoo, and losing it was considered unacceptable.
543 First let's define a simple QWeb template in the almost-empty
544 ``oepetstore/static/src/xml/petstore.xml`` file:
548 <?xml version="1.0" encoding="UTF-8"?>
549 <templates xml:space="preserve">
550 <t t-name="HomePageTemplate">
551 <div style="background-color: red;">This is some simple HTML</div>
555 Now we can use this template inside of the ``HomePage`` widget. Using the
556 ``QWeb`` loader variable defined at the top of the page, we can call to the
557 template defined in the XML file::
559 local.HomePage = instance.Widget.extend({
561 this.$el.append(QWeb.render("HomePageTemplate"));
565 :func:`QWeb.render` looks for the specified template, renders it to a string
566 and returns the result.
568 However, because :class:`~openerp.Widget` has special integration for QWeb
569 the template can be set directly on the widget via its
570 :attr:`~openerp.Widget.template` attribute::
572 local.HomePage = instance.Widget.extend({
573 template: "HomePageTemplate",
579 Although the result look similar, there are two differences between these
582 * with the second version, the template is rendered right before
583 :func:`~openerp.Widget.start` is called
584 * in the first version the template's content is added to the widget's root
585 element, whereas in the second version the template's root element is
586 directly *set as* the widget's root element. Which is why the "greetings"
587 sub-widget also gets a red background
591 templates should have a single non-``t`` root element, especially if
592 they're set as a widget's :attr:`~openerp.Widget.template`. If there are
593 multiple "root elements", results are undefined (usually only the first
594 root element will be used and the others will be ignored)
599 QWeb templates can be given data and can contain basic display logic.
601 For explicit calls to :func:`QWeb.render`, the template data is passed as
604 QWeb.render("HomePageTemplate", {name: "Klaus"});
606 with the template modified to:
610 <t t-name="HomePageTemplate">
611 <div>Hello <t t-esc="name"/></div>
618 <div>Hello Klaus</div>
620 When using :class:`~openerp.Widget`'s integration it is not possible to
621 provide additional data to the template. The template will be given a single
622 ``widget`` context variable, referencing the widget being rendered right
623 before :func:`~openerp.Widget.start` is called (the widget's state will
624 essentially be that set up by :func:`~openerp.Widget.init`):
628 <t t-name="HomePageTemplate">
629 <div>Hello <t t-esc="widget.name"/></div>
634 local.HomePage = instance.Widget.extend({
635 template: "HomePageTemplate",
636 init: function(parent) {
638 this.name = "Mordecai";
648 <div>Hello Mordecai</div>
653 We've seen how to *render* QWeb templates, let's now see the syntax of
654 the templates themselves.
656 A QWeb template is composed of regular XML mixed with QWeb *directives*. A
657 QWeb directive is declared with XML attributes starting with ``t-``.
659 The most basic directive is ``t-name``, used to declare new templates in
665 <t t-name="HomePageTemplate">
666 <div>This is some simple HTML</div>
670 ``t-name`` takes the name of the template being defined, and declares that
671 it can be called using :func:`QWeb.render`. It can only be used at the
672 top-level of a template file.
677 The ``t-esc`` directive can be used to output text:
681 <div>Hello <t t-esc="name"/></div>
683 It takes a Javascript expression which is evaluated, the result of the
684 expression is then HTML-escaped and inserted in the document. Since it's an
685 expression it's possible to provide just a variable name as above, or a more
686 complex expression like a computation:
690 <div><t t-esc="3+5"/></div>
696 <div><t t-esc="name.toUpperCase()"/></div>
701 To inject HTML in the page being rendered, use ``t-raw``. Like ``t-esc`` it
702 takes an arbitrary Javascript expression as parameter, but it does not
703 perform an HTML-escape step.
707 <div><t t-raw="name.link(user_account)"/></div>
711 ``t-raw`` *must not* be used on any data which may contain non-escaped
712 user-provided content as this leads to `cross-site scripting`_
718 QWeb can have conditional blocks using ``t-if``. The directive takes an
719 arbitrary expression, if the expression is falsy (``false``, ``null``, ``0``
720 or an empty string) the whole block is suppressed, otherwise it is displayed.
725 <t t-if="true == true">
728 <t t-if="true == false">
735 QWeb doesn't have an "else" structure, use a second ``t-if`` with the
736 original condition inverted. You may want to store the condition in a
737 local variable if it's a complex or expensive expression.
742 To iterate on a list, use ``t-foreach`` and ``t-as``. ``t-foreach`` takes an
743 expression returning a list to iterate on ``t-as`` takes a variable name to
744 bind to each item during iteration.
749 <t t-foreach="names" t-as="name">
751 Hello <t t-esc="name"/>
756 .. note:: ``t-foreach`` can also be used with numbers and objects
762 QWeb provides two related directives to define computed attributes:
763 :samp:`t-att-{name}` and :samp:`t-attf-{name}`. In either case, *name* is the
764 name of the attribute to create (e.g. ``t-att-id`` defines the attribute
765 ``id`` after rendering).
767 ``t-att-`` takes a javascript expression whose result is set as the
768 attribute's value, it is most useful if all of the attribute's value is
775 <input type="text" t-att-value="defaultName"/>
778 ``t-attf-`` takes a *format string*. A format string is literal text with
779 interpolation blocks inside, an interpolation block is a javascript
780 expression between ``{{`` and ``}}``, which will be replaced by the result
781 of the expression. It is most useful for attributes which are partially
782 literal and partially computed such as a class:
786 <div t-attf-class="container {{ left ? 'text-left' : '' }} {{ extra_class }}">
790 Calling other templates
791 '''''''''''''''''''''''
793 Templates can be split into sub-templates (for simplicity, maintainability,
794 reusability or to avoid excessive markup nesting).
796 This is done using the ``t-call`` directive, which takes the name of the
807 <div class="i-am-b"/>
810 rendering the ``A`` template will result in:
815 <div class="i-am-b"/>
818 Sub-templates inherit the rendering context of their caller.
820 To Learn More About QWeb
821 ''''''''''''''''''''''''
823 For a QWeb reference, see :ref:`reference/qweb`.
828 .. exercise:: Usage of QWeb in Widgets
830 Create a widget whose constructor takes two parameters aside from
831 ``parent``: ``product_names`` and ``color``.
833 * ``product_names`` should an array of strings, each one the name of a
835 * ``color`` is a string containing a color in CSS color format (ie:
836 ``#000000`` for black).
838 The widget should display the given product names one under the other,
839 each one in a separate box with a background color with the value of
840 ``color`` and a border. You should use QWeb to render the HTML. Any
841 necessary CSS should be in ``oepetstore/static/src/css/petstore.css``.
843 Use the widget in ``HomePage`` with half a dozen products.
849 openerp.oepetstore = function(instance, local) {
850 var _t = instance.web._t,
851 _lt = instance.web._lt;
852 var QWeb = instance.web.qweb;
854 local.HomePage = instance.Widget.extend({
856 var products = new local.ProductsWidget(
857 this, ["cpu", "mouse", "keyboard", "graphic card", "screen"], "#00FF00");
858 products.appendTo(this.$el);
862 local.ProductsWidget = instance.Widget.extend({
863 template: "ProductsWidget",
864 init: function(parent, products, color) {
866 this.products = products;
871 instance.web.client_actions.add(
872 'petstore.homepage', 'instance.oepetstore.HomePage');
877 <?xml version="1.0" encoding="UTF-8"?>
878 <templates xml:space="preserve">
879 <t t-name="ProductsWidget">
881 <t t-foreach="widget.products" t-as="product">
882 <span class="oe_products_item"
883 t-attf-style="background-color: {{ widget.color }};">
895 display: inline-block;
898 border: 1px solid black;
902 .. image:: web/qweb.*
909 ``Widget``'s jQuery Selector
910 ----------------------------
912 Selecting DOM elements within a widget can be performed by calling the
913 ``find()`` method on the widget's DOM root::
915 this.$el.find("input.my_input")...
917 But because it's an extremely common operation, :class:`~openerp.Widget`
918 provides an equivalent shortcut through the :func:`~openerp.Widget.$`
921 local.MyWidget = instance.Widget.extend({
923 this.$("input.my_input")...
929 The global jQuery function ``$()`` should *never* be used unless it is
930 absolutely necessary: selection on a widget's root are scoped to the
931 widget and local to it, but selections with ``$()`` are global to the
932 page/application and may match parts of other widgets and views, leading
933 to odd or dangerous side-effects. Since a widget should generally act
934 only on the DOM section it owns, there is no cause for global selection.
936 Easier DOM Events Binding
937 -------------------------
939 We have previously bound DOM events using normal jQuery event handlers (e.g.
940 ``.click()`` or ``.change()``) on widget elements::
942 local.MyWidget = instance.Widget.extend({
945 this.$(".my_button").click(function() {
946 self.button_clicked();
949 button_clicked: function() {
954 While this works it has a few issues:
956 1. it is rather verbose
957 2. it does not support replacing the widget's root element at runtime as
958 the binding is only performed when ``start()`` is run (during widget
960 3. it requires dealing with ``this``-binding issues
962 Widgets thus provide a shortcut to DOM event binding via
963 :attr:`~openerp.Widget.events`::
965 local.MyWidget = instance.Widget.extend({
967 "click .my_button": "button_clicked",
969 button_clicked: function() {
974 :attr:`~openerp.Widget.events` is an object (mapping) of an event to the
975 function or method to call when the event is triggered:
977 * the key is an event name, possibly refined with a CSS selector in which
978 case only if the event happens on a selected sub-element will the function
979 or method run: ``click`` will handle all clicks within the widget, but
980 ``click .my_button`` will only handle clicks in elements bearing the
982 * the value is the action to perform when the event is triggered
984 It can be either a function::
987 'click': function (e) { /* code here */ }
990 or the name of a method on the object (see example above).
992 In either case, the ``this`` is the widget instance and the handler is given
993 a single parameter, the `jQuery event object`_ for the event.
995 Widget Events and Properties
996 ============================
1001 Widgets provide an event system (separate from the DOM/jQuery event system
1002 described above): a widget can fire events on itself, and other widgets (or
1003 itself) can bind themselves and listen for these events::
1005 local.ConfirmWidget = instance.Widget.extend({
1007 'click button.ok_button': function () {
1008 this.trigger('user_chose', true);
1010 'click button.cancel_button': function () {
1011 this.trigger('user_chose', false);
1015 this.$el.append("<div>Are you sure you want to perform this action?</div>" +
1016 "<button class='ok_button'>Ok</button>" +
1017 "<button class='cancel_button'>Cancel</button>");
1021 This widget acts as a facade, transforming user input (through DOM events)
1022 into a documentable internal event to which parent widgets can bind
1025 :func:`~openerp.Widget.trigger` takes the name of the event to trigger as
1026 its first (mandatory) argument, any further arguments are treated as event
1027 data and passed directly to listeners.
1029 We can then set up a parent event instantiating our generic widget and
1030 listening to the ``user_chose`` event using :func:`~openerp.Widget.on`::
1032 local.HomePage = instance.Widget.extend({
1034 var widget = new local.ConfirmWidget(this);
1035 widget.on("user_chose", this, this.user_chose);
1036 widget.appendTo(this.$el);
1038 user_chose: function(confirm) {
1040 console.log("The user agreed to continue");
1042 console.log("The user refused to continue");
1047 :func:`~openerp.Widget.on` binds a function to be called when the
1048 event identified by ``event_name`` is. The ``func`` argument is the
1049 function to call and ``object`` is the object to which that function is
1050 related if it is a method. The bound function will be called with the
1051 additional arguments of :func:`~openerp.Widget.trigger` if it has
1056 widget.on("my_event", this, this.my_event_triggered);
1057 widget.trigger("my_event", 1, 2, 3);
1059 my_event_triggered: function(a, b, c) {
1060 console.log(a, b, c);
1061 // will print "1 2 3"
1066 Triggering events on an other widget is generally a bad idea. The main
1067 exception to that rule is ``openerp.web.bus`` which exists specifically
1068 to broadcasts evens in which any widget could be interested throughout
1069 the Odoo web application.
1074 Properties are very similar to normal object attributes in that they allow
1075 storing data on a widget instance, however they have the additional feature
1076 that they trigger events when set::
1080 this.widget.on("change:name", this, this.name_changed);
1081 this.widget.set("name", "Nicolas");
1083 name_changed: function() {
1084 console.log("The new value of the property 'name' is", this.widget.get("name"));
1087 * :func:`~openerp.Widget.set` sets the value of a property and triggers
1088 :samp:`change:{propname}` (where *propname* is the property name passed as
1089 first parameter to :func:`~openerp.Widget.set`) and ``change``
1090 * :func:`~openerp.Widget.get` retrieves the value of a property.
1095 .. exercise:: Widget Properties and Events
1097 Create a widget ``ColorInputWidget`` that will display 3 ``<input
1098 type="text">``. Each of these ``<input>`` is dedicated to type a
1099 hexadecimal number from 00 to FF. When any of these ``<input>`` is
1100 modified by the user the widget must query the content of the three
1101 ``<input>``, concatenate their values to have a complete CSS color code
1102 (ie: ``#00FF00``) and put the result in a property named ``color``. Please
1103 note the jQuery ``change()`` event that you can bind on any HTML
1104 ``<input>`` element and the ``val()`` method that can query the current
1105 value of that ``<input>`` could be useful to you for this exercise.
1107 Then, modify the ``HomePage`` widget to instantiate ``ColorInputWidget``
1108 and display it. The ``HomePage`` widget should also display an empty
1109 rectangle. That rectangle must always, at any moment, have the same
1110 background color than the color in the ``color`` property of the
1111 ``ColorInputWidget`` instance.
1113 Use QWeb to generate all HTML.
1119 openerp.oepetstore = function(instance, local) {
1120 var _t = instance.web._t,
1121 _lt = instance.web._lt;
1122 var QWeb = instance.web.qweb;
1124 local.ColorInputWidget = instance.Widget.extend({
1125 template: "ColorInputWidget",
1127 'change input': 'input_changed'
1130 this.input_changed();
1131 return this._super();
1133 input_changed: function() {
1136 this.$(".oe_color_red").val(),
1137 this.$(".oe_color_green").val(),
1138 this.$(".oe_color_blue").val()
1140 this.set("color", color);
1144 local.HomePage = instance.Widget.extend({
1145 template: "HomePage",
1147 this.colorInput = new local.ColorInputWidget(this);
1148 this.colorInput.on("change:color", this, this.color_changed);
1149 return this.colorInput.appendTo(this.$el);
1151 color_changed: function() {
1152 this.$(".oe_color_div").css("background-color", this.colorInput.get("color"));
1156 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1161 <?xml version="1.0" encoding="UTF-8"?>
1162 <templates xml:space="preserve">
1163 <t t-name="ColorInputWidget">
1165 Red: <input type="text" class="oe_color_red" value="00"></input><br />
1166 Green: <input type="text" class="oe_color_green" value="00"></input><br />
1167 Blue: <input type="text" class="oe_color_blue" value="00"></input><br />
1170 <t t-name="HomePage">
1172 <div class="oe_color_div"></div>
1185 Modify existing widgets and classes
1186 ===================================
1188 The class system of the Odoo web framework allows direct modification of
1189 existing classes using the :func:`~openerp.web.Class.include` method::
1191 var TestClass = instance.web.Class.extend({
1192 testMethod: function() {
1198 testMethod: function() {
1199 return this._super() + " world";
1203 console.log(new TestClass().testMethod());
1204 // will print "hello world"
1206 This system is similar to the inheritance mechanism, except it will alter the
1207 target class in-place instead of creating a new class.
1209 In that case, ``this._super()`` will call the original implementation of a
1210 method being replaced/redefined. If the class already had sub-classes, all
1211 calls to ``this._super()`` in sub-classes will call the new implementations
1212 defined in the call to :func:`~openerp.web.Class.include`. This will also work
1213 if some instances of the class (or of any of its sub-classes) were created
1214 prior to the call to :func:`~openerp.Widget.include`.
1219 The process to translate text in Python and JavaScript code is very
1220 similar. You could have noticed these lines at the beginning of the
1221 ``petstore.js`` file::
1223 var _t = instance.web._t,
1224 _lt = instance.web._lt;
1226 These lines are simply used to import the translation functions in the current
1227 JavaScript module. They are used thus::
1229 this.$el.text(_t("Hello user!"));
1231 In Odoo, translations files are automatically generated by scanning the source
1232 code. All piece of code that calls a certain function are detected and their
1233 content is added to a translation file that will then be sent to the
1234 translators. In Python, the function is ``_()``. In JavaScript the function is
1235 :func:`~openerp.web._t` (and also :func:`~openerp.web._lt`).
1237 ``_t()`` will return the translation defined for the text it is given. If no
1238 translation is defined for that text, it will return the original text as-is.
1242 To inject user-provided values in translatable strings, it is recommended
1243 to use `_.str.sprintf
1244 <http://gabceb.github.io/underscore.string.site/#sprintf>`_ with named
1245 arguments *after* the translation::
1247 this.$el.text(_.str.sprintf(
1248 _t("Hello, %(user)s!"), {
1252 This makes translatable strings more readable to translators, and gives
1253 them more flexibility to reorder or ignore parameters.
1255 :func:`~openerp.web._lt` ("lazy translate") is similar but somewhat more
1256 complex: instead of translating its parameter immediately, it returns
1257 an object which, when converted to a string, will perform the translation.
1259 It is used to define translatable terms before the translations system is
1260 initialized, for class attributes for instance (as modules are loaded before
1261 the user's language is configured and translations are downloaded).
1263 Communication with the Odoo Server
1264 ==================================
1269 Most operations with Odoo involve communicating with *models* implementing
1270 business concern, these models will then (potentially) interact with some
1271 storage engine (usually PostgreSQL_).
1273 Although jQuery_ provides a `$.ajax`_ function for network interactions,
1274 communicating with Odoo requires additional metadata whose setup before every
1275 call would be verbose and error-prone. As a result, Odoo web provides
1276 higher-level communication primitives.
1278 To demonstrate this, the file ``petstore.py`` already contains a small model
1279 with a sample method:
1281 .. code-block:: python
1283 class message_of_the_day(models.Model):
1284 _name = "oepetstore.message_of_the_day"
1287 def my_method(self):
1288 return {"hello": "world"}
1290 message = fields.Text(),
1291 color = fields.Char(size=20),
1293 This declares a model with two fields, and a method ``my_method()`` which
1294 returns a literal dictionary.
1296 Here is a sample widget that calls ``my_method()`` and displays the result::
1298 local.HomePage = instance.Widget.extend({
1301 var model = new instance.web.Model("oepetstore.message_of_the_day");
1302 model.call("my_method", {context: new instance.web.CompoundContext()}).then(function(result) {
1303 self.$el.append("<div>Hello " + result["hello"] + "</div>");
1304 // will show "Hello world" to the user
1309 The class used to call Odoo models is :class:`openerp.Model`. It is
1310 instantiated with the Odoo model's name as first parameter
1311 (``oepetstore.message_of_the_day`` here).
1313 :func:`~openerp.web.Model.call` can be used to call any (public) method of an
1314 Odoo model. It takes the following positional arguments:
1317 The name of the method to call, ``my_method`` here
1319 an array of `positional arguments`_ to provide to the method. Because the
1320 example has no positional argument to provide, the ``args`` parameter is not
1323 Here is an other example with positional arguments:
1325 .. code-block:: python
1328 def my_method2(self, a, b, c): ...
1330 .. code-block:: javascript
1332 model.call("my_method", [1, 2, 3], ...
1333 // with this a=1, b=2 and c=3
1336 a mapping of `keyword arguments`_ to pass. The example provides a single
1337 named argument ``context``.
1339 .. code-block:: python
1342 def my_method2(self, a, b, c): ...
1344 .. code-block:: javascript
1346 model.call("my_method", [], {a: 1, b: 2, c: 3, ...
1347 // with this a=1, b=2 and c=3
1349 :func:`~openerp.Widget.call` returns a deferred resolved with the value
1350 returned by the model's method as first argument.
1355 The previous section used a ``context`` argument which was not explained in
1358 model.call("my_method", {context: new instance.web.CompoundContext()})
1360 The context is like a "magic" argument that the web client will always give to
1361 the server when calling a method. The context is a dictionary containing
1362 multiple keys. One of the most important key is the language of the user, used
1363 by the server to translate all the messages of the application. Another one is
1364 the time zone of the user, used to compute correctly dates and times if Odoo
1365 is used by people in different countries.
1367 The ``argument`` is necessary in all methods, because if we forget it bad
1368 things could happen (like the application not being translated
1369 correctly). That's why, when you call a model's method, you should always give
1370 it to that argument. The solution to achieve that is to use
1371 :class:`openerp.web.CompoundContext`.
1373 :class:`~openerp.web.CompoundContext` is a class used to pass the user's
1374 context (with language, time zone, etc...) to the server as well as adding new
1375 keys to the context (some models' methods use arbitrary keys added to the
1376 context). It is created by giving to its constructor any number of
1377 dictionaries or other :class:`~openerp.web.CompoundContext` instances. It will
1378 merge all those contexts before sending them to the server.
1380 .. code-block:: javascript
1382 model.call("my_method", {context: new instance.web.CompoundContext({'new_key': 'key_value'})})
1384 .. code-block:: python
1387 def my_method(self):
1388 print self.env.context
1389 // will print: {'lang': 'en_US', 'new_key': 'key_value', 'tz': 'Europe/Brussels', 'uid': 1}
1391 You can see the dictionary in the argument ``context`` contains some keys that
1392 are related to the configuration of the current user in Odoo plus the
1393 ``new_key`` key that was added when instantiating
1394 :class:`~openerp.web.CompoundContext`.
1399 While :func:`~openerp.Model.call` is sufficient for any interaction with Odoo
1400 models, Odoo Web provides a helper for simpler and clearer querying of models
1401 (fetching of records based on various conditions):
1402 :func:`~openerp.Model.query` which acts as a shortcut for the common
1403 combination of :py:meth:`~openerp.models.Model.search` and
1404 ::py:meth:`~openerp.models.Model.read`. It provides a clearer syntax to search
1407 model.query(['name', 'login', 'user_email', 'signature'])
1408 .filter([['active', '=', true], ['company_id', '=', main_company]])
1410 .all().then(function (users) {
1411 // do work with users records
1416 model.call('search', [['active', '=', true], ['company_id', '=', main_company]], {limit: 15})
1417 .then(function (ids) {
1418 return model.call('read', [ids, ['name', 'login', 'user_email', 'signature']]);
1420 .then(function (users) {
1421 // do work with users records
1424 * :func:`~openerp.web.Model.query` takes an optional list of fields as
1425 parameter (if no field is provided, all fields of the model are fetched). It
1426 returns a :class:`openerp.web.Query` which can be further customized before
1428 * :class:`~openerp.web.Query` represents the query being built. It is
1429 immutable, methods to customize the query actually return a modified copy,
1430 so it's possible to use the original and the new version side-by-side. See
1431 :class:`~openerp.web.Query` for its customization options.
1433 When the query is set up as desired, simply call
1434 :func:`~openerp.web.Query.all` to perform the actual query and return a
1435 deferred to its result. The result is the same as
1436 :py:meth:`~openerp.models.Model.read`'s, an array of dictionaries where each
1437 dictionary is a requested record, with each requested field a dictionary key.
1442 .. exercise:: Message of the Day
1444 Create a ``MessageOfTheDay`` widget displaying the last record of the
1445 ``oepetstore.message_of_the_day`` model. The widget should fetch its
1446 record as soon as it is displayed.
1448 Display the widget in the Pet Store home page.
1452 .. code-block:: javascript
1454 openerp.oepetstore = function(instance, local) {
1455 var _t = instance.web._t,
1456 _lt = instance.web._lt;
1457 var QWeb = instance.web.qweb;
1459 local.HomePage = instance.Widget.extend({
1460 template: "HomePage",
1462 return new local.MessageOfTheDay(this).appendTo(this.$el);
1466 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1468 local.MessageOfTheDay = instance.Widget.extend({
1469 template: "MessageOfTheDay",
1472 return new instance.web.Model("oepetstore.message_of_the_day")
1474 .order_by('-create_date', '-id')
1476 .then(function(result) {
1477 self.$(".oe_mywidget_message_of_the_day").text(result.message);
1486 <?xml version="1.0" encoding="UTF-8"?>
1487 <templates xml:space="preserve">
1488 <t t-name="HomePage">
1489 <div class="oe_petstore_homepage">
1492 <t t-name="MessageOfTheDay">
1493 <div class="oe_petstore_motd">
1494 <p class="oe_mywidget_message_of_the_day"></p>
1505 background-color: #F0EEEE;
1508 .. exercise:: Pet Toys List
1510 Create a ``PetToysList`` widget displaying 5 toys (using their name and
1513 The pet toys are not stored in a new model, instead they're stored in
1514 ``product.product`` using a special category *Pet Toys*. You can see the
1515 pre-generated toys and add new ones by going to
1516 :menuselection:`Pet Store --> Pet Store --> Pet Toys`. You will probably
1517 need to explore ``product.product`` in order to create the right domain to
1518 select just pet toys.
1520 In Odoo, images are generally stored in regular fields encoded as
1521 base64_, HTML supports displaying images straight from base64 with
1522 :samp:`<img src="data:{mime_type};base64,{base64_image_data}"/>`
1524 The ``PetToysList`` widget should be displayed on the home page on the
1525 right of the ``MessageOfTheDay`` widget. You will need to make some layout
1526 with CSS to achieve this.
1530 .. code-block:: javascript
1532 openerp.oepetstore = function(instance, local) {
1533 var _t = instance.web._t,
1534 _lt = instance.web._lt;
1535 var QWeb = instance.web.qweb;
1537 local.HomePage = instance.Widget.extend({
1538 template: "HomePage",
1539 start: function () {
1541 new local.PetToysList(this).appendTo(this.$('.oe_petstore_homepage_left')),
1542 new local.MessageOfTheDay(this).appendTo(this.$('.oe_petstore_homepage_right'))
1546 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1548 local.MessageOfTheDay = instance.Widget.extend({
1549 template: 'MessageOfTheDay',
1550 start: function () {
1552 return new instance.web.Model('oepetstore.message_of_the_day')
1554 .order_by('-create_date', '-id')
1556 .then(function (result) {
1557 self.$(".oe_mywidget_message_of_the_day").text(result.message);
1562 local.PetToysList = instance.Widget.extend({
1563 template: 'PetToysList',
1564 start: function () {
1566 return new instance.web.Model('product.product')
1567 .query(['name', 'image'])
1568 .filter([['categ_id.name', '=', "Pet Toys"]])
1571 .then(function (results) {
1572 _(results).each(function (item) {
1573 self.$el.append(QWeb.render('PetToy', {item: item}));
1582 <?xml version="1.0" encoding="UTF-8"?>
1584 <templates xml:space="preserve">
1585 <t t-name="HomePage">
1586 <div class="oe_petstore_homepage">
1587 <div class="oe_petstore_homepage_left"></div>
1588 <div class="oe_petstore_homepage_right"></div>
1591 <t t-name="MessageOfTheDay">
1592 <div class="oe_petstore_motd">
1593 <p class="oe_mywidget_message_of_the_day"></p>
1596 <t t-name="PetToysList">
1597 <div class="oe_petstore_pettoyslist">
1601 <div class="oe_petstore_pettoy">
1602 <p><t t-esc="item.name"/></p>
1603 <p><img t-att-src="'data:image/jpg;base64,'+item.image"/></p>
1610 .oe_petstore_homepage {
1614 .oe_petstore_homepage_left {
1615 display: table-cell;
1619 .oe_petstore_homepage_right {
1620 display: table-cell;
1628 background-color: #F0EEEE;
1631 .oe_petstore_pettoyslist {
1635 .oe_petstore_pettoy {
1639 background-color: #F0EEEE;
1643 Existing web components
1644 =======================
1649 In Odoo, many operations start from an :ref:`action <reference/actions>`:
1650 opening a menu item (to a view), printing a report, ...
1652 Actions are pieces of data describing how a client should react to the
1653 activation of a piece of content. Actions can be stored (and read through a
1654 model) or they can be generated on-the fly (locally to the client by
1655 javascript code, or remotely by a method of a model).
1657 In Odoo Web, the component responsible for handling and reacting to these
1658 actions is the *Action Manager*.
1660 Using the Action Manager
1661 ''''''''''''''''''''''''
1663 The action manager can be invoked explicitly from javascript code by creating
1664 a dictionary describing :ref:`an action <reference/actions>` of the right
1665 type, and calling an action manager instance with it.
1667 :func:`~openerp.Widget.do_action` is a shortcut of :class:`~openerp.Widget`
1668 looking up the "current" action manager and executing the action::
1670 instance.web.TestWidget = instance.Widget.extend({
1671 dispatch_to_new_action: function() {
1673 type: 'ir.actions.act_window',
1674 res_model: "product.product",
1676 views: [[false, 'form']],
1683 The most common action ``type`` is ``ir.actions.act_window`` which provides
1684 views to a model (displays a model in various manners), its most common
1688 The model to display in views
1689 ``res_id`` (optional)
1690 For form views, a preselected record in ``res_model``
1692 Lists the views available through the action. A list of
1693 ``[view_id, view_type]``, ``view_id`` can either be the database identifier
1694 of a view of the right type, or ``false`` to use the view by default for
1695 the specified type. View types can not be present multiple times. The action
1696 will open the first view of the list by default.
1698 Either ``current`` (the default) which replaces the "content" section of the
1699 web client by the action, or ``new`` to open the action in a dialog box.
1701 Additional context data to use within the action.
1703 .. exercise:: Jump to Product
1705 Modify the ``PetToysList`` component so clicking on a toy replaces the
1706 homepage by the toy's form view.
1710 .. code-block:: javascript
1712 local.PetToysList = instance.Widget.extend({
1713 template: 'PetToysList',
1715 'click .oe_petstore_pettoy': 'selected_item',
1717 start: function () {
1719 return new instance.web.Model('product.product')
1720 .query(['name', 'image'])
1721 .filter([['categ_id.name', '=', "Pet Toys"]])
1724 .then(function (results) {
1725 _(results).each(function (item) {
1726 self.$el.append(QWeb.render('PetToy', {item: item}));
1730 selected_item: function (event) {
1732 type: 'ir.actions.act_window',
1733 res_model: 'product.product',
1734 res_id: $(event.currentTarget).data('id'),
1735 views: [[false, 'form']],
1743 <div class="oe_petstore_pettoy" t-att-data-id="item.id">
1744 <p><t t-esc="item.name"/></p>
1745 <p><img t-attf-src="data:image/jpg;base64,#{item.image}"/></p>
1752 Throughout this guide, we used a simple ``HomePage`` widget which the web
1753 client automatically starts when we select the right menu item. But how did
1754 the Odoo web know to start this widget? Because the widget is registered as
1757 A client action is (as its name implies) an action type defined almost
1758 entirely in the client, in javascript for Odoo web. The server simply sends
1759 an action tag (an arbitrary name), and optionally adds a few parameters, but
1760 beyond that *everything* is handled by custom client code.
1762 Our widget is registered as the handler for the client action through this::
1764 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1767 ``instance.web.client_actions`` is a :class:`~openerp.web.Registry` in which
1768 the action manager looks up client action handlers when it needs to execute
1769 one. The first parameter of :class:`~openerp.web.Registry.add` is the name
1770 (tag) of the client action, and the second parameter is the path to the widget
1771 from the Odoo web client root.
1773 When a client action must be executed, the action manager looks up its tag
1774 in the registry, walks the specified path and displays the widget it finds at
1777 .. note:: a client action handler can also be a regular function, in whch case
1778 it'll be called and its result (if any) will be interpreted as the
1779 next action to execute.
1781 On the server side, we had simply defined an ``ir.actions.client`` action:
1785 <record id="action_home_page" model="ir.actions.client">
1786 <field name="tag">petstore.homepage</field>
1789 and a menu opening the action:
1793 <menuitem id="home_page_petstore_menu" parent="petstore_menu"
1794 name="Home Page" action="action_home_page"/>
1796 Architecture of the Views
1797 -------------------------
1799 Much of Odoo web's usefulness (and complexity) resides in views. Each view
1800 type is a way of displaying a model in the client.
1805 When an ``ActionManager`` instance receive an action of type
1806 ``ir.actions.act_window``, it delegates the synchronization and handling of
1807 the views themselves to a *view manager*, which will then set up one or
1808 multiple views depending on the original action's requirements:
1810 .. image:: web/viewarchitecture.*
1817 Most :ref:`Odoo views <reference/views>` are implemented through a subclass
1818 of :class:`openerp.web.View` which provides a bit of generic basic structure
1819 for handling events and displaying model information.
1821 The *search view* is considered a view type by the main Odoo framework, but
1822 handled separately by the web client (as it's a more permanent fixture and
1823 can interact with other views, which regular views don't do).
1825 A view is responsible for loading its own description XML (using
1826 :py:class:`~openerp.models.Model.fields_view_get`) and any other data source
1827 it needs. To that purpose, views are provided with an optional view
1828 identifier set as the :attr:`~openerp.web.View.view_id` attribute.
1830 Views are also provided with a :class:`~openerp.web.DataSet` instance which
1831 holds most necessary model information (the model name and possibly various
1834 Views may also want to handle search queries by overriding
1835 :func:`~openerp.web.View.do_search`, and updating their
1836 :class:`~openerp.web.DataSet` as necessary.
1838 The Form View Fields
1839 --------------------
1841 A common Odoo web need is the extension of the form view to add new ways of
1842 displaying form fields.
1844 All built-in fields have a default display implementation, creating a new
1845 form widget may be necessary to correctly interact with a new field type
1846 (e.g. a :term:`GIS` field) or to provide new representations and ways to
1847 interact with existing field types (e.g. validate
1848 :py:class:`~openerp.fields.Char` fields which should contain email addresses
1849 and display them as email links).
1851 To explicitly specify which form widget should be used to display a field,
1852 simply use the ``widget`` attribute in the view's XML description:
1856 <field name="contact_mail" widget="email"/>
1860 * the same widget is used in both "view" (read-only) and "edition" modes
1861 of a form view, it's not possible to use a widget in one and an other
1863 * and a given field (name) can not be used multiple times in the same form
1864 * a widget may ignore the current mode of the form view and remain the
1865 same in both view and edition
1867 .. todo:: most of this should probably move to an advanced form view guide
1869 Fields are instantiated by the form view after it has read its XML description
1870 and constructed the corresponding HTML representing that description. After
1871 that, the form view will communicate with the field objects using some
1872 methods. Theses methods are defined by the ``FieldInterface``
1873 interface. Almost all fields inherit the ``AbstractField`` abstract
1874 class. That class defines some default mechanisms that need to be implemented
1877 Here are some of the responsibilities of a field class:
1879 * The field class must display and allow the user to edit the value of the field.
1880 * It must correctly implement the 3 field attributes available in all fields
1881 of Odoo. The ``AbstractField`` class already implements an algorithm that
1882 dynamically calculates the value of these attributes (they can change at any
1883 moment because their value change according to the value of other
1884 fields). Their values are stored in *Widget Properties* (the widget
1885 properties were explained earlier in this guide). It is the responsibility
1886 of each field class to check these widget properties and dynamically adapt
1887 depending of their values. Here is a description of each of these
1890 * ``required``: The field must have a value before saving. If ``required``
1891 is ``true`` and the field doesn't have a value, the method
1892 ``is_valid()`` of the field must return ``false``.
1893 * ``invisible``: When this is ``true``, the field must be invisible. The
1894 ``AbstractField`` class already has a basic implementation of this
1895 behavior that fits most fields.
1896 * ``readonly``: When ``true``, the field must not be editable by the
1897 user. Most fields in Odoo have a completely different behavior depending
1898 on the value of ``readonly``. As example, the ``FieldChar`` displays an
1899 HTML ``<input>`` when it is editable and simply displays the text when
1900 it is read-only. This also means it has much more code it would need to
1901 implement only one behavior, but this is necessary to ensure a good user
1904 * Fields have two methods, ``set_value()`` and ``get_value()``, which are
1905 called by the form view to give it the value to display and get back the new
1906 value entered by the user. These methods must be able to handle the value as
1907 given by the Odoo server when a ``read()`` is performed on a model and give
1908 back a valid value for a ``write()``. Remember that the JavaScript/Python
1909 data types used to represent the values given by ``read()`` and given to
1910 ``write()`` is not necessarily the same in Odoo. As example, when you read a
1911 many2one, it is always a tuple whose first value is the id of the pointed
1912 record and the second one is the name get (ie: ``(15, "Agrolait")``). But
1913 when you write a many2one it must be a single integer, not a tuple
1914 anymore. ``AbstractField`` has a default implementation of these methods
1915 that works well for simple data type and set a widget property named
1918 Please note that, to better understand how to implement fields, you are
1919 strongly encouraged to look at the definition of the ``FieldInterface``
1920 interface and the ``AbstractField`` class directly in the code of the Odoo web
1923 Creating a New Type of Field
1924 ''''''''''''''''''''''''''''
1926 In this part we will explain how to create a new type of field. The example
1927 here will be to re-implement the ``FieldChar`` class and explain progressively
1930 Simple Read-Only Field
1931 """"""""""""""""""""""
1933 Here is a first implementation that will only be able to display a text. The
1934 user will not be able to modify the content of the field.
1936 .. code-block:: javascript
1938 local.FieldChar2 = instance.web.form.AbstractField.extend({
1940 this._super.apply(this, arguments);
1941 this.set("value", "");
1943 render_value: function() {
1944 this.$el.text(this.get("value"));
1948 instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');
1950 In this example, we declare a class named ``FieldChar2`` inheriting from
1951 ``AbstractField``. We also register this class in the registry
1952 ``instance.web.form.widgets`` under the key ``char2``. That will allow us to
1953 use this new field in any form view by specifying ``widget="char2"`` in the
1954 ``<field/>`` tag in the XML declaration of the view.
1956 In this example, we define a single method: ``render_value()``. All it does is
1957 display the widget property ``value``. Those are two tools defined by the
1958 ``AbstractField`` class. As explained before, the form view will call the
1959 method ``set_value()`` of the field to set the value to display. This method
1960 already has a default implementation in ``AbstractField`` which simply sets
1961 the widget property ``value``. ``AbstractField`` also watch the
1962 ``change:value`` event on itself and calls the ``render_value()`` when it
1963 occurs. So, ``render_value()`` is a convenience method to implement in child
1964 classes to perform some operation each time the value of the field changes.
1966 In the ``init()`` method, we also define the default value of the field if
1967 none is specified by the form view (here we assume the default value of a
1968 ``char`` field should be an empty string).
1973 Fields that only display their content and don't give the possibility to the
1974 user to modify it can be useful, but most fields in Odoo allow edition
1975 too. This makes the field classes more complicated, mostly because fields are
1976 supposed to handle both and editable and non-editable mode, those modes are
1977 often completely different (for design and usability purpose) and the fields
1978 must be able to switch from one mode to another at any moment.
1980 To know in which mode the current field should be, the ``AbstractField`` class
1981 sets a widget property named ``effective_readonly``. The field should watch
1982 the changes in that widget property and display the correct mode
1983 accordingly. Example::
1985 local.FieldChar2 = instance.web.form.AbstractField.extend({
1987 this._super.apply(this, arguments);
1988 this.set("value", "");
1991 this.on("change:effective_readonly", this, function() {
1992 this.display_field();
1993 this.render_value();
1995 this.display_field();
1996 return this._super();
1998 display_field: function() {
2000 this.$el.html(QWeb.render("FieldChar2", {widget: this}));
2001 if (! this.get("effective_readonly")) {
2002 this.$("input").change(function() {
2003 self.internal_set_value(self.$("input").val());
2007 render_value: function() {
2008 if (this.get("effective_readonly")) {
2009 this.$el.text(this.get("value"));
2011 this.$("input").val(this.get("value"));
2016 instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');
2020 <t t-name="FieldChar2">
2021 <div class="oe_field_char2">
2022 <t t-if="! widget.get('effective_readonly')">
2023 <input type="text"></input>
2028 In the ``start()`` method (which is called right after a widget has been
2029 appended to the DOM), we bind on the event ``change:effective_readonly``. That
2030 will allow use to redisplay the field each time the widget property
2031 ``effective_readonly`` changes. This event handler will call
2032 ``display_field()``, which is also called directly in ``start()``. This
2033 ``display_field()`` was created specifically for this field, it's not a method
2034 defined in ``AbstractField`` or any other class. This is the method we will
2035 use to display the content of the field depending we are in read-only mode or
2038 From now on the conception of this field is quite typical, except there is a
2039 lot of verifications to know the state of the ``effective_readonly`` property:
2041 * In the QWeb template used to display the content of the widget, it displays
2042 an ``<input type="text" />`` if we are in read-write mode and nothing in
2043 particular in read-only mode.
2044 * In the ``display_field()`` method, we have to bind on the ``change`` event
2045 of the ``<input type="text" />`` to know when the user has changed the
2046 value. When it happens, we call the ``internal_set_value()`` method with the
2047 new value of the field. This is a convenience method provided by the
2048 ``AbstractField`` class. That method will set a new value in the ``value``
2049 property but will not trigger a call to ``render_value()`` (which is not
2050 necessary since the ``<input type="text" />`` already contains the correct
2052 * In ``render_value()``, we use a completely different code to display the
2053 value of the field depending if we are in read-only or in read-write mode.
2055 .. exercise:: Create a Color Field
2057 Create a ``FieldColor`` class. The value of this field should be a string
2058 containing a color code like those used in CSS (example: ``#FF0000`` for
2059 red). In read-only mode, this color field should display a little block
2060 whose color corresponds to the value of the field. In read-write mode, you
2061 should display an ``<input type="color" />``. That type of ``<input />``
2062 is an HTML5 component that doesn't work in all browsers but works well in
2063 Google Chrome. So it's OK to use as an exercise.
2065 You can use that widget in the form view of the ``message_of_the_day``
2066 model for its field named ``color``. As a bonus, you can change the
2067 ``MessageOfTheDay`` widget created in the previous part of this guide to
2068 display the message of the day with the background color indicated in the
2073 .. code-block:: javascript
2075 local.FieldColor = instance.web.form.AbstractField.extend({
2077 'change input': function (e) {
2078 if (!this.get('effective_readonly')) {
2079 this.internal_set_value($(e.currentTarget).val());
2084 this._super.apply(this, arguments);
2085 this.set("value", "");
2088 this.on("change:effective_readonly", this, function() {
2089 this.display_field();
2090 this.render_value();
2092 this.display_field();
2093 return this._super();
2095 display_field: function() {
2096 this.$el.html(QWeb.render("FieldColor", {widget: this}));
2098 render_value: function() {
2099 if (this.get("effective_readonly")) {
2100 this.$(".oe_field_color_content").css("background-color", this.get("value") || "#FFFFFF");
2102 this.$("input").val(this.get("value") || "#FFFFFF");
2106 instance.web.form.widgets.add('color', 'instance.oepetstore.FieldColor');
2110 <t t-name="FieldColor">
2111 <div class="oe_field_color">
2112 <t t-if="widget.get('effective_readonly')">
2113 <div class="oe_field_color_content" />
2115 <t t-if="! widget.get('effective_readonly')">
2116 <input type="color"></input>
2123 .oe_field_color_content {
2126 border: 1px solid black;
2129 The Form View Custom Widgets
2130 ----------------------------
2132 Form fields are used to edit a single field, and are intrinsically linked to
2133 a field. Because this may be limiting, it is also possible to create
2134 *form widgets* which are not so restricted and have less ties to a specific
2137 Custom form widgets can be added to a form view through the ``widget`` tag:
2141 <widget type="xxx" />
2143 This type of widget will simply be created by the form view during the
2144 creation of the HTML according to the XML definition. They have properties in
2145 common with the fields (like the ``effective_readonly`` property) but they are
2146 not assigned a precise field. And so they don't have methods like
2147 ``get_value()`` and ``set_value()``. They must inherit from the ``FormWidget``
2150 Form widgets can interact with form fields by listening for their changes and
2151 fetching or altering their values. They can access form fields through
2152 their :attr:`~openerp.web.form.FormWidget.field_manager` attribute::
2154 local.WidgetMultiplication = instance.web.form.FormWidget.extend({
2157 this.field_manager.on("field_changed:integer_a", this, this.display_result);
2158 this.field_manager.on("field_changed:integer_b", this, this.display_result);
2159 this.display_result();
2161 display_result: function() {
2162 var result = this.field_manager.get_field_value("integer_a") *
2163 this.field_manager.get_field_value("integer_b");
2164 this.$el.text("a*b = " + result);
2168 instance.web.form.custom_widgets.add('multiplication', 'instance.oepetstore.WidgetMultiplication');
2170 :attr:`~openerp.web.form.FormWidget` is generally the
2171 :class:`~openerp.web.form.FormView` itself, but features used from it should
2172 be limited to those defined by :class:`~openerp.web.form.FieldManagerMixin`,
2173 the most useful being:
2175 * :func:`~openerp.web.form.FieldManagerMixin.get_field_value(field_name)`
2176 which returns the value of a field.
2177 * :func:`~openerp.web.form.FieldManagerMixin.set_values(values)` sets multiple
2178 field values, takes a mapping of ``{field_name: value_to_set}``
2179 * An event :samp:`field_changed:{field_name}` is triggered any time the value
2180 of the field called ``field_name`` is changed
2182 .. exercise:: Show Coordinates on Google Map
2184 Add two fields to ``product.product`` storing a latitude and a longitude,
2185 then create a new form widget to display the latitude and longitude of
2186 a product's origin on a map
2188 To display the map, use Google Map's embedding:
2190 .. code-block:: html
2192 <iframe width="400" height="300" src="https://maps.google.com/?ie=UTF8&ll=XXX,YYY&output=embed">
2195 where ``XXX`` should be replaced by the latitude and ``YYY`` by the
2198 Display the two position fields and a map widget using them in a new
2199 notebook page of the product's form view.
2203 .. code-block:: javascript
2205 local.WidgetCoordinates = instance.web.form.FormWidget.extend({
2208 this.field_manager.on("field_changed:provider_latitude", this, this.display_map);
2209 this.field_manager.on("field_changed:provider_longitude", this, this.display_map);
2212 display_map: function() {
2213 this.$el.html(QWeb.render("WidgetCoordinates", {
2214 "latitude": this.field_manager.get_field_value("provider_latitude") || 0,
2215 "longitude": this.field_manager.get_field_value("provider_longitude") || 0,
2220 instance.web.form.custom_widgets.add('coordinates', 'local.WidgetCoordinates');
2224 <t t-name="WidgetCoordinates">
2225 <iframe width="400" height="300"
2226 t-attf-src="https://maps.google.com/?ie=UTF8&ll={{latitude}},{{longitude}}&output=embed">
2230 .. exercise:: Get the Current Coordinate
2232 Add a button resetting the product's coordinates to the location of the
2233 user, you can get these coordinates using the
2234 `javascript geolocation API`_.
2236 Now we would like to display an additional button to automatically set the
2237 coordinates to the location of the current user.
2239 To get the coordinates of the user, an easy way is to use the geolocation
2240 JavaScript API. `See the online documentation to know how to use it`_.
2242 .. _See the online documentation to know how to use it: http://www.w3schools.com/html/html5_geolocation.asp
2244 Please also note that it wouldn't be very logical to allow the user to
2245 click on that button when the form view is in read-only mode. So, this
2246 custom widget should handle correctly the ``effective_readonly`` property
2247 just like any field. One way to do this would be to make the button
2248 disappear when ``effective_readonly`` is true.
2252 .. code-block:: javascript
2254 local.WidgetCoordinates = instance.web.form.FormWidget.extend({
2256 'click button': function () {
2257 navigator.geolocation.getCurrentPosition(
2258 this.proxy('received_position'));
2262 var sup = this._super();
2263 this.field_manager.on("field_changed:provider_latitude", this, this.display_map);
2264 this.field_manager.on("field_changed:provider_longitude", this, this.display_map);
2265 this.on("change:effective_readonly", this, this.display_map);
2269 display_map: function() {
2270 this.$el.html(QWeb.render("WidgetCoordinates", {
2271 "latitude": this.field_manager.get_field_value("provider_latitude") || 0,
2272 "longitude": this.field_manager.get_field_value("provider_longitude") || 0,
2274 this.$("button").toggle(! this.get("effective_readonly"));
2276 received_position: function(obj) {
2277 this.field_manager.set_values({
2278 "provider_latitude": obj.coords.latitude,
2279 "provider_longitude": obj.coords.longitude,
2284 instance.web.form.custom_widgets.add('coordinates', 'local.WidgetCoordinates');
2288 <t t-name="WidgetCoordinates">
2289 <iframe width="400" height="300"
2290 t-att-src="https://maps.google.com/?ie=UTF8&ll={{latitude}},{{longitude}}&output=embed">
2292 <button>Get My Current Coordinate</button>
2295 .. [#classes] as a separate concept from instances. In many languages classes
2296 are full-fledged objects and themselves instance (of
2297 metaclasses) but there remains two fairly separate hierarchies
2298 between classes and instances
2299 .. [#dombugs] as well as papering over cross-browser differences, although
2300 this has become less necessary over time
2302 .. _jQuery: http://jquery.org
2303 .. _Underscore.js: http://underscorejs.org
2304 .. _git: http://git-scm.com
2305 .. _CSS: http://www.w3.org/Style/CSS/Overview.en.html
2306 .. _Simple JavaScript Inheritance:
2307 http://ejohn.org/blog/simple-javascript-inheritance/
2308 .. _W3C DOM: http://www.w3.org/TR/DOM-Level-3-Core/
2309 .. _Qt: http://qt-project.org
2310 .. _Cocoa: https://developer.apple.com/technologies/mac/cocoa.html
2311 .. _GTK: http://www.gtk.org
2312 .. _template engine: http://en.wikipedia.org/wiki/Web_template_system
2313 .. _cross-site scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
2314 .. _jQuery event object: http://api.jquery.com/category/events/event-object/
2315 .. _$.ajax: http://api.jquery.com/jquery.ajax/
2316 .. _base64: http://en.wikipedia.org/wiki/Base64
2317 .. _javascript geolocation API:
2318 http://diveintohtml5.info/geolocation.html
2319 .. _PostgreSQL: http://en.wikipedia.org/wiki/PostgreSQL
2320 .. _positional arguments:
2321 .. _keyword arguments:
2322 https://docs.python.org/2/glossary.html#term-argument