5 .. highlight:: javascript
9 This guide is about creating modules for Odoo's web client. To create websites
10 with Odoo, see :doc:`website`.
14 This guide assumes knowledge of:
16 * Javascript basics and good practices
21 A Simple Module to Test the Web Framework
22 -----------------------------------------
24 It's not really possible to include the multiple JavaScript files that
25 constitute the Odoo web framework in a simple HTML file like we did in the
26 previous chapter. So we will create a simple module in Odoo that contains some
27 configuration to have a web component that will give us the possibility to
28 test the web framework.
30 To download the example module, use this bazaar command:
34 bzr branch lp:~niv-openerp/+junk/oepetstore -r 1
36 Now you must add that folder to your the addons path when you launch Odoo
37 (``--addons-path`` parameter when you launch the ``odoo.py`` executable). Then
38 create a new database and install the new module ``oepetstore``.
40 Now let's see what files exist in that module:
59 This new module already contains some customization that should be easy to
60 understand if you already coded an Odoo module like a new table, some views,
61 menu items, etc... We'll come back to these elements later because they will
62 be useful to develop some example web module. Right now let's concentrate on
63 the essential: the files dedicated to web development.
65 Please note that all files to be used in the web part of an Odoo module must
66 always be placed in a ``static`` folder inside the module. This is mandatory
67 due to possible security issues. The fact we created the folders ``css``,
68 ``js`` and ``xml`` is just a convention.
70 ``oepetstore/static/css/petstore.css`` is our CSS file. It is empty right now
71 but we will add any CSS we need later.
73 ``oepetstore/static/xml/petstore.xml`` is an XML file that will contain our
74 QWeb templates. Right now it is almost empty too. Those templates will be
75 explained later, in the part dedicated to QWeb templates.
77 ``oepetstore/static/js/petstore.js`` is probably the most interesting part. It
78 contains the JavaScript of our application. Here is what it looks like right
81 openerp.oepetstore = function(instance) {
82 var _t = instance.web._t,
83 _lt = instance.web._lt;
84 var QWeb = instance.web.qweb;
86 instance.oepetstore = {};
88 instance.oepetstore.HomePage = instance.web.Widget.extend({
90 console.log("pet store home page loaded");
94 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
97 The multiple components of that file will explained progressively. Just know
98 that it doesn't do much things right now except display a blank page and print
99 a small message in the console.
101 Like Odoo's XML files containing views or data, these files must be indicated
102 in the ``__openerp__.py`` file. Here are the lines we added to explain to the
103 web client it has to load these files:
105 .. code-block:: python
107 'js': ['static/src/js/*.js'],
108 'css': ['static/src/css/*.css'],
109 'qweb': ['static/src/xml/*.xml'],
111 These configuration parameters use wildcards, so we can add new files without
112 altering ``__openerp__.py``: they will be loaded by the web client as long as
113 they have the correct extension and are in the correct folder.
117 In Odoo, all JavaScript files are, by default, concatenated in a single
118 file. Then we apply an operation called the *minification* on that
119 file. The minification will remove all comments, white spaces and
120 line-breaks in the file. Finally, it is sent to the user's browser.
122 That operation may seem complex, but it's a common procedure in big
123 application like Odoo with a lot of JavaScript files. It allows to load
124 the application a lot faster.
126 It has the main drawback to make the application almost impossible to
127 debug, which is very bad to develop. The solution to avoid this
128 side-effect and still be able to debug is to append a small argument to
129 the URL used to load Odoo: ``?debug``. So the URL will look like this:
133 http://localhost:8069/?debug
135 When you use that type of URL, the application will not perform all that
136 concatenation-minification process on the JavaScript files. The
137 application will take more time to load but you will be able to develop
138 with decent debugging tools.
140 Odoo JavaScript Module
141 -------------------------
143 In the previous chapter, we explained that JavaScript do not have a correct
144 mechanism to namespace the variables declared in different JavaScript files
145 and we proposed a simple method called the Module pattern.
147 In Odoo's web framework there is an equivalent of that pattern which is
148 integrated with the rest of the framework. Please note that **an Odoo web
149 module is a separate concept from an Odoo addon**. An addon is a folder with a
150 lot of files, a web module is not much more than a namespace for JavaScript.
152 The ``oepetstore/static/js/petstore.js`` already declare such a module::
154 openerp.oepetstore = function(instance) {
155 instance.oepetstore = {};
157 instance.oepetstore.xxx = ...;
160 In Odoo's web framework, you declare a JavaScript module by declaring a
161 function that you put in the global variable ``openerp``. The attribute you
162 set in that object must have the exact same name than your Odoo addon (this
163 addon is named ``oepetstore``, if I set ``openerp.petstore`` instead of
164 ``openerp.oepetstore`` that will not work).
166 That function will be called when the web client decides to load your
167 addon. It is given a parameter named ``instance``, which represents the
168 current Odoo web client instance and contains all the data related to the
169 current session as well as the variables of all web modules.
171 The convention is to create a new namespace inside the ``instance`` object
172 which has the same name than you addon. That's why we set an empty dictionary
173 in ``instance.oepetstore``. That dictionary is the namespace we will use to
174 declare all classes and variables used inside our module.
179 JavaScript doesn't have a class mechanism like most object-oriented
180 programming languages. To be more exact, it provides language elements to make
181 object-oriented programming but you have to define by yourself how you choose
182 to do it. Odoo's web framework provide tools to simplify this and let
183 programmers code in a similar way they would program in other languages like
184 Java. That class system is heavily inspired by John Resig's `Simple JavaScript
185 Inheritance <http://ejohn.org/blog/simple-javascript-inheritance/>`_.
187 To define a new class, you need to extend the :class:`openerp.web.Class`
190 instance.oepetstore.MyClass = instance.web.Class.extend({
191 say_hello: function() {
192 console.log("hello");
196 As you can see, you have to call :func:`instance.web.Class.extend` and give
197 it a dictionary. That dictionary will contain the methods and class attributes
198 of our new class. Here we simply put a method named ``say_hello()``. This
199 class can be instantiated and used like this::
201 var my_object = new instance.oepetstore.MyClass();
202 my_object.say_hello();
203 // print "hello" in the console
205 You can access the attributes of a class inside a method using ``this``::
207 instance.oepetstore.MyClass = instance.web.Class.extend({
208 say_hello: function() {
209 console.log("hello", this.name);
213 var my_object = new instance.oepetstore.MyClass();
214 my_object.name = "Nicolas";
215 my_object.say_hello();
216 // print "hello Nicolas" in the console
218 Classes can have a constructor, it is just a method named ``init()``. You can
219 pass parameters to the constructor like in most language::
221 instance.oepetstore.MyClass = instance.web.Class.extend({
222 init: function(name) {
225 say_hello: function() {
226 console.log("hello", this.name);
230 var my_object = new instance.oepetstore.MyClass("Nicolas");
231 my_object.say_hello();
232 // print "hello Nicolas" in the console
234 Classes can be inherited. To do so, use :func:`~openerp.web.Class.extend`
235 directly on your class just like you extended :class:`~openerp.web.Class`::
237 instance.oepetstore.MySpanishClass = instance.oepetstore.MyClass.extend({
238 say_hello: function() {
239 console.log("hola", this.name);
243 var my_object = new instance.oepetstore.MySpanishClass("Nicolas");
244 my_object.say_hello();
245 // print "hola Nicolas" in the console
247 When overriding a method using inheritance, you can use ``this._super()`` to
248 call the original method. ``this._super()`` is not a normal method of your
249 class, you can consider it's magic. Example::
251 instance.oepetstore.MySpanishClass = instance.oepetstore.MyClass.extend({
252 say_hello: function() {
254 console.log("translation in Spanish: hola", this.name);
258 var my_object = new instance.oepetstore.MySpanishClass("Nicolas");
259 my_object.say_hello();
260 // print "hello Nicolas \n translation in Spanish: hola Nicolas" in the console
265 In previous chapter we discovered jQuery and its DOM manipulation tools. It's
266 useful, but it's not sufficient to structure a real application. Graphical
267 user interface libraries like Qt, GTK or Windows Forms have classes to
268 represent visual components. In Odoo, we have the
269 :class:`~openerp.web.Widget` class. A widget is a generic component
270 dedicated to display content to the user.
275 The start module you installed already contains a small widget::
277 instance.oepetstore.HomePage = instance.web.Widget.extend({
279 console.log("pet store home page loaded");
283 Here we create a simple widget by extending the :class:`openerp.web.Widget`
284 class. This one defines a method named :func:`~openerp.web.Widget.start` that
285 doesn't do anything really interesting right now.
287 You may also have noticed this line at the end of the file::
289 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
291 This last line registers our basic widget as a client action. Client actions
292 will be explained in the next part of this guide. For now, just remember that
293 this is what allows our widget to be displayed when we click on the
294 :menuselection:`Pet Store --> Pet Store --> Home Page` menu element.
299 Widgets have a lot of methods and features, but let's start with the basics:
300 display some data inside the widget and how to instantiate a widget and
303 The ``HomePage`` widget already has a :func:`~openerp.web.Widget.start`
304 method. That method is automatically called after the widget has been
305 instantiated and it has received the order to display its content. We will use
306 it to display some content to the user.
308 To do so, we will also use the :attr:`~openerp.web.Widget.$el` attribute
309 that all widgets contain. That attribute is a jQuery object with a reference
310 to the HTML element that represents the root of our widget. A widget can
311 contain multiple HTML elements, but they must be contained inside one single
312 element. By default, all widgets have an empty root element which is a
313 ``<div>`` HTML element.
315 A ``<div>`` element in HTML is usually invisible for the user if it does not
316 have any content. That explains why when the ``instance.oepetstore.HomePage``
317 widget is displayed you can't see anything: it simply doesn't have any
318 content. To show something, we will use some simple jQuery methods on that
319 object to add some HTML in our root element::
321 instance.oepetstore.HomePage = instance.web.Widget.extend({
323 this.$el.append("<div>Hello dear Odoo user!</div>");
327 That message will now appear when you go to the menu :menuselection:`Pet Store
328 --> Pet Store --> Home Page` (remember you need to refresh your web browser,
329 although there is not need to restart Odoo's server).
331 Now you should learn how to instantiate a widget and display its content. To
332 do so, we will create a new widget::
334 instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
336 this.$el.append("<div>We are so happy to see you again in this menu!</div>");
340 Now we want to display the ``instance.oepetstore.GreetingsWidget`` inside the
341 home page. To do so we can use the :func:`~openerp.web.Widget.append`
342 method of ``Widget``::
344 instance.oepetstore.HomePage = instance.web.Widget.extend({
346 this.$el.append("<div>Hello dear Odoo user!</div>");
347 var greeting = new instance.oepetstore.GreetingsWidget(this);
348 greeting.appendTo(this.$el);
352 Here, the ``HomePage`` instantiate a ``GreetingsWidget`` (the first argument
353 of the constructor of ``GreetingsWidget`` will be explained in the next
354 part). Then it asks the ``GreetingsWidget`` to insert itself inside the DOM,
355 more precisely directly under the ``HomePage`` widget.
357 When the :func:`~openerp.web.Widget.appendTo` method is called, it asks the
358 widget to insert itself and to display its content. It's during the call to
359 :func:`~openerp.web.Widget.appentTo` that the
360 :func:`~openerp.web.Widget.start` method will be called.
362 To check the consequences of that code, let's use Chrome's DOM explorer. But
363 before that we will modify a little bit our widgets to have some classes on
364 some of our ``<div>`` elements so we can clearly see them in the explorer::
366 instance.oepetstore.HomePage = instance.web.Widget.extend({
368 this.$el.addClass("oe_petstore_homepage");
372 instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
374 this.$el.addClass("oe_petstore_greetings");
379 The result will be this if you can find the correct DOM part in the DOM explorer:
383 <div class="oe_petstore_homepage">
384 <div>Hello dear Odoo user!</div>
385 <div class="oe_petstore_greetings">
386 <div>We are so happy to see you again in this menu!</div>
390 Here we can clearly see the two ``<div>`` created implicitly by
391 :class:`~openerp.web.Widget`, because we added some classes on them. We can
392 also see the two divs containing messages we created using the jQuery methods
393 on ``$el``. Finally, note the ``<div class="oe_petstore_greetings">`` element
394 which represents the ``GreetingsWidget`` instance is *inside* the ``<div
395 class="oe_petstore_homepage">`` which represents the ``HomePage`` instance.
397 Widget Parents and Children
398 %%%%%%%%%%%%%%%%%%%%%%%%%%%
400 In the previous part, we instantiated a widget using this syntax::
402 new instance.oepetstore.GreetingsWidget(this);
404 The first argument is ``this``, which in that case was a ``HomePage``
405 instance. This serves to indicate the Widget what other widget is his parent.
407 As we've seen, widgets are usually inserted in the DOM by another widget and
408 *inside* that other widget. This means most widgets are always a part of
409 another widget. We call the container the *parent*, and the contained widget
412 Due to multiple technical and conceptual reasons, it is necessary for a widget
413 to know who is his parent and who are its children. This is why we have that
414 first parameter in the constructor of all widgets.
416 :func:`~openerp.web.Widget.getParent` can be used to get the parent of a
419 instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
421 console.log(this.getParent().$el );
422 // will print "div.oe_petstore_homepage" in the console
426 :func:`~openerp.web.Widget.getChildren` can be used to get a list of its
429 instance.oepetstore.HomePage = instance.web.Widget.extend({
431 var greeting = new instance.oepetstore.GreetingsWidget(this);
432 greeting.appendTo(this.$el);
433 console.log(this.getChildren()[0].$el);
434 // will print "div.oe_petstore_greetings" in the console
438 You should also remember that, when you override the
439 :func:`~openerp.web.Widget.init` method of a widget you should always put the
440 parent as first parameter are pass it to ``this._super()``::
442 instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
443 init: function(parent, name) {
449 Finally, if a widget does not logically have a parent (ie: because it's the
450 first widget you instantiate in an application), you can give null as a parent
453 new instance.oepetstore.GreetingsWidget(null);
458 If you can display content to your users, you should also be able to erase
459 it. This can simply be done using the :func:`~openerp.web.Widget.destroy`
464 When a widget is destroyed it will first call
465 :func:`~openerp.web.Widget.destroy` on all its children. Then it erases itself
466 from the DOM. The recursive call to destroy from parents to children is very
467 useful to clean properly complex structures of widgets and avoid memory leaks
468 that can easily appear in big JavaScript applications.
472 The QWeb Template Engine
473 ------------------------
475 The previous part of the guide showed how to define widgets that are able to
476 display HTML to the user. The example ``GreetingsWidget`` used a syntax like
479 this.$el.append("<div>Hello dear Odoo user!</div>");
481 This technically allow us to display any HTML, even if it is very complex and
482 require to be generated by code. Although generating text using pure
483 JavaScript is not very nice, that would necessitate to copy-paste a lot of
484 HTML lines inside our JavaScript source file, add the ``"`` character at the
485 beginning and the end of each line, etc...
487 The problem is exactly the same in most programming languages needing to
488 generate HTML. That's why they typically use template engines. Example of
489 template engines are Velocity, JSP (Java), Mako, Jinja (Python), Smarty (PHP),
492 In Odoo we use a template engine developed specifically for Odoo's web
493 client. Its name is QWeb.
495 QWeb is an XML-based templating language, similar to `Genshi
496 <http://en.wikipedia.org/wiki/Genshi_(templating_language)>`_, `Thymeleaf
497 <http://en.wikipedia.org/wiki/Thymeleaf>`_ or `Facelets
498 <http://en.wikipedia.org/wiki/Facelets>`_ with a few peculiarities:
500 * It's implemented fully in JavaScript and rendered in the browser.
501 * Each template file (XML files) contains multiple templates, where template
502 engine usually have a 1:1 mapping between template files and templates.
503 * It has special support in Odoo Web's :class:`~openerp.web.Widget`, though it
504 can be used outside of Odoo's web client (and it's possible to use
505 :class:`~openerp.web.Widget` without relying on QWeb).
507 The rationale behind using QWeb instead of existing javascript template
508 engines is that its extension mechanism is very similar to the Odoo view
509 inheritance mechanism. Like Odoo views a QWeb template is an XML tree and
510 therefore XPath or DOM manipulations are easy to perform on it.
512 Using QWeb inside a Widget
513 %%%%%%%%%%%%%%%%%%%%%%%%%%
515 First let's define a simple QWeb template in
516 ``oepetstore/static/src/xml/petstore.xml`` file, the exact meaning will be
521 <?xml version="1.0" encoding="UTF-8"?>
523 <templates xml:space="preserve">
524 <t t-name="HomePageTemplate">
525 <div style="background-color: red;">This is some simple HTML</div>
529 Now let's modify the ``HomePage`` class. Remember that enigmatic line at the
530 beginning the the JavaScript source file?
534 var QWeb = instance.web.qweb;
536 This is a line we recommend to copy-paste in all Odoo web modules. It is the
537 object giving access to all templates defined in template files that were
538 loaded by the web client. We can use the template we defined in our XML
539 template file like this::
541 instance.oepetstore.HomePage = instance.web.Widget.extend({
543 this.$el.append(QWeb.render("HomePageTemplate"));
547 Calling the ``QWeb.render()`` method asks to render the template identified by
548 the string passed as first parameter.
550 Another possibility commonly seen in Odoo code is to use ``Widget``'s
551 integration with QWeb::
553 instance.oepetstore.HomePage = instance.web.Widget.extend({
554 template: "HomePageTemplate",
560 When you put a ``template`` class attribute in a widget, the widget knows it
561 has to call ``QWeb.render()`` to render that template.
563 Please note there is a difference between those two syntaxes. When you use
564 ``Widget``'s QWeb integration the ``QWeb.render()`` method is called *before*
565 the widget calls :func:`~openerp.web.Widget.start`. It will also take the root
566 element of the rendered template and put it as a replacement of the default
567 root element generated by the :class:`~openerp.web.Widget` class. This will
568 alter the behavior, so you should remember it.
573 Like with all template engines, QWeb templates can contain code able to
574 manipulate data that is given to the template. To pass data to QWeb, use the
575 second argument to ``QWeb.render()``:
579 <t t-name="HomePageTemplate">
580 <div>Hello <t t-esc="name"/></div>
585 QWeb.render("HomePageTemplate", {name: "Nicolas"});
591 <div>Hello Nicolas</div>
593 When you use :class:`~openerp.web.Widget`'s integration you can not pass
594 additional data to the template. Instead the template will have a unique
595 ``widget`` variable which is a reference to the current widget:
599 <t t-name="HomePageTemplate">
600 <div>Hello <t t-esc="widget.name"/></div>
605 instance.oepetstore.HomePage = instance.web.Widget.extend({
606 template: "HomePageTemplate",
607 init: function(parent) {
609 this.name = "Nicolas";
619 <div>Hello Nicolas</div>
624 Now that we know everything about rendering templates we can try to understand
627 All QWeb directives use XML attributes beginning with the prefix ``t-``. To
628 declare new templates, we add a ``<t t-name="...">`` element into the XML
629 template file inside the root element ``<templates>``::
632 <t t-name="HomePageTemplate">
633 <div>This is some simple HTML</div>
637 ``t-name`` simply declares a template that can be called using
643 To put some text in the HTML, use ``t-esc``:
647 <t t-name="HomePageTemplate">
648 <div>Hello <t t-esc="name"/></div>
652 This will output the variable ``name`` and escape its content in case it
653 contains some characters that looks like HTML. Please note the attribute
654 ``t-esc`` can contain any type of JavaScript expression:
658 <t t-name="HomePageTemplate">
659 <div><t t-esc="3+5"/></div>
671 If you know you have some HTML contained in a variable, use ``t-raw`` instead
676 <t t-name="HomePageTemplate">
677 <div><t t-raw="some_html"/></div>
683 The basic alternative block of QWeb is ``t-if``:
687 <t t-name="HomePageTemplate">
689 <t t-if="true == true">
692 <t t-if="true == false">
698 Although QWeb does not contains any structure for else.
703 To iterate on a list, use ``t-foreach`` and ``t-as``:
707 <t t-name="HomePageTemplate">
709 <t t-foreach="names" t-as="name">
711 Hello <t t-esc="name"/>
717 Setting the Value of an XML Attribute
718 '''''''''''''''''''''''''''''''''''''
720 QWeb has a special syntax to set the value of an attribute. You must use
721 ``t-att-xxx`` and replace ``xxx`` with the name of the attribute:
725 <t t-name="HomePageTemplate">
728 <input type="text" t-att-value="defaultName"/>
732 To Learn More About QWeb
733 ''''''''''''''''''''''''
735 For a QWeb reference, see :ref:`reference/qweb`.
740 .. exercise:: Usage of QWeb in Widgets
742 Create a widget whose constructor contains two parameters aside from
743 ``parent``: ``product_names`` and ``color``. ``product_names`` is a list
744 of strings, each one being a name of product. ``color`` is a string
745 containing a color in CSS color format (ie: ``#000000`` for black). That
746 widget should display the given product names one under the other, each
747 one in a separate box with a background color with the value of ``color``
748 and a border. You must use QWeb to render the HTML. This exercise will
749 necessitate some CSS that you should put in
750 ``oepetstore/static/src/css/petstore.css``. Display that widget in the
751 ``HomePage`` widget with a list of five products and green as the
752 background color for boxes.
758 openerp.oepetstore = function(instance) {
759 var _t = instance.web._t,
760 _lt = instance.web._lt;
761 var QWeb = instance.web.qweb;
763 instance.oepetstore = {};
765 instance.oepetstore.HomePage = instance.web.Widget.extend({
767 var products = new instance.oepetstore.ProductsWidget(this, ["cpu", "mouse", "keyboard", "graphic card", "screen"], "#00FF00");
768 products.appendTo(this.$el);
772 instance.oepetstore.ProductsWidget = instance.web.Widget.extend({
773 template: "ProductsWidget",
774 init: function(parent, products, color) {
776 this.products = products;
781 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
786 <?xml version="1.0" encoding="UTF-8"?>
788 <templates xml:space="preserve">
789 <t t-name="ProductsWidget">
791 <t t-foreach="widget.products" t-as="product">
792 <span class="oe_products_item" t-att-style="'background-color: ' + widget.color + ';'"><t t-esc="product"/></span><br/>
801 display: inline-block;
804 border: 1px solid black;
808 .. image:: web/qweb.*
812 Widget Events and Properties
813 ----------------------------
815 Widgets still have more helper to learn. One of the more complex (and useful)
816 one is the event system. Events are also closely related to the widget
822 Widgets are able to fire events in a similar way most components in existing
823 graphical user interfaces libraries (Qt, GTK, Swing,...) handle
826 instance.oepetstore.ConfirmWidget = instance.web.Widget.extend({
829 this.$el.append("<div>Are you sure you want to perform this action?</div>" +
830 "<button class='ok_button'>Ok</button>" +
831 "<button class='cancel_button'>Cancel</button>");
832 this.$el.find("button.ok_button").click(function() {
833 self.trigger("user_choose", true);
835 this.$el.find("button.cancel_button").click(function() {
836 self.trigger("user_choose", false);
841 instance.oepetstore.HomePage = instance.web.Widget.extend({
843 var widget = new instance.oepetstore.ConfirmWidget(this);
844 widget.on("user_choose", this, this.user_choose);
845 widget.appendTo(this.$el);
847 user_choose: function(confirm) {
849 console.log("The user agreed to continue");
851 console.log("The user refused to continue");
856 First, we will explain what this example is supposed to do. We create a
857 generic widget to ask the user if he really wants to do an action that could
858 have important consequences (a type widget heavily used in Windows). To do so,
859 we put two buttons in the widget. Then we bind jQuery events to know when the
860 user click these buttons.
864 It could be hard to understand this particular line::
868 Remember, in JavaScript the variable ``this`` is a variable that is passed
869 implicitly to all functions. It allows us to know which is the object if
870 function is used like a method. Each declared function has its own
871 ``this``. So, when we declare a function inside a function, that new
872 function will have its own ``this`` that could be different from the
873 ``this`` of the parent function. If we want to remember the original
874 object the simplest method is to store a reference in a variable. By
875 convention in Odoo we very often name that variable ``self`` because it's
876 the equivalent of ``this`` in Python.
878 Since our widget is supposed to be generic, it should not perform any precise
879 action by itself. So, we simply make it trigger and event named
880 ``user_choose`` by using the :func:`~openerp.web.Widget.trigger` method.
882 :func:`~openerp.web.Widget.trigger` takes as first argument the name of the
883 event to trigger. Then it can takes any number of additional arguments. These
884 arguments will be passed to all the event listeners.
886 Then we modify the ``HomePage`` widget to instantiate a ``ConfirmWidget`` and
887 listen to its ``user_choose`` event by calling the
888 :func:`~openerp.web.Widget.on` method.
890 :func:`~openerp.web.Widget.on` allows to bind a function to be called when the
891 event identified by event_name is ``triggered``. The ``func`` argument is the
892 function to call and ``object`` is the object to which that function is
893 related if it is a method. The binded function will be called with the
894 additional arguments of :func:`~openerp.web.Widget.trigger` if it has
899 widget.on("my_event", this, this.my_event_triggered);
900 widget.trigger("my_event", 1, 2, 3);
902 my_event_triggered: function(a, b, c) {
903 console.log(a, b, c);
904 // will print "1 2 3"
910 Properties are very similar to normal object attributes. They allow to set
911 data on an object but with an additional feature: it triggers events when a
912 property's value has changed::
916 this.widget.on("change:name", this, this.name_changed);
917 this.widget.set("name", "Nicolas");
919 name_changed: function() {
920 console.log("The new value of the property 'name' is", this.widget.get("name"));
923 :func:`~openerp.web.Widget.set` allows to set the value of property. If the
924 value changed (or it didn't had a value previously) the object will trigger a
925 ``change:xxx`` where ``xxx`` is the name of the property.
927 :func:`~openerp.web.Widget.get` allows to retrieve the value of a property.
932 .. exercise:: Widget Properties and Events
934 Create a widget ``ColorInputWidget`` that will display 3 ``<input
935 type="text">``. Each of these ``<input>`` is dedicated to type a
936 hexadecimal number from 00 to FF. When any of these ``<input>`` is
937 modified by the user the widget must query the content of the three
938 ``<input>``, concatenate their values to have a complete CSS color code
939 (ie: ``#00FF00``) and put the result in a property named ``color``. Please
940 note the jQuery ``change()`` event that you can bind on any HTML
941 ``<input>`` element and the ``val()`` method that can query the current
942 value of that ``<input>`` could be useful to you for this exercise.
944 Then, modify the ``HomePage`` widget to instantiate ``ColorInputWidget``
945 and display it. The ``HomePage`` widget should also display an empty
946 rectangle. That rectangle must always, at any moment, have the same
947 background color than the color in the ``color`` property of the
948 ``ColorInputWidget`` instance.
950 Use QWeb to generate all HTML.
956 openerp.oepetstore = function(instance) {
957 var _t = instance.web._t,
958 _lt = instance.web._lt;
959 var QWeb = instance.web.qweb;
961 instance.oepetstore = {};
963 instance.oepetstore.ColorInputWidget = instance.web.Widget.extend({
964 template: "ColorInputWidget",
967 this.$el.find("input").change(function() {
968 self.input_changed();
970 self.input_changed();
972 input_changed: function() {
974 color += this.$el.find(".oe_color_red").val();
975 color += this.$el.find(".oe_color_green").val();
976 color += this.$el.find(".oe_color_blue").val();
977 this.set("color", color);
981 instance.oepetstore.HomePage = instance.web.Widget.extend({
982 template: "HomePage",
984 this.colorInput = new instance.oepetstore.ColorInputWidget(this);
985 this.colorInput.on("change:color", this, this.color_changed);
986 this.colorInput.appendTo(this.$el);
988 color_changed: function() {
989 this.$el.find(".oe_color_div").css("background-color", this.colorInput.get("color"));
993 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
998 <?xml version="1.0" encoding="UTF-8"?>
1000 <templates xml:space="preserve">
1001 <t t-name="ColorInputWidget">
1003 Red: <input type="text" class="oe_color_red" value="00"></input><br />
1004 Green: <input type="text" class="oe_color_green" value="00"></input><br />
1005 Blue: <input type="text" class="oe_color_blue" value="00"></input><br />
1008 <t t-name="HomePage">
1010 <div class="oe_color_div"></div>
1025 jQuery's ``css()`` method allows setting a css property.
1030 We've seen the basics of the :class:`~openerp.web.Widget` class, QWeb and the
1031 events/properties system. There are still some more useful methods proposed by
1034 ``Widget``'s jQuery Selector
1035 %%%%%%%%%%%%%%%%%%%%%%%%%%%%
1037 It is very common to need to select a precise element inside a widget. In the
1038 previous part of this guide we've seen a lot of uses of the ``find()`` method
1041 this.$el.find("input.my_input")...
1043 :class:`~openerp.web.Widget` provides a shorter syntax that does the same
1044 thing with the :func:`~openerp.web.Widget.$` method::
1046 instance.oepetstore.MyWidget = instance.web.Widget.extend({
1048 this.$("input.my_input")...
1054 We strongly advise you against using directly the global jQuery function
1055 ``$()`` like we did in the previous chapter were we explained the jQuery
1056 library and jQuery selectors. That type of global selection is sufficient
1057 for simple applications but is not a good idea in real, big web
1058 applications. The reason is simple: when you create a new type of widget
1059 you never know how many times it will be instantiated. Since the ``$()``
1060 global function operates in *the whole HTML displayed in the browser*, if
1061 you instantiate a widget 2 times and use that function you will
1062 incorrectly select the content of another instance of your widget. That's
1063 why you must restrict the jQuery selections to HTML which is located
1064 *inside* your widget most of the time.
1066 Applying the same logic, you can also guess it is a very bad idea to try
1067 to use HTML ids in any widget. If the widget is instantiated 2 times you
1068 will have 2 different HTML element in the whole application that have the
1070 id. And that is an error by itself. So you should stick to CSS classes to mark your HTML elements in all cases.
1072 Easier DOM Events Binding
1073 %%%%%%%%%%%%%%%%%%%%%%%%%
1075 In the previous part, we had to bind a lot of HTML element events like
1076 ``click()`` or ``change()``. Now that we have the ``$()`` method to simplify
1077 code a little, let's see how it would look like::
1079 instance.oepetstore.MyWidget = instance.web.Widget.extend({
1082 this.$(".my_button").click(function() {
1083 self.button_clicked();
1086 button_clicked: function() {
1091 It's still a bit long to type. That's why there is an even more simple syntax
1094 instance.oepetstore.MyWidget = instance.web.Widget.extend({
1096 "click .my_button": "button_clicked",
1098 button_clicked: function() {
1105 It's important to differentiate the jQuery events that are triggered on
1106 DOM elements and events of the widgets. The ``event`` class attribute *is
1107 a helper to help binding jQuery events*, it has nothing to do with the
1108 widget events that can be binded using the ``on()`` method.
1110 The ``event`` class attribute is a dictionary that allows to define jQuery
1111 events with a shorter syntax.
1113 The key is a string with 2 different parts separated with a space. The first
1114 part is the name of the event, the second one is the jQuery selector. So the
1115 key ``click .my_button`` will bind the event ``click`` on the elements
1116 matching the selector ``my_button``.
1118 The value is a string with the name of the method to call on the current
1121 Development Guidelines
1122 %%%%%%%%%%%%%%%%%%%%%%
1124 As explained in the prerequisites to read this guide, you should already know
1125 HTML and CSS. But developing web applications in JavaScript or developing web
1126 modules for Odoo require to be more strict than you will usually be when
1127 simply creating static web pages with CSS to style them. So these guidelines
1128 should be followed if you want to have manageable projects and avoid bugs or
1131 * Identifiers (``id`` attribute) should be avoided. In generic applications
1132 and modules, ``id`` limits the re-usability of components and tends to make
1133 code more brittle. Just about all the time, they can be replaced with
1134 nothing, with classes or with keeping a reference to a DOM node or a jQuery
1139 If it is absolutely necessary to have an ``id`` (because a third-party
1140 library requires one and can't take a DOM element), it should be
1141 generated with ``_.uniqueId()``.
1143 * Avoid predictable/common CSS class names. Class names such as "content" or
1144 "navigation" might match the desired meaning/semantics, but it is likely an
1145 other developer will have the same need, creating a naming conflict and
1146 unintended behavior. Generic class names should be prefixed with e.g. the
1147 name of the component they belong to (creating "informal" namespaces, much
1148 as in C or Objective-C).
1150 * Global selectors should be avoided. Because a component may be used several
1151 times in a single page (an example in Odoo is dashboards), queries should be
1152 restricted to a given component's scope. Unfiltered selections such as
1153 ``$(selector)`` or ``document.querySelectorAll(selector)`` will generally
1154 lead to unintended or incorrect behavior. Odoo Web's
1155 :class:`~openerp.web.Widget` has an attribute providing its DOM root
1156 (:attr:`~openerp.web.Widget.$el`), and a shortcut to select nodes directly
1157 (:func:`~openerp.web.Widget.$`).
1159 * More generally, never assume your components own or controls anything beyond
1160 its own personal :attr:`~openerp.web.Widget.$el`
1162 * html templating/rendering should use QWeb unless absolutely trivial.
1164 * All interactive components (components displaying information to the screen
1165 or intercepting DOM events) must inherit from Widget and correctly implement
1166 and use its API and life cycle.
1168 Modify Existent Widgets and Classes
1169 -----------------------------------
1171 The class system of the Odoo web framework allows direct modification of
1172 existing classes using the :func:`~openerp.web.Widget.include` method of a
1175 var TestClass = instance.web.Class.extend({
1176 testMethod: function() {
1182 testMethod: function() {
1183 return this._super() + " world";
1187 console.log(new TestClass().testMethod());
1188 // will print "hello world"
1190 This system is similar to the inheritance mechanism, except it will directly
1191 modify the class. You can call ``this._super()`` to call the original
1192 implementation of the methods you are redefining. If the class already had
1193 sub-classes, all calls to ``this._super()`` in sub-classes will call the new
1194 implementations defined in the call to ``include()``. This will also work if
1195 some instances of the class (or of any of its sub-classes) were created prior
1196 to the call to :func:`~openerp.web.Widget.include`.
1200 Please note that, even if :func:`~openerp.web.Widget.include` can be a
1201 powerful tool, it's not considered a very good programming practice
1202 because it can easily create problems if used in a wrong way. So you
1203 should use it to modify the behavior of an existing component only when
1204 there are no other options, and try to limit its usages to the strict
1210 The process to translate text in Python and JavaScript code is very
1211 similar. You could have noticed these lines at the beginning of the
1212 ``petstore.js`` file:
1214 var _t = instance.web._t,
1215 _lt = instance.web._lt;
1217 These lines are simply used to import the translation functions in the current
1218 JavaScript module. The correct to use them is this one::
1220 this.$el.text(_t("Hello dear user!"));
1222 In Odoo, translations files are automatically generated by scanning the source
1223 code. All piece of code that calls a certain function are detected and their
1224 content is added to a translation file that will then be sent to the
1225 translators. In Python, the function is ``_()``. In JavaScript the function is
1226 :func:`~openerp.web._t` (and also :func:`~openerp.web._lt`).
1228 If the source file as never been scanned and the translation files does not
1229 contain any translation for the text given to ``_t()`` it will return the text
1230 as-is. If there is a translation it will return it.
1232 :func:`~openerp.web._lt` does almost the exact same thing but is a little bit
1233 more complicated. It does not return a text but returns a function that will
1234 return the text. It is reserved for very special cases::
1236 var text_func = _lt("Hello dear user!");
1237 this.$el.text(text_func());
1239 To have more information about Odoo's translations, please take a look at the
1240 reference documentation: https://doc.openerp.com/contribute/translations/ .
1242 Communication with the Odoo Server
1243 -------------------------------------
1245 Now you should know everything you need to display any type of graphical user
1246 interface with your Odoo modules. Still, Odoo is a database-centric
1247 application so it's still not very useful if you can't query data from the
1250 As a reminder, in Odoo you are not supposed to directly query data from the
1251 PostgreSQL database, you will always use the build-in ORM (Object-Relational
1252 Mapping) and more precisely the Odoo *models*.
1257 In the previous chapter we explained how to send HTTP requests to the web
1258 server using the ``$.ajax()`` method and the JSON format. It is useful to know
1259 how to make a JavaScript application communicate with its web server using
1260 these tools, but it's still a little bit low-level to be used in a complex
1261 application like Odoo.
1263 When the web client contacts the Odoo server it has to pass additional data
1264 like the necessary information to authenticate the current user. There is also
1265 some more complexity due to Odoo models that need a higher-level communication
1266 protocol to be used.
1268 This is why you will not use directly ``$.ajax()`` to communicate with the
1269 server. The web client framework provides classes to abstract that protocol.
1271 To demonstrate this, the file ``petstore.py`` already contains a small model
1272 with a sample method:
1274 .. code-block:: python
1276 class message_of_the_day(osv.osv):
1277 _name = "message_of_the_day"
1279 def my_method(self, cr, uid, context=None):
1280 return {"hello": "world"}
1283 'message': fields.text(string="Message"),
1284 'color': fields.char(string="Color", size=20),
1287 If you know Odoo models that code should be familiar to you. This model
1288 declares a table named ``message_of_the_day`` with two fields. It also has a
1289 method ``my_method()`` that doesn't do much except return a dictionary.
1291 Here is a sample widget that calls ``my_method()`` and displays the result::
1293 instance.oepetstore.HomePage = instance.web.Widget.extend({
1296 var model = new instance.web.Model("message_of_the_day");
1297 model.call("my_method", [], {context: new instance.web.CompoundContext()}).then(function(result) {
1298 self.$el.append("<div>Hello " + result["hello"] + "</div>");
1299 // will show "Hello world" to the user
1304 The class used to contact Odoo models is ``instance.web.Model``. When you
1305 instantiate it, you must give as first argument to its constructor the name of
1306 the model you want to contact in Odoo. (Here it is ``message_of_the_day``, the
1307 model created for this example, but it could be any other model like
1310 :func:`~openerp.web.Model.call` is the method of :class:`~openerp.web.Model`
1311 used to call any method of an Odoo server-side model. Here are its arguments:
1313 * ``name`` is the name of the method to call on the model. Here it is the
1314 method named ``my_method``.
1315 * ``args`` is a list of positional arguments to give to the method. The sample
1316 ``my_method()`` method does not contain any particular argument we want to
1317 give to it, so here is another example:
1319 .. code-block:: python
1321 def my_method2(self, cr, uid, a, b, c, context=None): ...
1323 .. code-block:: javascript
1325 model.call("my_method", [1, 2, 3], ...
1326 // with this a=1, b=2 and c=3
1328 * ``kwargs`` is a list of named arguments to give to the method. In the
1329 example, we have one named argument which is a bit special:
1330 ``context``. It's given a value that may seem very strange right now: ``new
1331 instance.web.CompoundContext()``. The meaning of that argument will be
1332 explained later. Right now you should just know the ``kwargs`` argument
1333 allows to give arguments to the Python method by name instead of
1336 .. code-block:: python
1338 def my_method2(self, cr, uid, a, b, c, context=None): ...
1340 .. code-block:: javascript
1342 model.call("my_method", [], {a: 1, b: 2, c: 3, ...
1343 // with this a=1, b=2 and c=3
1347 If you take a look at the ``my_method()``'s declaration in Python, you can
1348 see it has two arguments named ``cr`` and ``uid``:
1350 .. code-block:: python
1352 def my_method(self, cr, uid, context=None):
1354 You could have noticed we do not give theses arguments to the server when
1355 we call that method from JavaScript. That is because theses arguments that
1356 have to be declared in all models' methods are never sent from the Odoo
1357 client. These arguments are added implicitly by the Odoo server. The
1358 first one is an object called the *cursor* that allows communication with
1359 the database. The second one is the id of the currently logged in user.
1361 :func:`~openerp.web.Widget.call` returns a deferred resolved with the value
1362 returned by the model's method as first argument. If you don't know what
1363 deferreds are, take a look at the previous chapter (the part about HTTP
1364 requests in jQuery).
1369 In the previous part, we avoided to explain the strange ``context`` argument
1370 in the call to our model's method:
1372 .. code-block:: javascript
1374 model.call("my_method", [], {context: new instance.web.CompoundContext()})
1376 In Odoo, models' methods should always have an argument named ``context``:
1378 .. code-block:: python
1380 def my_method(self, cr, uid, context=None): ...
1382 The context is like a "magic" argument that the web client will always give to
1383 the server when calling a method. The context is a dictionary containing
1384 multiple keys. One of the most important key is the language of the user, used
1385 by the server to translate all the messages of the application. Another one is
1386 the time zone of the user, used to compute correctly dates and times if Odoo
1387 is used by people in different countries.
1389 The ``argument`` is necessary in all methods, because if we forget it bad
1390 things could happen (like the application not being translated
1391 correctly). That's why, when you call a model's method, you should always give
1392 it to that argument. The solution to achieve that is to use
1393 :class:`openerp.web.CompoundContext`.
1395 :class:`~openerp.web.CompoundContext` is a class used to pass the user's
1396 context (with language, time zone, etc...) to the server as well as adding new
1397 keys to the context (some models' methods use arbitrary keys added to the
1398 context). It is created by giving to its constructor any number of
1399 dictionaries or other :class:`~openerp.web.CompoundContext` instances. It will
1400 merge all those contexts before sending them to the server.
1402 .. code-block:: javascript
1404 model.call("my_method", [], {context: new instance.web.CompoundContext({'new_key': 'key_value'})})
1406 .. code-block:: python
1408 def display_context(self, cr, uid, context=None):
1410 // will print: {'lang': 'en_US', 'new_key': 'key_value', 'tz': 'Europe/Brussels', 'uid': 1}
1412 You can see the dictionary in the argument ``context`` contains some keys that
1413 are related to the configuration of the current user in Odoo plus the
1414 ``new_key`` key that was added when instantiating
1415 :class:`~openerp.web.CompoundContext`.
1417 To resume, you should always add an instance of
1418 :class:`~openerp.web.CompoundContext` in all calls to a model's method.
1423 If you know Odoo module development, you should already know everything
1424 necessary to communicate with models and make them do what you want. But there
1425 is still a small helper that could be useful to you :
1426 :func:`~openerp.web.Model.query`.
1428 :func:`~openerp.web.Model.query` is a shortcut for the usual combination of
1429 :py:meth:`~openerp.models.Model.search` and
1430 ::py:meth:`~openerp.models.Model.read` methods in Odoo models. It allows to
1431 :search records and get their data with a shorter syntax. Example::
1433 model.query(['name', 'login', 'user_email', 'signature'])
1434 .filter([['active', '=', true], ['company_id', '=', main_company]])
1436 .all().then(function (users) {
1437 // do work with users records
1440 :func:`~openerp.web.Model.query` takes as argument a list of fields to query
1441 in the model. It returns an instance of the :class:`openerp.web.Query` class.
1443 :class:`~openerp.web.Query` is a class representing the query you are trying
1444 to construct before sending it to the server. It has multiple methods you can
1445 call to customize the query. All these methods will return the current
1446 instance of :class:`~openerp.web.Query`:
1448 * :func:`~openerp.web.Query.filter` allows to specify an Odoo *domain*. As a
1449 reminder, a domain in Odoo is a list of conditions, each condition is a list
1451 * :func:`~openerp.web.Query.limit` sets a limit to the number of records
1454 When you have customized you query, you can call the
1455 :func:`~openerp.web.Query.all` method. It will performs the real query to the
1456 server and return a deferred resolved with the result. The result is the same
1457 thing return by the model's method :py:meth:`~openerp.models.Model.read` (a
1458 list of dictionaries containing the asked fields).
1463 .. exercise:: Message of the Day
1465 Create a widget ``MessageOfTheDay`` that will display the message
1466 contained in the last record of the ``message_of_the_day``. The widget
1467 should query the message as soon as it is inserted in the DOM and display
1468 the message to the user. Display that widget on the home page of the Odoo
1473 .. code-block:: javascript
1475 openerp.oepetstore = function(instance) {
1476 var _t = instance.web._t,
1477 _lt = instance.web._lt;
1478 var QWeb = instance.web.qweb;
1480 instance.oepetstore = {};
1482 instance.oepetstore.HomePage = instance.web.Widget.extend({
1483 template: "HomePage",
1485 var motd = new instance.oepetstore.MessageOfTheDay(this);
1486 motd.appendTo(this.$el);
1490 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1492 instance.oepetstore.MessageOfTheDay = instance.web.Widget.extend({
1493 template: "MessageofTheDay",
1495 this._super.apply(this, arguments);
1499 new instance.web.Model("message_of_the_day").query(["message"]).first().then(function(result) {
1500 self.$(".oe_mywidget_message_of_the_day").text(result.message);
1509 <?xml version="1.0" encoding="UTF-8"?>
1511 <templates xml:space="preserve">
1512 <t t-name="HomePage">
1513 <div class="oe_petstore_homepage">
1516 <t t-name="MessageofTheDay">
1517 <div class="oe_petstore_motd">
1518 <p class="oe_mywidget_message_of_the_day"></p>
1529 background-color: #F0EEEE;
1532 .. exercise:: Pet Toys List
1534 Create a widget ``PetToysList`` that will display 5 toys on the home page
1535 with their names and their images.
1537 In this Odoo addon, the pet toys are not stored in a new table like for
1538 the message of the day. They are in the table ``product.product``. If you
1539 click on the menu item :menuselection:`Pet Store --> Pet Store --> Pet
1540 Toys` you will be able to see them. Pet toys are identified by the
1541 category named ``Pet Toys``. You could need to document yourself on the
1542 model ``product.product`` to be able to create a domain to select pet toys
1543 and not all the products.
1545 To display the images of the pet toys, you should know that images in Odoo
1546 can be queried from the database like any other fields, but you will
1547 obtain a string containing Base64-encoded binary. There is a little trick
1548 to display images in Base64 format in HTML:
1550 .. code-block:: html
1552 <img class="oe_kanban_image" src="data:image/png;base64,${replace this by base64}"></image>
1554 The ``PetToysList`` widget should be displayed on the home page on the
1555 right of the ``MessageOfTheDay`` widget. You will need to make some layout
1556 with CSS to achieve this.
1560 .. code-block:: javascript
1562 openerp.oepetstore = function(instance) {
1563 var _t = instance.web._t,
1564 _lt = instance.web._lt;
1565 var QWeb = instance.web.qweb;
1567 instance.oepetstore = {};
1569 instance.oepetstore.HomePage = instance.web.Widget.extend({
1570 template: "HomePage",
1572 var pettoys = new instance.oepetstore.PetToysList(this);
1573 pettoys.appendTo(this.$(".oe_petstore_homepage_left"));
1574 var motd = new instance.oepetstore.MessageOfTheDay(this);
1575 motd.appendTo(this.$(".oe_petstore_homepage_right"));
1579 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1581 instance.oepetstore.MessageOfTheDay = instance.web.Widget.extend({
1582 template: "MessageofTheDay",
1584 this._super.apply(this, arguments);
1588 new instance.web.Model("message_of_the_day").query(["message"]).first().then(function(result) {
1589 self.$(".oe_mywidget_message_of_the_day").text(result.message);
1594 instance.oepetstore.PetToysList = instance.web.Widget.extend({
1595 template: "PetToysList",
1598 new instance.web.Model("product.product").query(["name", "image"])
1599 .filter([["categ_id.name", "=", "Pet Toys"]]).limit(5).all().then(function(result) {
1600 _.each(result, function(item) {
1601 var $item = $(QWeb.render("PetToy", {item: item}));
1602 self.$el.append($item);
1612 <?xml version="1.0" encoding="UTF-8"?>
1614 <templates xml:space="preserve">
1615 <t t-name="HomePage">
1616 <div class="oe_petstore_homepage">
1617 <div class="oe_petstore_homepage_left"></div>
1618 <div class="oe_petstore_homepage_right"></div>
1621 <t t-name="MessageofTheDay">
1622 <div class="oe_petstore_motd">
1623 <p class="oe_mywidget_message_of_the_day"></p>
1626 <t t-name="PetToysList">
1627 <div class="oe_petstore_pettoyslist">
1631 <div class="oe_petstore_pettoy">
1632 <p><t t-esc="item.name"/></p>
1633 <p><img t-att-src="'data:image/jpg;base64,'+item.image"/></p>
1640 .oe_petstore_homepage {
1644 .oe_petstore_homepage_left {
1645 display: table-cell;
1649 .oe_petstore_homepage_right {
1650 display: table-cell;
1658 background-color: #F0EEEE;
1661 .oe_petstore_pettoyslist {
1665 .oe_petstore_pettoy {
1669 background-color: #F0EEEE;
1673 Existing web components
1674 -----------------------
1676 In the previous part, we explained the Odoo web framework, a development
1677 framework to create and architecture graphical JavaScript applications. The
1678 current part is dedicated to the existing components of the Odoo web client
1679 and most notably those containing entry points for developers to create new
1680 widgets that will be inserted inside existing views or components.
1685 To display a view or show a popup, as example when you click on a menu button,
1686 Odoo use the concept of actions. Actions are pieces of information explaining
1687 what the web client should do. They can be loaded from the database or created
1688 on-the-fly. The component handling actions in the web client is the *Action
1691 Using the Action Manager
1692 ''''''''''''''''''''''''
1694 A way to launch an action is to use a menu element targeting an action
1695 registered in the database. As a reminder, here is how is defined a typical
1696 action and its associated menu item:
1700 <record model="ir.actions.act_window" id="message_of_the_day_action">
1701 <field name="name">Message of the day</field>
1702 <field name="res_model">message_of_the_day</field>
1703 <field name="view_type">form</field>
1704 <field name="view_mode">tree,form</field>
1707 <menuitem id="message_day" name="Message of the day" parent="petstore_menu"
1708 action="message_of_the_day_action"/>
1710 It is also possible to ask the Odoo client to load an action from a JavaScript
1711 code. To do so you have to create a dictionary explaining the action and then
1712 to ask the action manager to re-dispatch the web client to the new action. To
1713 send a message to the action manager, :class:`~openerp.web.Widget` has a
1714 shortcut that will automatically find the current action manager and execute
1715 the action. Here is an example call to that method::
1717 instance.web.TestWidget = instance.web.Widget.extend({
1718 dispatch_to_new_action: function() {
1720 type: 'ir.actions.act_window',
1721 res_model: "product.product",
1723 views: [[false, 'form']],
1730 The method to call to ask the action manager to execute a new action is
1731 :func:`~openerp.web.Widget.do_action`. It receives as argument a dictionary
1732 defining the properties of the action. Here is a description of the most usual
1733 properties (not all of them may be used by all type of actions):
1735 * ``type``: The type of the action, which means the name of the model in which
1736 the action is stored. As example, use ``ir.actions.act_window`` to show
1737 views and ``ir.actions.client`` for client actions.
1738 * ``res_model``: For ``act_window`` actions, it is the model used by the
1740 * ``res_id``: The ``id`` of the record to display.
1741 * ``views``: For ``act_window`` actions, it is a list of the views to
1742 display. This argument must be a list of tuples with two components. The
1743 first one must be the identifier of the view (or ``false`` if you just want
1744 to use the default view defined for the model). The second one must be the
1746 * ``target``: If the value is ``current``, the action will be opened in the
1747 main content part of the web client. The current action will be destroyed
1748 before loading the new one. If it is ``new``, the action will appear in a
1749 popup and the current action will not be destroyed.
1750 * ``context``: The context to use.
1752 .. exercise:: Jump to Product
1754 Modify the ``PetToysList`` component developed in the previous part to
1755 jump to a form view displaying the shown item when we click on the item in
1760 .. code-block:: javascript
1762 instance.oepetstore.PetToysList = instance.web.Widget.extend({
1763 template: "PetToysList",
1766 new instance.web.Model("product.product").query(["name", "image"])
1767 .filter([["categ_id.name", "=", "Pet Toys"]]).limit(5).all().then(function(result) {
1768 _.each(result, function(item) {
1769 var $item = $(QWeb.render("PetToy", {item: item}));
1770 self.$el.append($item);
1771 $item.click(function() {
1772 self.item_clicked(item);
1777 item_clicked: function(item) {
1779 type: 'ir.actions.act_window',
1780 res_model: "product.product",
1782 views: [[false, 'form']],
1792 In the module installed during the previous part of this guide, we defined a
1793 simple widget that was displayed when we clicked on a menu element. This is
1794 because this widget was registered as a *client action*. Client actions are a
1795 type of action that are completely defined by JavaScript code. Here is a
1796 reminder of the way we defined this client action::
1798 instance.oepetstore.HomePage = instance.web.Widget.extend({
1800 console.log("pet store home page loaded");
1804 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1806 ``instance.web.client_actions`` is an instance of the
1807 :class:`~openerp.web.Registry` class. Registries are not very different to
1808 simple dictionaries, except they assign strings to class names. Adding the
1809 ``petstore.homepage`` key to this registry simply tells the web client "If
1810 someone asks you to open a client action with key ``petstore.homepage``,
1811 instantiate the ``instance.oepetstore.HomePage`` class and show it to the
1814 Here is how the menu element to show this client action was defined:
1818 <record id="action_home_page" model="ir.actions.client">
1819 <field name="tag">petstore.homepage</field>
1822 <menuitem id="home_page_petstore_menu" name="Home Page" parent="petstore_menu"
1823 action="action_home_page"/>
1825 Client actions do not need a lot of information except their type, which is
1826 stored in the ``tag`` field.
1828 When the web client wants to display a client action, it will simply show it
1829 in the main content block of the web client. This is completely sufficient to
1830 allow the widget to display anything and so create a completely new feature
1833 Architecture of the Views
1834 %%%%%%%%%%%%%%%%%%%%%%%%%
1836 Most of the complexity of the web client resides in views. They are the basic
1837 tools to display the data in the database. The part will explain the views
1838 and how those are displayed in the web client.
1843 Previously we already explained the purpose of the *Action Manager*. It is a
1844 component, whose class is ``ActionManager``, that will handle the Odoo actions
1845 (notably the actions associated with menu buttons).
1847 When an ``ActionManager`` instance receive an action with type
1848 ``ir.actions.act_window``, it knows it has to show one or more views
1849 associated with a precise model. To do so, it creates a *View Manager* that
1850 will create one or multiple *Views*. See this diagram:
1852 .. image:: web/viewarchitecture.*
1856 The ``ViewManager`` instance will instantiate each view class corresponding to
1857 the views indicated in the ``ir.actions.act_window`` action. As example, the
1858 class corresponding to the view type ``form`` is ``FormView``. Each view class
1859 inherits the ``View`` abstract class.
1864 All the typical type of views in Odoo (all those you can switch to using the
1865 small buttons under the search input text) are represented by a class
1866 extending the ``View`` abstract class. Note the *Search View* (the search
1867 input text on the top right of the screen that typically appear in kanban and
1868 list views) is also considered a type of view even if it doesn't work like the
1869 others (you can not "switch to" the search view and it doesn't take the full
1872 A view has the responsibility to load its XML view description from the server
1873 and display it. Views are also given an instance of the ``DataSet``
1874 class. That class contains a list of identifiers corresponding to records that
1875 the view should display. It is filled by the search view and the current view
1876 is supposed to display the result of each search after it was performed by the
1879 The Form View Fields
1880 %%%%%%%%%%%%%%%%%%%%
1882 A typical need in the web client is to extend the form view to display more
1883 specific widgets. One of the possibilities to do this is to define a new type
1886 A field, in the form view, is a type of widget designed to display and edit
1887 the content of *one (and only one) field* in a single record displayed by the
1888 form view. All data types available in models have a default implementation to
1889 display and edit them in the form view. As example, the ``FieldChar`` class
1890 allows to edit the ``char`` data type.
1892 Other field classes simply provide an alternative widget to represent an
1893 existing data type. A good example of this is the ``FieldEmail`` class. There
1894 is no ``email`` type in the models of Odoo. That class is designed to display
1895 a ``char`` field assuming it contains an email (it will show a clickable link
1896 to directly send a mail to the person and will also check the validity of the
1899 Also note there is nothing that disallow a field class to work with more than
1900 one data type. As example, the ``FieldSelection`` class works with both
1901 ``selection`` and ``many2one`` field types.
1903 As a reminder, to indicate a precise field type in a form view XML
1904 description, you just have to specify the ``widget`` attribute:
1908 <field name="contact_mail" widget="email"/>
1910 It is also a good thing to notice that the form view field classes are also
1911 used in the editable list views. So, by defining a new field class, it make
1912 this new widget available in both views.
1914 Another type of extension mechanism for the form view is the *Form Widget*,
1915 which has fewer restrictions than the fields (even though it can be more
1916 complicated to implement). Form widgets will be explained later in this guide.
1918 Fields are instantiated by the form view after it has read its XML description
1919 and constructed the corresponding HTML representing that description. After
1920 that, the form view will communicate with the field objects using some
1921 methods. Theses methods are defined by the ``FieldInterface``
1922 interface. Almost all fields inherit the ``AbstractField`` abstract
1923 class. That class defines some default mechanisms that need to be implemented
1926 Here are some of the responsibilities of a field class:
1928 * The field class must display and allow the user to edit the value of the field.
1929 * It must correctly implement the 3 field attributes available in all fields
1930 of Odoo. The ``AbstractField`` class already implements an algorithm that
1931 dynamically calculates the value of these attributes (they can change at any
1932 moment because their value change according to the value of other
1933 fields). Their values are stored in *Widget Properties* (the widget
1934 properties were explained earlier in this guide). It is the responsibility
1935 of each field class to check these widget properties and dynamically adapt
1936 depending of their values. Here is a description of each of these
1939 * ``required``: The field must have a value before saving. If ``required``
1940 is ``true`` and the field doesn't have a value, the method
1941 ``is_valid()`` of the field must return ``false``.
1942 * ``invisible``: When this is ``true``, the field must be invisible. The
1943 ``AbstractField`` class already has a basic implementation of this
1944 behavior that fits most fields.
1945 * ``readonly``: When ``true``, the field must not be editable by the
1946 user. Most fields in Odoo have a completely different behavior depending
1947 on the value of ``readonly``. As example, the ``FieldChar`` displays an
1948 HTML ``<input>`` when it is editable and simply displays the text when
1949 it is read-only. This also means it has much more code it would need to
1950 implement only one behavior, but this is necessary to ensure a good user
1953 * Fields have two methods, ``set_value()`` and ``get_value()``, which are
1954 called by the form view to give it the value to display and get back the new
1955 value entered by the user. These methods must be able to handle the value as
1956 given by the Odoo server when a ``read()`` is performed on a model and give
1957 back a valid value for a ``write()``. Remember that the JavaScript/Python
1958 data types used to represent the values given by ``read()`` and given to
1959 ``write()`` is not necessarily the same in Odoo. As example, when you read a
1960 many2one, it is always a tuple whose first value is the id of the pointed
1961 record and the second one is the name get (ie: ``(15, "Agrolait")``). But
1962 when you write a many2one it must be a single integer, not a tuple
1963 anymore. ``AbstractField`` has a default implementation of these methods
1964 that works well for simple data type and set a widget property named
1967 Please note that, to better understand how to implement fields, you are
1968 strongly encouraged to look at the definition of the ``FieldInterface``
1969 interface and the ``AbstractField`` class directly in the code of the Odoo web
1972 Creating a New Type of Field
1973 ''''''''''''''''''''''''''''
1975 In this part we will explain how to create a new type of field. The example
1976 here will be to re-implement the ``FieldChar`` class and explain progressively
1979 Simple Read-Only Field
1980 """"""""""""""""""""""
1982 Here is a first implementation that will only be able to display a text. The
1983 user will not be able to modify the content of the field.
1985 .. code-block:: javascript
1987 instance.oepetstore.FieldChar2 = instance.web.form.AbstractField.extend({
1989 this._super.apply(this, arguments);
1990 this.set("value", "");
1992 render_value: function() {
1993 this.$el.text(this.get("value"));
1997 instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');
1999 In this example, we declare a class named ``FieldChar2`` inheriting from
2000 ``AbstractField``. We also register this class in the registry
2001 ``instance.web.form.widgets`` under the key ``char2``. That will allow us to
2002 use this new field in any form view by specifying ``widget="char2"`` in the
2003 ``<field/>`` tag in the XML declaration of the view.
2005 In this example, we define a single method: ``render_value()``. All it does is
2006 display the widget property ``value``. Those are two tools defined by the
2007 ``AbstractField`` class. As explained before, the form view will call the
2008 method ``set_value()`` of the field to set the value to display. This method
2009 already has a default implementation in ``AbstractField`` which simply sets
2010 the widget property ``value``. ``AbstractField`` also watch the
2011 ``change:value`` event on itself and calls the ``render_value()`` when it
2012 occurs. So, ``render_value()`` is a convenience method to implement in child
2013 classes to perform some operation each time the value of the field changes.
2015 In the ``init()`` method, we also define the default value of the field if
2016 none is specified by the form view (here we assume the default value of a
2017 ``char`` field should be an empty string).
2022 Fields that only display their content and don't give the possibility to the
2023 user to modify it can be useful, but most fields in Odoo allow edition
2024 too. This makes the field classes more complicated, mostly because fields are
2025 supposed to handle both and editable and non-editable mode, those modes are
2026 often completely different (for design and usability purpose) and the fields
2027 must be able to switch from one mode to another at any moment.
2029 To know in which mode the current field should be, the ``AbstractField`` class
2030 sets a widget property named ``effective_readonly``. The field should watch
2031 the changes in that widget property and display the correct mode
2032 accordingly. Example::
2034 instance.oepetstore.FieldChar2 = instance.web.form.AbstractField.extend({
2036 this._super.apply(this, arguments);
2037 this.set("value", "");
2040 this.on("change:effective_readonly", this, function() {
2041 this.display_field();
2042 this.render_value();
2044 this.display_field();
2045 return this._super();
2047 display_field: function() {
2049 this.$el.html(QWeb.render("FieldChar2", {widget: this}));
2050 if (! this.get("effective_readonly")) {
2051 this.$("input").change(function() {
2052 self.internal_set_value(self.$("input").val());
2056 render_value: function() {
2057 if (this.get("effective_readonly")) {
2058 this.$el.text(this.get("value"));
2060 this.$("input").val(this.get("value"));
2065 instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');
2069 <t t-name="FieldChar2">
2070 <div class="oe_field_char2">
2071 <t t-if="! widget.get('effective_readonly')">
2072 <input type="text"></input>
2077 In the ``start()`` method (which is called right after a widget has been
2078 appended to the DOM), we bind on the event ``change:effective_readonly``. That
2079 will allow use to redisplay the field each time the widget property
2080 ``effective_readonly`` changes. This event handler will call
2081 ``display_field()``, which is also called directly in ``start()``. This
2082 ``display_field()`` was created specifically for this field, it's not a method
2083 defined in ``AbstractField`` or any other class. This is the method we will
2084 use to display the content of the field depending we are in read-only mode or
2087 From now on the conception of this field is quite typical, except there is a
2088 lot of verifications to know the state of the ``effective_readonly`` property:
2090 * In the QWeb template used to display the content of the widget, it displays
2091 an ``<input type="text" />`` if we are in read-write mode and nothing in
2092 particular in read-only mode.
2093 * In the ``display_field()`` method, we have to bind on the ``change`` event
2094 of the ``<input type="text" />`` to know when the user has changed the
2095 value. When it happens, we call the ``internal_set_value()`` method with the
2096 new value of the field. This is a convenience method provided by the
2097 ``AbstractField`` class. That method will set a new value in the ``value``
2098 property but will not trigger a call to ``render_value()`` (which is not
2099 necessary since the ``<input type="text" />`` already contains the correct
2101 * In ``render_value()``, we use a completely different code to display the
2102 value of the field depending if we are in read-only or in read-write mode.
2104 .. exercise:: Create a Color Field
2106 Create a ``FieldColor`` class. The value of this field should be a string
2107 containing a color code like those used in CSS (example: ``#FF0000`` for
2108 red). In read-only mode, this color field should display a little block
2109 whose color corresponds to the value of the field. In read-write mode, you
2110 should display an ``<input type="color" />``. That type of ``<input />``
2111 is an HTML5 component that doesn't work in all browsers but works well in
2112 Google Chrome. So it's OK to use as an exercise.
2114 You can use that widget in the form view of the ``message_of_the_day``
2115 model for its field named ``color``. As a bonus, you can change the
2116 ``MessageOfTheDay`` widget created in the previous part of this guide to
2117 display the message of the day with the background color indicated in the
2122 .. code-block:: javascript
2124 instance.oepetstore.FieldColor = instance.web.form.AbstractField.extend({
2126 this._super.apply(this, arguments);
2127 this.set("value", "");
2130 this.on("change:effective_readonly", this, function() {
2131 this.display_field();
2132 this.render_value();
2134 this.display_field();
2135 return this._super();
2137 display_field: function() {
2139 this.$el.html(QWeb.render("FieldColor", {widget: this}));
2140 if (! this.get("effective_readonly")) {
2141 this.$("input").change(function() {
2142 self.internal_set_value(self.$("input").val());
2146 render_value: function() {
2147 if (this.get("effective_readonly")) {
2148 this.$(".oe_field_color_content").css("background-color", this.get("value") || "#FFFFFF");
2150 this.$("input").val(this.get("value") || "#FFFFFF");
2155 instance.web.form.widgets.add('color', 'instance.oepetstore.FieldColor');
2159 <t t-name="FieldColor">
2160 <div class="oe_field_color">
2161 <t t-if="widget.get('effective_readonly')">
2162 <div class="oe_field_color_content" />
2164 <t t-if="! widget.get('effective_readonly')">
2165 <input type="color"></input>
2172 .oe_field_color_content {
2175 border: 1px solid black;
2178 The Form View Custom Widgets
2179 %%%%%%%%%%%%%%%%%%%%%%%%%%%%
2181 Form fields can be useful, but their purpose is to edit a single field. To
2182 interact with the whole form view and have more liberty to integrate new
2183 widgets in it, it is recommended to create a custom form widget.
2185 Custom form widgets are widgets that can be added in any form view using a
2186 specific syntax in the XML definition of the view. Example:
2190 <widget type="xxx" />
2192 This type of widget will simply be created by the form view during the
2193 creation of the HTML according to the XML definition. They have properties in
2194 common with the fields (like the ``effective_readonly`` property) but they are
2195 not assigned a precise field. And so they don't have methods like
2196 ``get_value()`` and ``set_value()``. They must inherit from the ``FormWidget``
2199 The custom form widgets can also interact with the fields of the form view by
2200 getting or setting their values using the ``field_manager`` attribute of
2201 ``FormWidget``. Here is an example usage::
2203 instance.oepetstore.WidgetMultiplication = instance.web.form.FormWidget.extend({
2206 this.field_manager.on("field_changed:integer_a", this, this.display_result);
2207 this.field_manager.on("field_changed:integer_b", this, this.display_result);
2208 this.display_result();
2210 display_result: function() {
2211 var result = this.field_manager.get_field_value("integer_a") *
2212 this.field_manager.get_field_value("integer_b");
2213 this.$el.text("a*b = " + result);
2217 instance.web.form.custom_widgets.add('multiplication', 'instance.oepetstore.WidgetMultiplication');
2219 This example custom widget is designed to take the values of two existing
2220 fields (those must exist in the form view) and print the result of their
2221 multiplication. It also refreshes each time the value of any of those fields
2224 The ``field_manager`` attribute is in fact the ``FormView`` instance
2225 representing the form view. The methods that widgets can call on that form
2226 view are documented in the code of the web client in the ``FieldManagerMixin``
2227 interface. The most useful features are:
2229 * The method ``get_field_value()`` which returns the value of a field.
2230 * When the value of a field is changed, for any reason, the form view will
2231 trigger an event named ``field_changed:xxx`` where ``xxx`` is the name of
2233 * Also, it is possible to change the value of the fields using the method
2234 ``set_values()``. This method takes a dictionary as first and only argument
2235 whose keys are the names of the fields to change and values are the new
2238 .. exercise:: Show Coordinates on Google Map
2240 In this exercise we would like to add two new fields on the
2241 ``product.product`` model: ``provider_latitude`` and
2242 ``provider_longitude``. Those would represent coordinates on a map. We
2243 also would like you to create a custom widget able to display a map
2244 showing these coordinates.
2246 To display that map, you can simply use the Google Map service using an HTML code similar to this:
2248 .. code-block:: html
2250 <iframe width="400" height="300" src="https://maps.google.com/?ie=UTF8&ll=XXX,YYY&output=embed">
2253 Just replace ``XXX`` with the latitude and ``YYY`` with the longitude.
2255 You should display those two new fields as well as the map widget in a new
2256 page of the notebook displayed in the product form view.
2260 .. code-block:: javascript
2262 instance.oepetstore.WidgetCoordinates = instance.web.form.FormWidget.extend({
2265 this.field_manager.on("field_changed:provider_latitude", this, this.display_map);
2266 this.field_manager.on("field_changed:provider_longitude", 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,
2277 instance.web.form.custom_widgets.add('coordinates', 'instance.oepetstore.WidgetCoordinates');
2281 <t t-name="WidgetCoordinates">
2282 <iframe width="400" height="300"
2283 t-att-src="'https://maps.google.com/?ie=UTF8&ll=' + latitude + ',' + longitude + '&output=embed'">
2287 .. exercise:: Get the Current Coordinate
2289 Now we would like to display an additional button to automatically set the
2290 coordinates to the location of the current user.
2292 To get the coordinates of the user, an easy way is to use the geolocation
2293 JavaScript API. `See the online documentation to know how to use it`_.
2295 .. _See the online documentation to know how to use it: http://www.w3schools.com/html/html5_geolocation.asp
2297 Please also note that it wouldn't be very logical to allow the user to
2298 click on that button when the form view is in read-only mode. So, this
2299 custom widget should handle correctly the ``effective_readonly`` property
2300 just like any field. One way to do this would be to make the button
2301 disappear when ``effective_readonly`` is true.
2305 .. code-block:: javascript
2307 instance.oepetstore.WidgetCoordinates = instance.web.form.FormWidget.extend({
2310 this.field_manager.on("field_changed:provider_latitude", this, this.display_map);
2311 this.field_manager.on("field_changed:provider_longitude", this, this.display_map);
2312 this.on("change:effective_readonly", this, this.display_map);
2315 display_map: function() {
2317 this.$el.html(QWeb.render("WidgetCoordinates", {
2318 "latitude": this.field_manager.get_field_value("provider_latitude") || 0,
2319 "longitude": this.field_manager.get_field_value("provider_longitude") || 0,
2321 this.$("button").toggle(! this.get("effective_readonly"));
2322 this.$("button").click(function() {
2323 navigator.geolocation.getCurrentPosition(_.bind(self.received_position, self));
2326 received_position: function(obj) {
2327 var la = obj.coords.latitude;
2328 var lo = obj.coords.longitude;
2329 this.field_manager.set_values({
2330 "provider_latitude": la,
2331 "provider_longitude": lo,
2336 instance.web.form.custom_widgets.add('coordinates', 'instance.oepetstore.WidgetCoordinates');
2340 <t t-name="WidgetCoordinates">
2341 <iframe width="400" height="300"
2342 t-att-src="'https://maps.google.com/?ie=UTF8&ll=' + latitude + ',' + longitude + '&output=embed'">
2344 <button>Get My Current Coordinate</button>
2347 .. _jQuery: http://jquery.org
2348 .. _Underscore.js: http://underscorejs.org