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:
62 The module already holds various server customizations. We'll come back to
63 these later, for now let's focus on the web-related content, in the ``static``
66 Files used in the "web" side of an Odoo module must be placed in a ``static``
67 folder so they are available to a web browser, files outside that folder can
68 not be fetched by browsers. The ``src/css``, ``src/js`` and ``src/xml``
69 sub-folders are conventional and not strictly necessary.
71 ``oepetstore/static/css/petstore.css``
72 currently empty, will hold the CSS_ for pet store content
73 ``oepetstore/static/xml/petstore.xml``
74 Mostly empty, will hold :ref:`reference/qweb` templates
75 ``oepetstore/static/js/petstore.js``
76 The most important (and interesting) part, contains the logic of the
77 application (or at least its web-browser side) as javascript. It should
80 openerp.oepetstore = function(instance, local) {
81 var _t = openerp.web._t,
82 _lt = openerp.web._lt;
83 var QWeb = openerp.web.qweb;
85 local.HomePage = instance.Widget.extend({
87 console.log("pet store home page loaded");
91 openerp.web.client_actions.add(
92 'petstore.homepage', 'local.HomePage');
95 Which only prints a small message in the browser's console.
99 All JavaScript files are concatenated and :term:`minified` to improve
100 application load time.
102 One of the drawback is debugging becomes more difficult as
103 individual files disappear and the code is made significantly less
104 readable. It is possible to disable this process by enabling the
105 "developer mode": log into your Odoo instance (user *admin* password
106 *admin* by default) open the user menu (in the top-right corner of the
107 Odoo screen) and select :guilabel:`About Odoo` then :guilabel:`Activate
110 .. image:: web/about_odoo.png
113 .. image:: web/devmode.png
116 This will reload the web client with optimizations disabled, making
117 development and debugging significantly more comfortable.
119 .. todo:: qweb files hooked via __openerp__.py, but js and CSS use bundles
121 Odoo JavaScript Module
122 ======================
124 Javascript doesn't have built-in modules. As a result variables defined in
125 different files are all mashed together and may conflict. This has given rise
126 to various module patterns used to build clean namespaces and limit risks of
129 The Odoo framework uses one such pattern to define modules within web addons,
130 in order to both namespace code and correctly order its loading.
132 ``oepetstore/static/js/petstore.js`` contains a module declaration::
134 openerp.oepetstore = function(instance, local) {
138 In Odoo web, modules are declared as functions set on the global ``openerp``
139 variable. The function's name must be the same as the addon (in this case
140 ``oepetstore``) so the framework can find it, and automatically initialize it.
142 When the web client decides to load your module, it'll call the root function
143 and provide two parameters:
145 * the first parameter is the current instance of the Odoo web client, it gives
146 access to various capabilities defined by the Odoo (translations,
147 network services) as well as objects defined by the core or by other
149 * the second parameter is your own local namespace automatically created by
150 the web client. Objects and variables which should be accessible from
151 outside your module (either because the Odoo web client needs to call them
152 or because others may want to customize them) should be set inside that
158 Much as modules, and contrary to most object-oriented languages, javascript
159 does not build in *classes*\ [#classes]_ although it provides roughly
160 equivalent (if lower-level and more verbose) mechanisms.
162 For simplicity and developer-friendliness purposes, Odoo web provides a class
163 system based on John Resig's `Simple JavaScript Inheritance`_.
165 New classes are defined by calling the :func:`~openerp.web.Class.extend`
166 method of :class:`openerp.web.Class`::
168 var MyClass = instance.web.Class.extend({
169 say_hello: function() {
170 console.log("hello");
174 The :func:`~openerp.web.Class.extend` method takes a dictionary describing
175 the new class's content (methods and static attributes). In this case, it will
176 only have a ``say_hello`` method which takes no parameters.
178 Classes are instantiated using the ``new`` operator::
180 var my_object = new MyClass();
181 my_object.say_hello();
182 // print "hello" in the console
184 And attributes of the instance can be accessed via ``this``::
186 var MyClass = instance.web.Class.extend({
187 say_hello: function() {
188 console.log("hello", this.name);
192 var my_object = new MyClass();
193 my_object.name = "Bob";
194 my_object.say_hello();
195 // print "hello Bob" in the console
197 Classes can provide an initializer to perform the initial setup of the
198 instance, by defining an ``init()`` method. The initializer receives the
199 parameters passed when using the ``new`` operator::
201 var MyClass = instance.web.Class.extend({
202 init: function(name) {
205 say_hello: function() {
206 console.log("hello", this.name);
210 var my_object = new MyClass("Bob");
211 my_object.say_hello();
212 // print "hello Bob" in the console
214 It is also possible to create subclasses from existing (used-defined) classes
215 by calling :func:`~openerp.web.Class.extend` on the parent class, as is done
216 to subclass :class:`~openerp.web.Class`::
218 var MySpanishClass = MyClass.extend({
219 say_hello: function() {
220 console.log("hola", this.name);
224 var my_object = new MySpanishClass("Bob");
225 my_object.say_hello();
226 // print "hola Bob" in the console
228 When overriding a method using inheritance, you can use ``this._super()`` to
229 call the original method::
231 var MySpanishClass = MyClass.extend({
232 say_hello: function() {
234 console.log("translation in Spanish: hola", this.name);
238 var my_object = new MySpanishClass("Bob");
239 my_object.say_hello();
240 // print "hello Bob \n translation in Spanish: hola Bob" in the console
244 ``_super`` is not a standard method, it is set on-the-fly to the next
245 method in the current inheritance chain, if any. It is only defined
246 during the *synchronous* part of a method call, for use in asynchronous
247 handlers (after network calls or in ``setTimeout`` callbacks) a reference
248 to its value should be retained, it should not be accessed via ``this``::
250 // broken, will generate an error
251 say_hello: function () {
252 setTimeout(function () {
258 say_hello: function () {
259 // don't forget .bind()
260 var _super = this._super.bind(this);
261 setTimeout(function () {
269 The Odoo web client bundles jQuery_ for easy DOM manipulation. It is useful
270 and provides a better API than standard `W3C DOM`_\ [#dombugs]_, but
271 insufficient to structure complex applications leading to difficult
274 Much like object-oriented desktop UI toolkits (e.g. Qt_, Cocoa_ or GTK_),
275 Odoo Web makes specific components responsible for sections of a page. In
276 Odoo web, the base for such components is the :class:`~openerp.Widget`
277 class, a component specialized in handling a page section and displaying
278 information for the user.
283 The initial demonstration module already provides a basic widget::
285 local.HomePage = instance.Widget.extend({
287 console.log("pet store home page loaded");
291 It extends :class:`~openerp.Widget` and overrides the standard method
292 :func:`~openerp.Widget.start`, which — much like the previous ``MyClass``
293 — does little for now.
295 This line at the end of the file::
297 instance.web.client_actions.add(
298 'petstore.homepage', 'instance.oepetstore.HomePage');
300 registers our basic widget as a client action. Client actions will be
301 explained later in the guide, for now this is just what allows our widget to
302 be called and displayed when we select the
303 :menuselection:`Pet Store --> Pet Store --> Home Page` menu.
307 because the widget will be called from outside our module, the web client
308 needs its "fully qualified" name, not the local version.
313 Widgets have a number of methods and features, but the basics are simple:
316 * format the widget's data
319 The ``HomePage`` widget already has a :func:`~openerp.Widget.start`
320 method. That method is part of the normal widget lifecycle and automatically
321 called once the widget is inserted in the page. We can use it to display some
324 All widgets have a :attr:`~openerp.Widget.$el` which represents the
325 section of page they're in charge of (as a jQuery_ object). Widget content
326 should be inserted there. By default, :attr:`~openerp.Widget.$el` is an
327 empty ``<div>`` element.
329 A ``<div>`` element is usually invisible for the user if it does not
330 have any content (or specific styles giving it a size) which is why nothing
331 is displayed on the page when ``HomePage`` is launched.
333 Let's add some content to the widget's root element, using jQuery::
335 local.HomePage = instance.Widget.extend({
337 this.$el.append("<div>Hello dear Odoo user!</div>");
341 That message will now appear when you open :menuselection:`Pet Store
342 --> Pet Store --> Home Page`
346 to refresh the javascript code loaded in Odoo Web, you will need to reload
347 the page. There is no need to restart the Odoo server
349 The ``HomePage`` widget is used by Odoo Web and managed automatically, to
350 learn how to use a widget "from scratch" let's create a new one::
352 local.GreetingsWidget = instance.Widget.extend({
354 this.$el.append("<div>We are so happy to see you again in this menu!</div>");
358 We can now add our ``GreetingsWidget`` to the ``HomePage`` by using the
359 ``GreetingsWidget``'s :func:`~openerp.Widget.appendTo` method::
361 local.HomePage = instance.Widget.extend({
363 this.$el.append("<div>Hello dear Odoo user!</div>");
364 var greeting = new local.GreetingsWidget(this);
365 return greeting.appendTo(this.$el);
369 * ``HomePage`` first adds its own content to its DOM root
370 * ``HomePage`` then instantiates ``GreetingsWidget``
371 * Finally it tells ``GreetingsWidget`` where to insert itself, delegating part
372 of its :attr:`~openerp.Widget.$el` to the ``GreetingsWidget``.
374 When the :func:`~openerp.Widget.appendTo` method is called, it asks the
375 widget to insert itself at the specified position and to display its content.
376 The :func:`~openerp.Widget.start` method will be called during the call
377 to :func:`~openerp.Widget.appendTo`.
379 To see what happens under the displayed interface, we will use the browser's
380 DOM Explorer. But first let's alter our widgets slightly so we can more easily
381 find where they are, by :attr:`adding a class to their root elements
382 <openerp.Widget.className>`::
384 local.HomePage = instance.Widget.extend({
385 className: 'oe_petstore_homepage',
388 local.GreetingsWidget = instance.Widget.extend({
389 className: 'oe_petstore_greetings',
393 If you can find the relevant section of the DOM (right-click on the text
394 then :guilabel:`Inspect Element`), it should look like this:
398 <div class="oe_petstore_homepage">
399 <div>Hello dear Odoo user!</div>
400 <div class="oe_petstore_greetings">
401 <div>We are so happy to see you again in this menu!</div>
405 Which clearly shows the two ``<div>`` elements automatically created by
406 :class:`~openerp.Widget`, because we added some classes on them.
408 We can also see the two message-holding divs we added ourselves
410 Finally, note the ``<div class="oe_petstore_greetings">`` element which
411 represents the ``GreetingsWidget`` instance is *inside* the
412 ``<div class="oe_petstore_homepage">`` which represents the ``HomePage``
413 instance, since we appended
415 Widget Parents and Children
416 ---------------------------
418 In the previous part, we instantiated a widget using this syntax::
420 new local.GreetingsWidget(this);
422 The first argument is ``this``, which in that case was a ``HomePage``
423 instance. This tells the widget being created which other widget is its
426 As we've seen, widgets are usually inserted in the DOM by another widget and
427 *inside* that other widget's root element. This means most widgets are "part"
428 of another widget, and exist on behalf of it. We call the container the
429 *parent*, and the contained widget the *child*.
431 Due to multiple technical and conceptual reasons, it is necessary for a widget
432 to know who is his parent and who are its children.
434 :func:`~openerp.Widget.getParent`
435 can be used to get the parent of a widget::
437 local.GreetingsWidget = instance.Widget.extend({
439 console.log(this.getParent().$el );
440 // will print "div.oe_petstore_homepage" in the console
444 :func:`~openerp.Widget.getChildren`
445 can be used to get a list of its children::
447 local.HomePage = instance.Widget.extend({
449 var greeting = new local.GreetingsWidget(this);
450 greeting.appendTo(this.$el);
451 console.log(this.getChildren()[0].$el);
452 // will print "div.oe_petstore_greetings" in the console
456 When overriding the :func:`~openerp.Widget.init` method of a widget it is
457 *of the utmost importance* to pass the parent to the ``this._super()`` call,
458 otherwise the relation will not be set up correctly::
460 local.GreetingsWidget = instance.Widget.extend({
461 init: function(parent, name) {
467 Finally, if a widget does not have a parent (e.g. because it's the root
468 widget of the application), ``null`` can be provided as parent::
470 new local.GreetingsWidget(null);
475 If you can display content to your users, you should also be able to erase
476 it. This is done via the :func:`~openerp.Widget.destroy` method::
480 When a widget is destroyed it will first call
481 :func:`~openerp.Widget.destroy` on all its children. Then it erases itself
482 from the DOM. If you have set up permanent structures in
483 :func:`~openerp.Widget.init` or :func:`~openerp.Widget.start` which
484 must be explicitly cleaned up (because the garbage collector will not handle
485 them), you can override :func:`~openerp.Widget.destroy`.
489 when overriding :func:`~openerp.Widget.destroy`, ``_super()``
490 *must always* be called otherwise the widget and its children are not
491 correctly cleaned up leaving possible memory leaks and "phantom events",
492 even if no error is displayed
494 The QWeb Template Engine
495 ========================
497 In the previous section we added content to our widgets by directly
498 manipulating (and adding to) their DOM::
500 this.$el.append("<div>Hello dear Odoo user!</div>");
502 This allows generating and displaying any type of content, but tends to
503 rapidly get unwieldy when generating significant amounts of DOM (lots of
504 duplication, quoting issues, ...)
506 As many other environments, Odoo's solution is to use a `template engine`_.
507 Odoo's template engine is called :ref:`reference/qweb`.
509 QWeb is an XML-based templating language, similar to `Genshi
510 <http://en.wikipedia.org/wiki/Genshi_(templating_language)>`_, `Thymeleaf
511 <http://en.wikipedia.org/wiki/Thymeleaf>`_ or `Facelets
512 <http://en.wikipedia.org/wiki/Facelets>`_. It has the following
515 * It's implemented fully in JavaScript and rendered in the browser
516 * Each template file (XML files) contains multiple templates
517 * It has special support in Odoo Web's :class:`~openerp.Widget`, though it
518 can be used outside of Odoo's web client (and it's possible to use
519 :class:`~openerp.Widget` without relying on QWeb)
523 The rationale behind using QWeb instead of existing javascript template
524 engines is the extensibility of pre-existing (third-party) templates, much
525 like Odoo :ref:`views <reference/views>`.
527 Most javascript template engines are text-based which precludes easy
528 structural extensibility where an XML-based templating engine can be
529 generically altered using e.g. XPath or CSS and a tree-alteration DSL (or
530 even just XSLT). This flexibility and extensibility is a core
531 characteristic of Odoo, and losting it was considered unacceptable.
536 First let's define a simple QWeb template in the almost-empty
537 ``oepetstore/static/src/xml/petstore.xml`` file:
541 <?xml version="1.0" encoding="UTF-8"?>
542 <templates xml:space="preserve">
543 <t t-name="HomePageTemplate">
544 <div style="background-color: red;">This is some simple HTML</div>
548 Now we can use this template inside of the ``HomePage`` widget. Using the
549 ``QWeb`` loader variable defined at the top of the page, we can call to the
550 template defined in the XML file::
552 local.HomePage = instance.Widget.extend({
554 this.$el.append(QWeb.render("HomePageTemplate"));
558 :func:`QWeb.render` looks for the specified template, renders it to a string
559 and returns the result.
561 However, because :class:`~openerp.Widget` has special integration for QWeb
562 the template can be set directly on the widget via its
563 :attr:`~openerp.Widget.template` attribute::
565 local.HomePage = instance.Widget.extend({
566 template: "HomePageTemplate",
572 Although the result look similar, there are two differences between these
575 * with the second version, the template is rendered right before
576 :func:`~openerp.Widget.start` is called
577 * in the first version the template's content is added to the widget's root
578 element, whereas in the second version the template's root element is
579 directly *set as* the widget's root element. Which is why the "greetings"
580 sub-widget also gets a red background
584 templates should have a single non-``t`` root element, especially if
585 they're set as a widget's :attr:`~openerp.Widget.template`. If there are
586 multiple "root elements", results are undefined (usually only the first
587 root element will be used and the others will be ignored)
592 QWeb templates can be given data and can contain basic display logic.
594 For explicit calls to :func:`QWeb.render`, the template data is passed as
597 QWeb.render("HomePageTemplate", {name: "Klaus"});
599 with the template modified to:
603 <t t-name="HomePageTemplate">
604 <div>Hello <t t-esc="name"/></div>
611 <div>Hello Klaus</div>
613 When using :class:`~openerp.Widget`'s integration it is not possible to
614 provide additional data to the template. The template will be given a single
615 ``widget`` context variable, referencing the widget being rendered right
616 before :func:`~openerp.Widget.start` is called (the widget's state will
617 essentially be that set up by :func:`~openerp.Widget.init`):
621 <t t-name="HomePageTemplate">
622 <div>Hello <t t-esc="widget.name"/></div>
627 local.HomePage = instance.Widget.extend({
628 template: "HomePageTemplate",
629 init: function(parent) {
631 this.name = "Mordecai";
641 <div>Hello Mordecai</div>
646 We've seen how to *render* QWeb templates, let's now see the syntax of
647 the templates themselves.
649 A QWeb template is composed of regular XML mixed with QWeb *directives*. A
650 QWeb directive is declared with XML attributes starting with ``t-``.
652 The most basic directive is ``t-name``, used to declare new templates in
658 <t t-name="HomePageTemplate">
659 <div>This is some simple HTML</div>
663 ``t-name`` takes the name of the template being defined, and declares that
664 it can be called using :func:`QWeb.render`. It can only be used at the
665 top-level of a template file.
670 The ``t-esc`` directive can be used to output text:
674 <div>Hello <t t-esc="name"/></div>
676 It takes a Javascript expression which is evaluated, the result of the
677 expression is then HTML-escaped and inserted in the document. Since it's an
678 expression it's possible to provide just a variable name as above, or a more
679 complex expression like a computation:
683 <div><t t-esc="3+5"/></div>
689 <div><t t-esc="name.toUpperCase()"/></div>
694 To inject HTML in the page being rendered, use ``t-raw``. Like ``t-esc`` it
695 takes an arbitrary Javascript expression as parameter, but it does not
696 perform an HTML-escape step.
700 <div><t t-raw="name.link(user_account)"/></div>
704 ``t-raw`` *must not* be used on any data which may contain non-escaped
705 user-provided content as this leads to `cross-site scripting`_
711 QWeb can have conditional blocks using ``t-if``. The directive takes an
712 arbitrary expression, if the expression is falsy (``false``, ``null``, ``0``
713 or an empty string) the whole block is suppressed, otherwise it is displayed.
718 <t t-if="true == true">
721 <t t-if="true == false">
728 QWeb doesn't have an "else" structure, use a second ``t-if`` with the
729 original condition inverted. You may want to store the condition in a
730 local variable if it's a complex or expensive expression.
735 To iterate on a list, use ``t-foreach`` and ``t-as``. ``t-foreach`` takes an
736 expression returning a list to iterate on ``t-as`` takes a variable name to
737 bind to each item during iteration.
742 <t t-foreach="names" t-as="name">
744 Hello <t t-esc="name"/>
749 .. note:: ``t-foreach`` can also be used with numbers and objects
755 QWeb provides two related directives to define computed attributes:
756 :samp:`t-att-{name}` and :samp:`t-attf-{name}`. In either case, *name* is the
757 name of the attribute to create (e.g. ``t-att-id`` defines the attribute
758 ``id`` after rendering).
760 ``t-att-`` takes a javascript expression whose result is set as the
761 attribute's value, it is most useful if all of the attribute's value is
768 <input type="text" t-att-value="defaultName"/>
771 ``t-attf-`` takes a *format string*. A format string is literal text with
772 interpolation blocks inside, an interpolation block is a javascript
773 expression between ``{{`` and ``}}``, which will be replaced by the result
774 of the expression. It is most useful for attributes which are partially
775 literal and partially computed such as a class:
779 <div t-attf-class="container {{ left ? 'text-left' : '' }} {{ extra_class }}">
783 Calling other templates
784 '''''''''''''''''''''''
786 Templates can be split into sub-templates (for simplicity, maintainability,
787 reusability or to avoid excessive markup nesting).
789 This is done using the ``t-call`` directive, which takes the name of the
800 <div class="i-am-b"/>
803 rendering the ``A`` template will result in:
808 <div class="i-am-b"/>
811 Sub-templates inherit the rendering context of their caller.
813 To Learn More About QWeb
814 ''''''''''''''''''''''''
816 For a QWeb reference, see :ref:`reference/qweb`.
821 .. exercise:: Usage of QWeb in Widgets
823 Create a widget whose constructor takes two parameters aside from
824 ``parent``: ``product_names`` and ``color``.
826 * ``product_names`` should an array of strings, each one the name of a
828 * ``color`` is a string containing a color in CSS color format (ie:
829 ``#000000`` for black).
831 The widget should display the given product names one under the other,
832 each one in a separate box with a background color with the value of
833 ``color`` and a border. You should use QWeb to render the HTML. Any
834 necessary CSS should be in ``oepetstore/static/src/css/petstore.css``.
836 Use the widget in ``HomePage`` with half a dozen products.
842 openerp.oepetstore = function(instance, local) {
843 var _t = instance.web._t,
844 _lt = instance.web._lt;
845 var QWeb = instance.web.qweb;
847 local.HomePage = instance.Widget.extend({
849 var products = new local.ProductsWidget(
850 this, ["cpu", "mouse", "keyboard", "graphic card", "screen"], "#00FF00");
851 products.appendTo(this.$el);
855 local.ProductsWidget = instance.Widget.extend({
856 template: "ProductsWidget",
857 init: function(parent, products, color) {
859 this.products = products;
864 instance.web.client_actions.add(
865 'petstore.homepage', 'instance.oepetstore.HomePage');
870 <?xml version="1.0" encoding="UTF-8"?>
871 <templates xml:space="preserve">
872 <t t-name="ProductsWidget">
874 <t t-foreach="widget.products" t-as="product">
875 <span class="oe_products_item"
876 t-attf-style="background-color: {{ widget.color }};">
888 display: inline-block;
891 border: 1px solid black;
895 .. image:: web/qweb.*
902 ``Widget``'s jQuery Selector
903 ----------------------------
905 Selecting DOM elements within a widget can be performed by calling the
906 ``find()`` method on the widget's DOM root::
908 this.$el.find("input.my_input")...
910 But because it's an extremely common operation, :class:`~openerp.Widget`
911 provides an equivalent shortcut through the :func:`~openerp.Widget.$`
914 local.MyWidget = instance.Widget.extend({
916 this.$("input.my_input")...
922 The global jQuery function ``$()`` should *never* be used unless it is
923 absolutely necessary: selection on a widget's root are scoped to the
924 widget and local to it, but selections with ``$()`` are global to the
925 page/application and may match parts of other widgets and views, leading
926 to odd or dangerous side-effects. Since a widget should generally act
927 only on the DOM section it owns, there is no cause for global selection.
929 Easier DOM Events Binding
930 -------------------------
932 We have previously bound DOM events using normal jQuery event handlers (e.g.
933 ``.click()`` or ``.change()``) on widget elements::
935 local.MyWidget = instance.Widget.extend({
938 this.$(".my_button").click(function() {
939 self.button_clicked();
942 button_clicked: function() {
947 While this works it has a few issues:
949 1. it is rather verbose
950 2. it does not support replacing the widget's root element at runtime as
951 the binding is only performed when ``start()`` is run (during widget
953 3. it requires dealing with ``this``-binding issues
955 Widgets thus provide a shortcut to DOM event binding via
956 :attr:`~openerp.Widget.events`::
958 local.MyWidget = instance.Widget.extend({
960 "click .my_button": "button_clicked",
962 button_clicked: function() {
967 :attr:`~openerp.Widget.events` is an object (mapping) of an event to the
968 function or method to call when the event is triggered:
970 * the key is an event name, possibly refined with a CSS selector in which
971 case only if the event happens on a selected sub-element will the function
972 or method run: ``click`` will handle all clicks within the widget, but
973 ``click .my_button`` will only handle clicks in elements bearing the
975 * the value is the action to perform when the event is triggered
977 It can be either a function::
980 'click': function (e) { /* code here */ }
983 or the name of a method on the object (see example above).
985 In either case, the ``this`` is the widget instance and the handler is given
986 a single parameter, the `jQuery event object`_ for the event.
988 Widget Events and Properties
989 ============================
994 Widgets provide an event system (separate from the DOM/jQuery event system
995 described above): a widget can fire events on itself, and other widgets (or
996 itself) can bind themselves and listen for these events::
998 local.ConfirmWidget = instance.Widget.extend({
1000 'click button.ok_button': function () {
1001 this.trigger('user_chose', true);
1003 'click button.cancel_button': function () {
1004 this.trigger('user_chose', false);
1008 this.$el.append("<div>Are you sure you want to perform this action?</div>" +
1009 "<button class='ok_button'>Ok</button>" +
1010 "<button class='cancel_button'>Cancel</button>");
1014 This widget acts as a facade, transforming user input (through DOM events)
1015 into a documentable internal event to which parent widgets can bind
1018 :func:`~openerp.Widget.trigger` takes the name of the event to trigger as
1019 its first (mandatory) argument, any further arguments are treated as event
1020 data and passed directly to listeners.
1022 We can then set up a parent event instantiating our generic widget and
1023 listening to the ``user_chose`` event using :func:`~openerp.Widget.on`::
1025 local.HomePage = instance.Widget.extend({
1027 var widget = new local.ConfirmWidget(this);
1028 widget.on("user_chose", this, this.user_chose);
1029 widget.appendTo(this.$el);
1031 user_chose: function(confirm) {
1033 console.log("The user agreed to continue");
1035 console.log("The user refused to continue");
1040 :func:`~openerp.Widget.on` binds a function to be called when the
1041 event identified by ``event_name`` is. The ``func`` argument is the
1042 function to call and ``object`` is the object to which that function is
1043 related if it is a method. The bound function will be called with the
1044 additional arguments of :func:`~openerp.Widget.trigger` if it has
1049 widget.on("my_event", this, this.my_event_triggered);
1050 widget.trigger("my_event", 1, 2, 3);
1052 my_event_triggered: function(a, b, c) {
1053 console.log(a, b, c);
1054 // will print "1 2 3"
1059 Triggering events on an other widget is generally a bad idea. The main
1060 exception to that rule is ``openerp.web.bus`` which exists specifically
1061 to broadcasts evens in which any widget could be interested throughout
1062 the Odoo web application.
1067 Properties are very similar to normal object attributes in that they allow
1068 storing data on a widget instance, however they have the additional feature
1069 that they trigger events when set::
1073 this.widget.on("change:name", this, this.name_changed);
1074 this.widget.set("name", "Nicolas");
1076 name_changed: function() {
1077 console.log("The new value of the property 'name' is", this.widget.get("name"));
1080 * :func:`~openerp.Widget.set` sets the value of a property and triggers
1081 :samp:`change:{propname}` (where *propname* is the property name passed as
1082 first parameter to :func:`~openerp.Widget.set`) and ``change``
1083 * :func:`~openerp.Widget.get` retrieves the value of a property.
1088 .. exercise:: Widget Properties and Events
1090 Create a widget ``ColorInputWidget`` that will display 3 ``<input
1091 type="text">``. Each of these ``<input>`` is dedicated to type a
1092 hexadecimal number from 00 to FF. When any of these ``<input>`` is
1093 modified by the user the widget must query the content of the three
1094 ``<input>``, concatenate their values to have a complete CSS color code
1095 (ie: ``#00FF00``) and put the result in a property named ``color``. Please
1096 note the jQuery ``change()`` event that you can bind on any HTML
1097 ``<input>`` element and the ``val()`` method that can query the current
1098 value of that ``<input>`` could be useful to you for this exercise.
1100 Then, modify the ``HomePage`` widget to instantiate ``ColorInputWidget``
1101 and display it. The ``HomePage`` widget should also display an empty
1102 rectangle. That rectangle must always, at any moment, have the same
1103 background color than the color in the ``color`` property of the
1104 ``ColorInputWidget`` instance.
1106 Use QWeb to generate all HTML.
1112 openerp.oepetstore = function(instance, local) {
1113 var _t = instance.web._t,
1114 _lt = instance.web._lt;
1115 var QWeb = instance.web.qweb;
1117 local.ColorInputWidget = instance.Widget.extend({
1118 template: "ColorInputWidget",
1120 'change input': 'input_changed'
1123 this.input_changed();
1125 input_changed: function() {
1128 this.$(".oe_color_red").val(),
1129 this.$(".oe_color_green").val(),
1130 this.$(".oe_color_blue").val()
1132 this.set("color", color);
1136 local.HomePage = instance.Widget.extend({
1137 template: "HomePage",
1139 this.colorInput = new local.ColorInputWidget(this)
1140 .on("change:color", this, this.color_changed);
1141 .appendTo(this.$el);
1143 color_changed: function() {
1144 this.$(".oe_color_div").css("background-color", this.colorInput.get("color"));
1148 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1153 <?xml version="1.0" encoding="UTF-8"?>
1154 <templates xml:space="preserve">
1155 <t t-name="ColorInputWidget">
1157 Red: <input type="text" class="oe_color_red" value="00"></input><br />
1158 Green: <input type="text" class="oe_color_green" value="00"></input><br />
1159 Blue: <input type="text" class="oe_color_blue" value="00"></input><br />
1162 <t t-name="HomePage">
1164 <div class="oe_color_div"></div>
1177 Modify existing widgets and classes
1178 ===================================
1180 The class system of the Odoo web framework allows direct modification of
1181 existing classes using the :func:`~openerp.web.Class.include` method::
1183 var TestClass = instance.web.Class.extend({
1184 testMethod: function() {
1190 testMethod: function() {
1191 return this._super() + " world";
1195 console.log(new TestClass().testMethod());
1196 // will print "hello world"
1198 This system is similar to the inheritance mechanism, except it will alter the
1199 target class in-place instead of creating a new class.
1201 In that case, ``this._super()`` will call the original implementation of a
1202 method being replaced/redefined. If the class already had sub-classes, all
1203 calls to ``this._super()`` in sub-classes will call the new implementations
1204 defined in the call to :func:`~openerp.web.Class.include`. This will also work
1205 if some instances of the class (or of any of its sub-classes) were created
1206 prior to the call to :func:`~openerp.Widget.include`.
1211 The process to translate text in Python and JavaScript code is very
1212 similar. You could have noticed these lines at the beginning of the
1213 ``petstore.js`` file::
1215 var _t = instance.web._t,
1216 _lt = instance.web._lt;
1218 These lines are simply used to import the translation functions in the current
1219 JavaScript module. They are used thus::
1221 this.$el.text(_t("Hello user!"));
1223 In Odoo, translations files are automatically generated by scanning the source
1224 code. All piece of code that calls a certain function are detected and their
1225 content is added to a translation file that will then be sent to the
1226 translators. In Python, the function is ``_()``. In JavaScript the function is
1227 :func:`~openerp.web._t` (and also :func:`~openerp.web._lt`).
1229 ``_t()`` will return the translation defined for the text it is given. If no
1230 translation is defined for that text, it will return the original text as-is.
1234 To inject user-provided values in translatable strings, it is recommended
1235 to use `_.str.sprintf
1236 <http://gabceb.github.io/underscore.string.site/#sprintf>`_ with named
1237 arguments *after* the translation::
1239 this.$el.text(_.str.sprintf(
1240 _t("Hello, %(user)s!"), {
1244 This makes translatable strings more readable to translators, and gives
1245 them more flexibility to reorder or ignore parameters.
1247 :func:`~openerp.web._lt` ("lazy translate") is similar but somewhat more
1248 complex: instead of translating its parameter immediately, it returns
1249 an object which, when converted to a string, will perform the translation.
1251 It is used to define translatable terms before the translations system is
1252 initialized, for class attributes for instance (as modules are loaded before
1253 the user's language is configured and translations are downloaded).
1255 Communication with the Odoo Server
1256 ==================================
1261 Most operations with Odoo involve communicating with *models* implementing
1262 business concern, these models will then (potentially) interact with some
1263 storage engine (usually PostgreSQL_).
1265 Although jQuery_ provides a `$.ajax`_ function for network interactions,
1266 communicating with Odoo requires additional metadata whose setup before every
1267 call would be verbose and error-prone. As a result, Odoo web provides
1268 higher-level communication primitives.
1270 To demonstrate this, the file ``petstore.py`` already contains a small model
1271 with a sample method:
1273 .. code-block:: python
1275 class message_of_the_day(models.Model):
1276 _name = "oepetstore.message_of_the_day"
1279 def my_method(self):
1280 return {"hello": "world"}
1282 message = fields.Text(),
1283 color = fields.Char(size=20),
1285 This declares a model with two fields, and a method ``my_method()`` which
1286 returns a literal dictionary.
1288 Here is a sample widget that calls ``my_method()`` and displays the result::
1290 local.HomePage = instance.Widget.extend({
1293 var model = new instance.web.Model("oepetstore.message_of_the_day");
1294 model.call("my_method", {context: new instance.web.CompoundContext()}).then(function(result) {
1295 self.$el.append("<div>Hello " + result["hello"] + "</div>");
1296 // will show "Hello world" to the user
1301 The class used to call Odoo models is :class:`openerp.Model`. It is
1302 instantiated with the Odoo model's name as first parameter
1303 (``oepetstore.message_of_the_day`` here).
1305 :func:`~openerp.web.Model.call` can be used to call any (public) method of an
1306 Odoo model. It takes the following positional arguments:
1309 The name of the method to call, ``my_method`` here
1311 an array of `positional arguments`_ to provide to the method. Because the
1312 example has no positional argument to provide, the ``args`` parameter is not
1315 Here is an other example with positional arguments:
1317 .. code-block:: python
1320 def my_method2(self, a, b, c): ...
1322 .. code-block:: javascript
1324 model.call("my_method", [1, 2, 3], ...
1325 // with this a=1, b=2 and c=3
1328 a mapping of `keyword arguments`_ to pass. The example provides a single
1329 named argument ``context``.
1331 .. code-block:: python
1334 def my_method2(self, a, b, c): ...
1336 .. code-block:: javascript
1338 model.call("my_method", [], {a: 1, b: 2, c: 3, ...
1339 // with this a=1, b=2 and c=3
1341 :func:`~openerp.Widget.call` returns a deferred resolved with the value
1342 returned by the model's method as first argument.
1347 The previous section used a ``context`` argument which was not explained in
1350 model.call("my_method", {context: new instance.web.CompoundContext()})
1352 The context is like a "magic" argument that the web client will always give to
1353 the server when calling a method. The context is a dictionary containing
1354 multiple keys. One of the most important key is the language of the user, used
1355 by the server to translate all the messages of the application. Another one is
1356 the time zone of the user, used to compute correctly dates and times if Odoo
1357 is used by people in different countries.
1359 The ``argument`` is necessary in all methods, because if we forget it bad
1360 things could happen (like the application not being translated
1361 correctly). That's why, when you call a model's method, you should always give
1362 it to that argument. The solution to achieve that is to use
1363 :class:`openerp.web.CompoundContext`.
1365 :class:`~openerp.web.CompoundContext` is a class used to pass the user's
1366 context (with language, time zone, etc...) to the server as well as adding new
1367 keys to the context (some models' methods use arbitrary keys added to the
1368 context). It is created by giving to its constructor any number of
1369 dictionaries or other :class:`~openerp.web.CompoundContext` instances. It will
1370 merge all those contexts before sending them to the server.
1372 .. code-block:: javascript
1374 model.call("my_method", {context: new instance.web.CompoundContext({'new_key': 'key_value'})})
1376 .. code-block:: python
1379 def my_method(self):
1380 print self.env.context
1381 // will print: {'lang': 'en_US', 'new_key': 'key_value', 'tz': 'Europe/Brussels', 'uid': 1}
1383 You can see the dictionary in the argument ``context`` contains some keys that
1384 are related to the configuration of the current user in Odoo plus the
1385 ``new_key`` key that was added when instantiating
1386 :class:`~openerp.web.CompoundContext`.
1391 While :func:`~openerp.Model.call` is sufficient for any interaction with Odoo
1392 models, Odoo Web provides a helper for simpler and clearer querying of models
1393 (fetching of records based on various conditions):
1394 :func:`~openerp.Model.query` which acts as a shortcut for the common
1395 combination of :py:meth:`~openerp.models.Model.search` and
1396 ::py:meth:`~openerp.models.Model.read`. It provides a clearer syntax to search
1399 model.query(['name', 'login', 'user_email', 'signature'])
1400 .filter([['active', '=', true], ['company_id', '=', main_company]])
1402 .all().then(function (users) {
1403 // do work with users records
1408 model.call('search', [['active', '=', true], ['company_id', '=', main_company]], {limit: 15})
1409 .then(function (ids) {
1410 return model.call('read', [ids, ['name', 'login', 'user_email', 'signature']]);
1412 .then(function (users) {
1413 // do work with users records
1416 * :func:`~openerp.web.Model.query` takes an optional list of fields as
1417 parameter (if no field is provided, all fields of the model are fetched). It
1418 returns a :class:`openerp.web.Query` which can be further customized before
1420 * :class:`~openerp.web.Query` represents the query being built. It is
1421 immutable, methods to customize the query actually return a modified copy,
1422 so it's possible to use the original and the new version side-by-side. See
1423 :class:`~openerp.web.Query` for its customization options.
1425 When the query is set up as desired, simply call
1426 :func:`~openerp.web.Query.all` to perform the actual query and return a
1427 deferred to its result. The result is the same as
1428 :py:meth:`~openerp.models.Model.read`'s, an array of dictionaries where each
1429 dictionary is a requested record, with each requested field a dictionary key.
1434 .. exercise:: Message of the Day
1436 Create a ``MessageOfTheDay`` widget displaying the last record of the
1437 ``oepetstore.message_of_the_day`` model. The widget should fetch its
1438 record as soon as it is displayed.
1440 Display the widget in the Pet Store home page.
1444 .. code-block:: javascript
1446 openerp.oepetstore = function(instance, local) {
1447 var _t = instance.web._t,
1448 _lt = instance.web._lt;
1449 var QWeb = instance.web.qweb;
1451 local.HomePage = instance.Widget.extend({
1452 template: "HomePage",
1454 return new local.MessageOfTheDay(this).appendTo(this.$el);
1458 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1460 local.MessageOfTheDay = instance.Widget.extend({
1461 template: "MessageOfTheDay",
1464 return new instance.web.Model("oepetstore.message_of_the_day")
1466 .order_by('-create_date', '-id')
1468 .then(function(result) {
1469 self.$(".oe_mywidget_message_of_the_day").text(result.message);
1478 <?xml version="1.0" encoding="UTF-8"?>
1479 <templates xml:space="preserve">
1480 <t t-name="HomePage">
1481 <div class="oe_petstore_homepage">
1484 <t t-name="MessageOfTheDay">
1485 <div class="oe_petstore_motd">
1486 <p class="oe_mywidget_message_of_the_day"></p>
1497 background-color: #F0EEEE;
1500 .. exercise:: Pet Toys List
1502 Create a ``PetToysList`` widget displaying 5 toys (using their name and
1505 The pet toys are not stored in a new model, instead they're stored in
1506 ``product.product`` using a special category *Pet Toys*. You can see the
1507 pre-generated toys and add new ones by going to
1508 :menuselection:`Pet Store --> Pet Store --> Pet Toys`. You will probably
1509 need to explore ``product.product`` in order to create the right domain to
1510 select just pet toys.
1512 In Odoo, images are generally stored in regular fields encoded as
1513 base64_, HTML supports displaying images straight from base64 with
1514 :samp:`<img src="data:{mime_type};base64,{base64_image_data}"/>`
1516 The ``PetToysList`` widget should be displayed on the home page on the
1517 right of the ``MessageOfTheDay`` widget. You will need to make some layout
1518 with CSS to achieve this.
1522 .. code-block:: javascript
1524 openerp.oepetstore = function(instance, local) {
1525 var _t = instance.web._t,
1526 _lt = instance.web._lt;
1527 var QWeb = instance.web.qweb;
1529 local.HomePage = instance.Widget.extend({
1530 template: "HomePage",
1531 start: function () {
1533 new local.PetToysList(this).appendTo(this.$('.oe_petstore_homepage_left')),
1534 new local.MessageOfTheDay(this).appendTo(this.$('.oe_petstore_homepage_right'))
1538 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1540 local.MessageOfTheDay = instance.Widget.extend({
1541 template: 'MessageOfTheDay',
1542 start: function () {
1544 return new instance.web.Model('oepetstore.message_of_the_day')
1546 .order_by('-create_date', '-id')
1548 .then(function (result) {
1549 self.$(".oe_mywidget_message_of_the_day").text(result.message);
1554 local.PetToysList = instance.Widget.extend({
1555 template: 'PetToysList',
1556 start: function () {
1558 return new instance.web.Model('product.product')
1559 .query(['name', 'image'])
1560 .filter([['categ_id.name', '=', "Pet Toys"]])
1563 .then(function (result) {
1564 self.$el.append(QWeb.render('PetToys', {item: item}));
1572 <?xml version="1.0" encoding="UTF-8"?>
1574 <templates xml:space="preserve">
1575 <t t-name="HomePage">
1576 <div class="oe_petstore_homepage">
1577 <div class="oe_petstore_homepage_left"></div>
1578 <div class="oe_petstore_homepage_right"></div>
1581 <t t-name="MessageofTheDay">
1582 <div class="oe_petstore_motd">
1583 <p class="oe_mywidget_message_of_the_day"></p>
1586 <t t-name="PetToysList">
1587 <div class="oe_petstore_pettoyslist">
1591 <div class="oe_petstore_pettoy">
1592 <p><t t-esc="item.name"/></p>
1593 <p><img t-att-src="'data:image/jpg;base64,'+item.image"/></p>
1600 .oe_petstore_homepage {
1604 .oe_petstore_homepage_left {
1605 display: table-cell;
1609 .oe_petstore_homepage_right {
1610 display: table-cell;
1618 background-color: #F0EEEE;
1621 .oe_petstore_pettoyslist {
1625 .oe_petstore_pettoy {
1629 background-color: #F0EEEE;
1633 Existing web components
1634 =======================
1639 In Odoo, many operations start from an :ref:`action <reference/actions>`:
1640 opening a menu item (to a view), printing a report, ...
1642 Actions are pieces of data describing how a client should react to the
1643 activation of a piece of content. Actions can be stored (and read through a
1644 model) or they can be generated on-the fly (locally to the client by
1645 javascript code, or remotely by a method of a model).
1647 In Odoo Web, the component responsible for handling and reacting to these
1648 actions is the *Action Manager*.
1650 Using the Action Manager
1651 ''''''''''''''''''''''''
1653 The action manager can be invoked explicitly from javascript code by creating
1654 a dictionary describing :ref:`an action <reference/actions>` of the right
1655 type, and calling an action manager instance with it.
1657 :func:`~openerp.Widget.do_action` is a shortcut of :class:`~openerp.Widget`
1658 looking up the "current" action manager and executing the action::
1660 instance.web.TestWidget = instance.Widget.extend({
1661 dispatch_to_new_action: function() {
1663 type: 'ir.actions.act_window',
1664 res_model: "product.product",
1666 views: [[false, 'form']],
1673 The most common action ``type`` is ``ir.actions.act_window`` which provides
1674 views to a model (displays a model in various manners), its most common
1678 The model to display in views
1679 ``res_id`` (optional)
1680 For form views, a preselected record in ``res_model``
1682 Lists the views available through the action. A list of
1683 ``[view_id, view_type]``, ``view_id`` can either be the database identifier
1684 of a view of the right type, or ``false`` to use the view by default for
1685 the specified type. View types can not be present multiple times. The action
1686 will open the first view of the list by default.
1688 Either ``current`` (the default) which replaces the "content" section of the
1689 web client by the action, or ``new`` to open the action in a dialog box.
1691 Additional context data to use within the action.
1693 .. exercise:: Jump to Product
1695 Modify the ``PetToysList`` component so clicking on a toy replaces the
1696 homepage by the toy's form view.
1700 .. code-block:: javascript
1702 local.PetToysList = instance.Widget.extend({
1703 template: 'PetToysList',
1705 'click .oe_petstore_pettoy': 'selected_item',
1707 start: function () {
1709 return new instance.web.Model('product.product')
1710 .query(['name', 'image'])
1711 .filter([['categ_id.name', '=', "Pet Toys"]])
1714 .then(function (results) {
1715 _(results).each(function (item) {
1716 self.$el.append(QWeb.render('PetToy', {item: item}));
1720 selected_item: function (event) {
1722 type: 'ir.actions.act_window',
1723 res_model: 'product.product',
1724 res_id: $(event.currentTarget).data('id'),
1725 views: [[false, 'form']],
1733 <div class="oe_petstore_pettoy" t-att-data-id="item.id">
1734 <p><t t-esc="item.name"/></p>
1735 <p><img t-attf-src="data:image/jpg;base64,#{item.image}"/></p>
1742 Throughout this guide, we used a simple ``HomePage`` widget which the web
1743 client automatically starts when we select the right menu item. But how did
1744 the Odoo web know to start this widget? Because the widget is registered as
1747 A client action is (as its name implies) an action type defined almost
1748 entirely in the client, in javascript for Odoo web. The server simply sends
1749 an action tag (an arbitrary name), and optionally adds a few parameters, but
1750 beyond that *everything* is handled by custom client code.
1752 Our widget is registered as the handler for the client action through this::
1754 instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
1757 ``instance.web.client_actions`` is a :class:`~openerp.web.Registry` in which
1758 the action manager looks up client action handlers when it needs to execute
1759 one. The first parameter of :class:`~openerp.web.Registry.add` is the name
1760 (tag) of the client action, and the second parameter is the path to the widget
1761 from the Odoo web client root.
1763 When a client action must be executed, the action manager looks up its tag
1764 in the registry, walks the specified path and displays the widget it finds at
1767 .. note:: a client action handler can also be a regular function, in whch case
1768 it'll be called and its result (if any) will be interpreted as the
1769 next action to execute.
1771 On the server side, we had simply defined an ``ir.actions.client`` action:
1775 <record id="action_home_page" model="ir.actions.client">
1776 <field name="tag">petstore.homepage</field>
1779 and a menu opening the action:
1783 <menuitem id="home_page_petstore_menu" parent="petstore_menu"
1784 name="Home Page" action="action_home_page"/>
1786 Architecture of the Views
1787 -------------------------
1789 Much of Odoo web's usefulness (and complexity) resides in views. Each view
1790 type is a way of displaying a model in the client.
1795 When an ``ActionManager`` instance receive an action of type
1796 ``ir.actions.act_window``, it delegates the synchronization and handling of
1797 the views themselves to a *view manager*, which will then set up one or
1798 multiple views depending on the original action's requirements:
1800 .. image:: web/viewarchitecture.*
1807 Most :ref:`Odoo views <reference/views>` are implemented through a subclass
1808 of :class:`openerp.web.View` which provides a bit of generic basic structure
1809 for handling events and displaying model information.
1811 The *search view* is considered a view type by the main Odoo framework, but
1812 handled separately by the web client (as it's a more permanent fixture and
1813 can interact with other views, which regular views don't do).
1815 A view is responsible for loading its own description XML (using
1816 :py:class:`~openerp.models.Model.fields_view_get`) and any other data source
1817 it needs. To that purpose, views are provided with an optional view
1818 identifier set as the :attr:`~openerp.web.View.view_id` attribute.
1820 Views are also provided with a :class:`~openerp.web.DataSet` instance which
1821 holds most necessary model information (the model name and possibly various
1824 Views may also want to handle search queries by overriding
1825 :func:`~openerp.web.View.do_search`, and updating their
1826 :class:`~openerp.web.DataSet` as necessary.
1828 The Form View Fields
1829 --------------------
1831 A common Odoo web need is the extension of the form view to add new ways of
1832 displaying form fields.
1834 All built-in fields have a default display implementation, creating a new
1835 form widget may be necessary to correctly interact with a new field type
1836 (e.g. a :term:`GIS` field) or to provide new representations and ways to
1837 interact with existing field types (e.g. validate
1838 :py:class:`~openerp.fields.Char` fields which should contain email addresses
1839 and display them as email links).
1841 To explicitly specify which form widget should be used to display a field,
1842 simply use the ``widget`` attribute in the view's XML description:
1846 <field name="contact_mail" widget="email"/>
1850 * the same widget is used in both "view" (read-only) and "edition" modes
1851 of a form view, it's not possible to use a widget in one and an other
1853 * and a given field (name) can not be used multiple times in the same form
1854 * a widget may ignore the current mode of the form view and remain the
1855 same in both view and edition
1857 .. todo:: most of this should probably move to an advanced form view guide
1859 Fields are instantiated by the form view after it has read its XML description
1860 and constructed the corresponding HTML representing that description. After
1861 that, the form view will communicate with the field objects using some
1862 methods. Theses methods are defined by the ``FieldInterface``
1863 interface. Almost all fields inherit the ``AbstractField`` abstract
1864 class. That class defines some default mechanisms that need to be implemented
1867 Here are some of the responsibilities of a field class:
1869 * The field class must display and allow the user to edit the value of the field.
1870 * It must correctly implement the 3 field attributes available in all fields
1871 of Odoo. The ``AbstractField`` class already implements an algorithm that
1872 dynamically calculates the value of these attributes (they can change at any
1873 moment because their value change according to the value of other
1874 fields). Their values are stored in *Widget Properties* (the widget
1875 properties were explained earlier in this guide). It is the responsibility
1876 of each field class to check these widget properties and dynamically adapt
1877 depending of their values. Here is a description of each of these
1880 * ``required``: The field must have a value before saving. If ``required``
1881 is ``true`` and the field doesn't have a value, the method
1882 ``is_valid()`` of the field must return ``false``.
1883 * ``invisible``: When this is ``true``, the field must be invisible. The
1884 ``AbstractField`` class already has a basic implementation of this
1885 behavior that fits most fields.
1886 * ``readonly``: When ``true``, the field must not be editable by the
1887 user. Most fields in Odoo have a completely different behavior depending
1888 on the value of ``readonly``. As example, the ``FieldChar`` displays an
1889 HTML ``<input>`` when it is editable and simply displays the text when
1890 it is read-only. This also means it has much more code it would need to
1891 implement only one behavior, but this is necessary to ensure a good user
1894 * Fields have two methods, ``set_value()`` and ``get_value()``, which are
1895 called by the form view to give it the value to display and get back the new
1896 value entered by the user. These methods must be able to handle the value as
1897 given by the Odoo server when a ``read()`` is performed on a model and give
1898 back a valid value for a ``write()``. Remember that the JavaScript/Python
1899 data types used to represent the values given by ``read()`` and given to
1900 ``write()`` is not necessarily the same in Odoo. As example, when you read a
1901 many2one, it is always a tuple whose first value is the id of the pointed
1902 record and the second one is the name get (ie: ``(15, "Agrolait")``). But
1903 when you write a many2one it must be a single integer, not a tuple
1904 anymore. ``AbstractField`` has a default implementation of these methods
1905 that works well for simple data type and set a widget property named
1908 Please note that, to better understand how to implement fields, you are
1909 strongly encouraged to look at the definition of the ``FieldInterface``
1910 interface and the ``AbstractField`` class directly in the code of the Odoo web
1913 Creating a New Type of Field
1914 ''''''''''''''''''''''''''''
1916 In this part we will explain how to create a new type of field. The example
1917 here will be to re-implement the ``FieldChar`` class and explain progressively
1920 Simple Read-Only Field
1921 """"""""""""""""""""""
1923 Here is a first implementation that will only be able to display a text. The
1924 user will not be able to modify the content of the field.
1926 .. code-block:: javascript
1928 local.FieldChar2 = instance.web.form.AbstractField.extend({
1930 this._super.apply(this, arguments);
1931 this.set("value", "");
1933 render_value: function() {
1934 this.$el.text(this.get("value"));
1938 instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');
1940 In this example, we declare a class named ``FieldChar2`` inheriting from
1941 ``AbstractField``. We also register this class in the registry
1942 ``instance.web.form.widgets`` under the key ``char2``. That will allow us to
1943 use this new field in any form view by specifying ``widget="char2"`` in the
1944 ``<field/>`` tag in the XML declaration of the view.
1946 In this example, we define a single method: ``render_value()``. All it does is
1947 display the widget property ``value``. Those are two tools defined by the
1948 ``AbstractField`` class. As explained before, the form view will call the
1949 method ``set_value()`` of the field to set the value to display. This method
1950 already has a default implementation in ``AbstractField`` which simply sets
1951 the widget property ``value``. ``AbstractField`` also watch the
1952 ``change:value`` event on itself and calls the ``render_value()`` when it
1953 occurs. So, ``render_value()`` is a convenience method to implement in child
1954 classes to perform some operation each time the value of the field changes.
1956 In the ``init()`` method, we also define the default value of the field if
1957 none is specified by the form view (here we assume the default value of a
1958 ``char`` field should be an empty string).
1963 Fields that only display their content and don't give the possibility to the
1964 user to modify it can be useful, but most fields in Odoo allow edition
1965 too. This makes the field classes more complicated, mostly because fields are
1966 supposed to handle both and editable and non-editable mode, those modes are
1967 often completely different (for design and usability purpose) and the fields
1968 must be able to switch from one mode to another at any moment.
1970 To know in which mode the current field should be, the ``AbstractField`` class
1971 sets a widget property named ``effective_readonly``. The field should watch
1972 the changes in that widget property and display the correct mode
1973 accordingly. Example::
1975 local.FieldChar2 = instance.web.form.AbstractField.extend({
1977 this._super.apply(this, arguments);
1978 this.set("value", "");
1981 this.on("change:effective_readonly", this, function() {
1982 this.display_field();
1983 this.render_value();
1985 this.display_field();
1986 return this._super();
1988 display_field: function() {
1990 this.$el.html(QWeb.render("FieldChar2", {widget: this}));
1991 if (! this.get("effective_readonly")) {
1992 this.$("input").change(function() {
1993 self.internal_set_value(self.$("input").val());
1997 render_value: function() {
1998 if (this.get("effective_readonly")) {
1999 this.$el.text(this.get("value"));
2001 this.$("input").val(this.get("value"));
2006 instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');
2010 <t t-name="FieldChar2">
2011 <div class="oe_field_char2">
2012 <t t-if="! widget.get('effective_readonly')">
2013 <input type="text"></input>
2018 In the ``start()`` method (which is called right after a widget has been
2019 appended to the DOM), we bind on the event ``change:effective_readonly``. That
2020 will allow use to redisplay the field each time the widget property
2021 ``effective_readonly`` changes. This event handler will call
2022 ``display_field()``, which is also called directly in ``start()``. This
2023 ``display_field()`` was created specifically for this field, it's not a method
2024 defined in ``AbstractField`` or any other class. This is the method we will
2025 use to display the content of the field depending we are in read-only mode or
2028 From now on the conception of this field is quite typical, except there is a
2029 lot of verifications to know the state of the ``effective_readonly`` property:
2031 * In the QWeb template used to display the content of the widget, it displays
2032 an ``<input type="text" />`` if we are in read-write mode and nothing in
2033 particular in read-only mode.
2034 * In the ``display_field()`` method, we have to bind on the ``change`` event
2035 of the ``<input type="text" />`` to know when the user has changed the
2036 value. When it happens, we call the ``internal_set_value()`` method with the
2037 new value of the field. This is a convenience method provided by the
2038 ``AbstractField`` class. That method will set a new value in the ``value``
2039 property but will not trigger a call to ``render_value()`` (which is not
2040 necessary since the ``<input type="text" />`` already contains the correct
2042 * In ``render_value()``, we use a completely different code to display the
2043 value of the field depending if we are in read-only or in read-write mode.
2045 .. exercise:: Create a Color Field
2047 Create a ``FieldColor`` class. The value of this field should be a string
2048 containing a color code like those used in CSS (example: ``#FF0000`` for
2049 red). In read-only mode, this color field should display a little block
2050 whose color corresponds to the value of the field. In read-write mode, you
2051 should display an ``<input type="color" />``. That type of ``<input />``
2052 is an HTML5 component that doesn't work in all browsers but works well in
2053 Google Chrome. So it's OK to use as an exercise.
2055 You can use that widget in the form view of the ``message_of_the_day``
2056 model for its field named ``color``. As a bonus, you can change the
2057 ``MessageOfTheDay`` widget created in the previous part of this guide to
2058 display the message of the day with the background color indicated in the
2063 .. code-block:: javascript
2065 local.FieldColor = instance.web.form.AbstractField.extend({
2067 'change input': function (e) {
2068 if (!this.get('effective_readonly')) {
2069 this.internal_set_value($(e.currentTarget).val());
2074 this._super.apply(this, arguments);
2075 this.set("value", "");
2078 this.on("change:effective_readonly", this, function() {
2079 this.display_field();
2080 this.render_value();
2082 this.display_field();
2083 return this._super();
2085 display_field: function() {
2086 this.$el.html(QWeb.render("FieldColor", {widget: this}));
2088 render_value: function() {
2089 if (this.get("effective_readonly")) {
2090 this.$(".oe_field_color_content").css("background-color", this.get("value") || "#FFFFFF");
2092 this.$("input").val(this.get("value") || "#FFFFFF");
2096 instance.web.form.widgets.add('color', 'instance.oepetstore.FieldColor');
2100 <t t-name="FieldColor">
2101 <div class="oe_field_color">
2102 <t t-if="widget.get('effective_readonly')">
2103 <div class="oe_field_color_content" />
2105 <t t-if="! widget.get('effective_readonly')">
2106 <input type="color"></input>
2113 .oe_field_color_content {
2116 border: 1px solid black;
2119 The Form View Custom Widgets
2120 ----------------------------
2122 Form fields are used to edit a single field, and are intrinsically linked to
2123 a field. Because this may be limiting, it is also possible to create
2124 *form widgets* which are not so restricted and have less ties to a specific
2127 Custom form widgets can be added to a form view through the ``widget`` tag:
2131 <widget type="xxx" />
2133 This type of widget will simply be created by the form view during the
2134 creation of the HTML according to the XML definition. They have properties in
2135 common with the fields (like the ``effective_readonly`` property) but they are
2136 not assigned a precise field. And so they don't have methods like
2137 ``get_value()`` and ``set_value()``. They must inherit from the ``FormWidget``
2140 Form widgets can interact with form fields by listening for their changes and
2141 fetching or altering their values. They can access form fields through
2142 their :attr:`~openerp.web.form.FormWidget.field_manager` attribute::
2144 local.WidgetMultiplication = instance.web.form.FormWidget.extend({
2147 this.field_manager.on("field_changed:integer_a", this, this.display_result);
2148 this.field_manager.on("field_changed:integer_b", this, this.display_result);
2149 this.display_result();
2151 display_result: function() {
2152 var result = this.field_manager.get_field_value("integer_a") *
2153 this.field_manager.get_field_value("integer_b");
2154 this.$el.text("a*b = " + result);
2158 instance.web.form.custom_widgets.add('multiplication', 'instance.oepetstore.WidgetMultiplication');
2160 :attr:`~openerp.web.form.FormWidget` is generally the
2161 :class:`~openerp.web.form.FormView` itself, but features used from it should
2162 be limited to those defined by :class:`~openerp.web.form.FieldManagerMixin`,
2163 the most useful being:
2165 * :func:`~openerp.web.form.FieldManagerMixin.get_field_value(field_name)`
2166 which returns the value of a field.
2167 * :func:`~openerp.web.form.FieldManagerMixin.set_values(values)` sets multiple
2168 field values, takes a mapping of ``{field_name: value_to_set}``
2169 * An event :samp:`field_changed:{field_name}` is triggered any time the value
2170 of the field called ``field_name`` is changed
2172 .. exercise:: Show Coordinates on Google Map
2174 Add two fields to ``product.product`` storing a latitude and a longitude,
2175 then create a new form widget to display the latitude and longitude of
2176 a product's origin on a map
2178 To display the map, use Google Map's embedding:
2180 .. code-block:: html
2182 <iframe width="400" height="300" src="https://maps.google.com/?ie=UTF8&ll=XXX,YYY&output=embed">
2185 where ``XXX`` should be replaced by the latitude and ``YYY`` by the
2188 Display the two position fields and a map widget using them in a new
2189 notebook page of the product's form view.
2193 .. code-block:: javascript
2195 local.WidgetCoordinates = instance.web.form.FormWidget.extend({
2198 this.field_manager.on("field_changed:provider_latitude", this, this.display_map);
2199 this.field_manager.on("field_changed:provider_longitude", this, this.display_map);
2202 display_map: function() {
2203 this.$el.html(QWeb.render("WidgetCoordinates", {
2204 "latitude": this.field_manager.get_field_value("provider_latitude") || 0,
2205 "longitude": this.field_manager.get_field_value("provider_longitude") || 0,
2210 instance.web.form.custom_widgets.add('coordinates', 'local.WidgetCoordinates');
2214 <t t-name="WidgetCoordinates">
2215 <iframe width="400" height="300"
2216 t-attf-src="https://maps.google.com/?ie=UTF8&ll={{latitude}},{{longitude}}&output=embed">
2220 .. exercise:: Get the Current Coordinate
2222 Add a button resetting the product's coordinates to the location of the
2223 user, you can get these coordinates using the
2224 `javascript geolocation API`_.
2226 Now we would like to display an additional button to automatically set the
2227 coordinates to the location of the current user.
2229 To get the coordinates of the user, an easy way is to use the geolocation
2230 JavaScript API. `See the online documentation to know how to use it`_.
2232 .. _See the online documentation to know how to use it: http://www.w3schools.com/html/html5_geolocation.asp
2234 Please also note that it wouldn't be very logical to allow the user to
2235 click on that button when the form view is in read-only mode. So, this
2236 custom widget should handle correctly the ``effective_readonly`` property
2237 just like any field. One way to do this would be to make the button
2238 disappear when ``effective_readonly`` is true.
2242 .. code-block:: javascript
2244 local.WidgetCoordinates = instance.web.form.FormWidget.extend({
2246 'click button': function () {
2247 navigator.geolocation.getCurrentPosition(
2248 this.proxy('received_position'));
2252 var sup = this._super();
2253 this.field_manager.on("field_changed:provider_latitude", this, this.display_map);
2254 this.field_manager.on("field_changed:provider_longitude", this, this.display_map);
2255 this.on("change:effective_readonly", this, this.display_map);
2259 display_map: function() {
2260 this.$el.html(QWeb.render("WidgetCoordinates", {
2261 "latitude": this.field_manager.get_field_value("provider_latitude") || 0,
2262 "longitude": this.field_manager.get_field_value("provider_longitude") || 0,
2264 this.$("button").toggle(! this.get("effective_readonly"));
2266 received_position: function(obj) {
2267 this.field_manager.set_values({
2268 "provider_latitude": obj.coords.latitude,
2269 "provider_longitude": obj.coords.longitude,
2274 instance.web.form.custom_widgets.add('coordinates', 'local.WidgetCoordinates');
2278 <t t-name="WidgetCoordinates">
2279 <iframe width="400" height="300"
2280 t-att-src="https://maps.google.com/?ie=UTF8&ll={{latitude}},{{longitude}}&output=embed">
2282 <button>Get My Current Coordinate</button>
2285 .. [#classes] as a separate concept from instances. In many languages classes
2286 are full-fledged objects and themselves instance (of
2287 metaclasses) but there remains two fairly separate hierarchies
2288 between classes and instances
2289 .. [#dombugs] as well as papering over cross-browser differences, although
2290 this has become less necessary over time
2292 .. _jQuery: http://jquery.org
2293 .. _Underscore.js: http://underscorejs.org
2294 .. _git: http://git-scm.com
2295 .. _CSS: http://www.w3.org/Style/CSS/Overview.en.html
2296 .. _Simple JavaScript Inheritance:
2297 http://ejohn.org/blog/simple-javascript-inheritance/
2298 .. _W3C DOM: http://www.w3.org/TR/DOM-Level-3-Core/
2299 .. _Qt: http://qt-project.org
2300 .. _Cocoa: https://developer.apple.com/technologies/mac/cocoa.html
2301 .. _GTK: http://www.gtk.org
2302 .. _template engine: http://en.wikipedia.org/wiki/Web_template_system
2303 .. _cross-site scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
2304 .. _jQuery event object: http://api.jquery.com/category/events/event-object/
2305 .. _$.ajax: http://api.jquery.com/jquery.ajax/
2306 .. _base64: http://en.wikipedia.org/wiki/Base64
2307 .. _javascript geolocation API:
2308 http://diveintohtml5.info/geolocation.html
2309 .. _PostgreSQL: http://en.wikipedia.org/wiki/PostgreSQL
2310 .. _positional arguments:
2311 .. _keyword arguments:
2312 https://docs.python.org/2/glossary.html#term-argument