Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] developer: rewrite a bunch of outdated info in JS reference #9180

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,344 changes: 275 additions & 1,069 deletions content/developer/reference/frontend/javascript_reference.rst
Original file line number Diff line number Diff line change
@@ -29,50 +29,49 @@ The Javascript framework is designed to work with three main use cases:
specialized single page application.

Some javascript code is common to these three use cases, and is bundled together
(see below in the assets section). This document will focus mostly on the web
client design.
(see below in the assets section). This document will focus mostly on the architecture
of the web client.

Web client
==========

Single Page Application
-----------------------

In short, the *webClient*, instance of *WebClient* is the root component of the
whole user interface. Its responsibility is to orchestrate all various
subcomponents, and to provide services, such as rpcs, local storage and more.

In runtime, the web client is a single page application. It does not need to
request a full page from the server each time the user perform an action. Instead,
it only requests what it needs, and then replaces/updates the view. Also, it
manages the url: it is kept in sync with the web client state.

It means that while a user is working on Odoo, the web client class (and the
action manager) actually creates and destroys many sub components. The state is
highly dynamic, and each widget could be destroyed at any time.
The web client is a single-page application: instead of
requesting a full page from the server each time the user performs an action,
it only loads what is needed to update the user interface (UI) as a result of that
action. While doing this, it also takes care of updating information in the URL,
so that, in most cases, refreshing the page or closing the browser and opening it
again shows you the same thing.

Overview of web client JS code
------------------------------

Here, we give a very quick overview on the web client code, in
the *web/static/src/js* addon. Note that it is deliberately not exhaustive.
We only cover the most important files/folders.

- *boot.js*: this is the file that defines the module system. It needs to be
loaded first.
- *core/*: this is a collection of lower level building blocks. Notably, it
contains the class system, the widget system, concurrency utilities, and many
other class/functions.
- *chrome/*: in this folder, we have most large widgets which make up most of
the user interface.
- *chrome/abstract_web_client.js* and *chrome/web_client.js*: together, these
files define the WebClient widget, which is the root widget for the web client.
- *chrome/action_manager.js*: this is the code that will convert an action into
a widget (for example a kanban or a form view)
- *chrome/search_X.js* all these files define the search view (it is not a view
Here, we give a very quick overview of the web client code, in the :file:`web` addon.
The paths will be described relative to :file:`web/static/src`.
The following description is deliberately not exhaustive; the goal is only to
give the reader a bird's eye view of the architecture.

- :file:`module_loader.js`: this is the file that defines the Odoo javascript module
system. It needs to be loaded before any other JS module.
- :file:`core/`: this folder contains code that forms the lowest level of the javascript
framework and that can be used in the web client as well as the website, portal,
and point of sale application.
- :file:`weblient/`: this folder contains files that are specific to the web client and
cannot be used in the website or point of sale, such as the action manager and the
action service.
- :file:`webclient/webclient.js`: this is the webclient component proper. It is mostly
a wrapper for the action container and the navbar, and does a few things that
are required upon starting the application, such as loading the state of the url.
- :file:`webclient/actions/`: this folder contains the code responsible for displaying
and switching between actions.
- :file:`views/`: this folder contains the code for the view infrastructure, as well
as most of the views (some types of views are added by other addons).
- :file:`views/fields/`: contains the definition of the various field components, as well
as some utilities used by multiple fields.
- :file:`search/` all these files define the search view (it is not a view
in the point of view of the web client, only from the server point of view)
- *fields*: all main view field widgets are defined here
- *views*: this is where the views are located


What to do if a file is not loaded/updated
@@ -81,925 +80,289 @@ What to do if a file is not loaded/updated
There are many different reasons why a file may not be properly loaded. Here
are a few things you can try to solve the issue:

- once the server is started, it does not know if an asset file has been
modified. So, you can simply restart the server to regenerate the assets.
- check the console (in the dev tools, usually opened with F12) to make sure
there are no obvious errors
- try to add a `console.log()` at the beginning of your file (before any module
definition), so you can see if a file has been loaded or not
- when in any debug mode, there is an option in the debug manager menu (bug icon)
to force the server to update its assets files.
- use the *debug=assets* mode. This will actually bypass the asset bundles (note
that it does not actually solve the issue. The server still uses outdated bundles)
- finally, the most convenient way to do it, for a developer, is to start the
server with the *--dev=all* option. This activates the file watcher options,
which will automatically invalidate assets when necessary. Note that it does
not work very well if the OS is Windows.
- remember to refresh your page!
- or maybe to save your code file...
- Make sure you saved your file; forgetting to do that happens to the best of us.
- Take a look at the console (in the dev tools, usually opened with F12) and check
for errors.
- Try adding a `console.log()` at the beginning of your file so you can see if
a file has been loaded or not. If it is not loaded, if may not be in the proper
assets bundle, or the asset bundle may not be up to date.
- Depending on your settings, the server may not regenerate the assets bundles
after a file has been modified; there are a few options to solve this:

.. note::
Once an asset file has been recreated, you need to refresh the page, to reload
the proper files (if that does not work, the files may be cached).
- restarting the server will force it to check if the asset bundle is up to
date the next time it is requested
- in debug mode, there is an option in the debug menu (:icon:`fa-bug` button in the navbar)
to force the server to regenerate the assets bundle on the fly without restarting.
- starting the server with the `--dev=xml` option will force the server to check
if an asset bundle is up to date every time it is requested. We advise you to use
this option when actively developing, but not in production.

- Make sure you refresh your page after changing the code. Odoo currently does not
have any hot module reloading mechanism.


Loading Javascript Code
=======================

Large applications are usually broken up into smaller files, that need to be
connected together. Some file may need to use some part of code defined in
connected together. Some file may need to use code defined in
another file. There are two ways of sharing code between files:

- use the global scope (the *window* object) to write/read references to some
- using the global scope (the *window* object) to read/write references to some
objects or functions,

- use a module system that will provide a way for each modules to export or import
- using a module system that will provide a way for each modules to export or import
values, and will make sure that they are loaded in a proper order.

While it's possible to work in the global scope, this has a number of issues:

It is difficult to ensure that implementation details are not exposed by work done in the global scope directly.

- Dependencies are implicit, leading to fragile and unreliable load ordering.

- The lack of insight into execution means it's impossible to use various optimisations (e.g. deferred and asynchronous
loading).

- Module systems help resolve these issues: because modules specify their dependencies the module system can ensure the
necessary order of loading is respected, and because modules can precisely specify their exports it is less likely
that they will leak implementation details.

For most Odoo code, we want to use a module system. Because of the way assets
work in Odoo (and in particular, the fact that each installed odoo addon can
modify the list of files contained in a bundle), Odoo has to resolve modules
browser side. To do that, Odoo provides a small module system described just
below (see :ref:`frontend/modules/odoo_module`).

However, Odoo also provides support for native javascript modules (see
:ref:`frontend/modules/native_js`). These modules
will simply be translated by the server into odoo modules. It is encouraged to
write all javascript code as a native module, for a better IDE integration. In
the future, the Odoo module system should be considered an implementation detail,
not the primary way to write javascript code.

.. note::
Native javascript modules are the primary way to define javascript code.

Class System
============

Odoo was developed before ECMAScript 6 classes were available. In Ecmascript 5,
the standard way to define a class is to define a function and to add methods
on its prototype object. This is fine, but it is slightly complex when we want
to use inheritance, mixins.

For these reasons, Odoo decided to use its own class system, inspired by John
Resig. The base Class is located in *web.Class*, in the file *class.js*.


.. note ::
Note that the custom class system should be avoided for creating new code. It
will be deprecated at some point, and then removed. New classes should use
the standard ES6 class system.
Creating a subclass
-------------------
- It is difficult to ensure that implementation details are not exposed: function
declarations in the global scope are accessible to all other code.

Let us discuss how classes are created. The main mechanism is to use the
*extend* method (this is more or less the equivalent of *extend* in ES6 classes).
- There is a single namespace, creating great potential for naming conflicts.

.. code-block:: javascript
var Class = require('web.Class');
var Animal = Class.extend({
init: function () {
this.x = 0;
this.hunger = 0;
},
move: function () {
this.x = this.x + 1;
this.hunger = this.hunger + 1;
},
eat: function () {
this.hunger = 0;
},
});
In this example, the *init* function is the constructor. It will be called when
an instance is created. Making an instance is done by using the *new* keyword.

Inheritance
-----------

It is convenient to be able to inherit an existing class. This is simply done
by using the *extend* method on the superclass. When a method is called, the
framework will secretly rebind a special method: *_super* to the currently
called method. This allows us to use *this._super* whenever we need to call a
parent method.

.. code-block:: javascript
var Animal = require('web.Animal');
var Dog = Animal.extend({
move: function () {
this.bark();
this._super.apply(this, arguments);
},
bark: function () {
console.log('woof');
},
});
- Dependencies are implicit: if a piece of code depends on another, the order in
which they are loaded is important, but difficult to guarantee.

var dog = new Dog();
dog.move()
Using a module system helps resolve these issues: because modules specify their
dependencies, the module system can load them in the proper order or emit an error
if dependencies are missing or circular. Modules also form their own namespace,
and can choose what to export, preventing exposure of implementation detail and
naming collisions.

Mixins
------
While we could use ECMAScript (ES) modules directly, there are a number of
disadvantages to that approach: each ES module requires a network round trip, which
becomes very slow when you have hundreds of files, and many files in Odoo need to
be present despite not being imported by anything because they simply add code
that the framework will use instead of the other way around.

The odoo Class system does not support multiple inheritance, but for those cases
when we need to share some behaviour, we have a mixin system: the *extend*
method can actually take an arbitrary number of arguments, and will combine all
of them in the new class.
Because of this, Odoo has a system of asset bundles. In these bundles, JavaScript
files are ES modules with a special annotation at the top. These modules will be
bundled together and transpiled to be usable by our module loader. While you can
write code that doesn't use this module system, it is generally not recommended.

.. code-block:: javascript
var Animal = require('web.Animal');
var DanceMixin = {
dance: function () {
console.log('dancing...');
},
};
var Hamster = Animal.extend(DanceMixin, {
sleep: function () {
console.log('sleeping');
},
});
In this example, the *Hamster* class is a subclass of Animal, but it also mix
the DanceMixin in.

Patching an existing class
--------------------------

It is not common, but we sometimes need to modify another class *in place*. The
goal is to have a mechanism to change a class and all future/present instances.
This is done by using the *include* method:

.. code-block:: javascript
var Hamster = require('web.Hamster');
Hamster.include({
sleep: function () {
this._super.apply(this, arguments);
console.log('zzzz');
},
});
This is obviously a dangerous operation and should be done with care. But with
the way Odoo is structured, it is sometimes necessary in one addon to modify
the behavior of a widget/class defined in another addon. Note that it will
modify all instances of the class, even if they have already been created.

Widgets
=======

The *Widget* class is really an important building block of the user interface.
Pretty much everything in the user interface is under the control of a widget.
The Widget class is defined in the module *web.Widget*, in *widget.js*.

In short, the features provided by the Widget class include:

* parent/child relationships between widgets (*PropertiesMixin*)
* extensive lifecycle management with safety features (e.g. automatically
destroying children widgets during the destruction of a parent)
* automatic rendering with :ref:`qweb <reference/qweb>`
* various utility functions to help interacting with the outside environment.

Here is an example of a basic counter widget:

.. code-block:: javascript
var Widget = require('web.Widget');
var Counter = Widget.extend({
template: 'some.template',
events: {
'click button': '_onClick',
},
init: function (parent, value) {
this._super(parent);
this.count = value;
},
_onClick: function () {
this.count++;
this.$('.val').text(this.count);
},
});
For this example, assume that the template *some.template* (and is properly
loaded: the template is in a file, which is properly defined in the assets of
the module manifest, see
:ref:`assets <reference/assets>`.) is given by:

.. code-block:: xml
(see :ref:`frontend/modules/native_js`)

<div t-name="some.template">
<span class="val"><t t-esc="widget.count"/></span>
<button>Increment</button>
</div>

This example widget can be used in the following manner:

.. code-block:: javascript
// Create the instance
var counter = new Counter(this, 4);
// Render and insert into DOM
counter.appendTo(".some-div");
This example illustrates a few of the features of the *Widget* class, including
the event system, the template system, the constructor with the initial *parent* argument.

Widget Lifecycle
Patching classes
----------------

Like many component systems, the widget class has a well defined lifecycle. The
usual lifecycle is the following: *init* is called, then *willStart*, then the
rendering takes place, then *start* and finally *destroy*.

.. function:: Widget.init(parent)

this is the constructor. The init method is supposed to initialize the
base state of the widget. It is synchronous and can be overridden to
take more parameters from the widget's creator/parent

:param parent: the new widget's parent, used to handle automatic
destruction and event propagation. Can be ``null`` for
the widget to have no parent.
:type parent: :class:`~Widget`

.. function:: Widget.willStart()

this method will be called once by the framework when a widget is created
and in the process of being appended to the DOM. The *willStart* method is a
hook that should return a promise. The JS framework will wait for this promise
to complete before moving on to the rendering step. Note that at this point,
the widget does not have a DOM root element. The *willStart* hook is mostly
useful to perform some asynchronous work, such as fetching data from the server

.. function:: [Rendering]

This step is automatically done by the framework. What happens is
that the framework checks if a template key is defined on the widget. If that is
the case, then it will render that template with the *widget* key bound to the
widget in the rendering context (see the example above: we use *widget.count*
in the QWeb template to read the value from the widget). If no template is
defined, we read the *tagName* key and create a corresponding DOM element.
When the rendering is done, we set the result as the $el property of the widget.
After this, we automatically bind all events in the events and custom_events
keys.

.. function:: Widget.start()

when the rendering is complete, the framework will automatically call
the *start* method. This is useful to perform some specialized post-rendering
work. For example, setting up a library.

Must return a promise to indicate when its work is done.

:returns: promise

.. function:: Widget.destroy()

This is always the final step in the life of a widget. When a
widget is destroyed, we basically perform all necessary cleanup operations:
removing the widget from the component tree, unbinding all events, ...

Automatically called when the widget's parent is destroyed,
must be called explicitly if the widget has no parent or if it is
removed but its parent remains.

Note that the willStart and start method are not necessarily called. A widget
can be created (the *init* method will be called) and then destroyed (*destroy*
method) without ever having been appended to the DOM. If that is the case, the
willStart and start will not even be called.

Widget API
----------

.. attribute:: Widget.tagName

Used if the widget has no template defined. Defaults to ``div``,
will be used as the tag name to create the DOM element to set as
the widget's DOM root. It is possible to further customize this
generated DOM root with the following attributes:


.. attribute:: Widget.id

Used to generate an ``id`` attribute on the generated DOM
root. Note that this is rarely needed, and is probably not a good idea
if a widget can be used more than once.

.. attribute:: Widget.className

Used to generate a ``class`` attribute on the generated DOM root. Note
that it can actually contain more than one css class:
*'some-class other-class'*

.. attribute:: Widget.attributes

Mapping (object literal) of attribute names to attribute
values. Each of these k:v pairs will be set as a DOM attribute
on the generated DOM root.

.. attribute:: Widget.el

raw DOM element set as root to the widget (only available after the start
lifecycle method)

.. attribute:: Widget.$el

jQuery wrapper around :attr:`~Widget.el`. (only available after the start
lifecycle method)

.. attribute:: Widget.template

Should be set to the name of a :ref:`QWeb template <reference/qweb>`.
If set, the template will be rendered after the widget has been
initialized but before it has been started. The root element generated by
the template will be set as the DOM root of the widget.

.. attribute:: Widget.events

Events are a mapping of an event selector (an event name and an optional
CSS selector separated by a space) to a callback. The callback can
be the name of a widget's method or a function object. In either case, the
``this`` will be set to the widget:

.. code-block:: javascript
events: {
'click p.oe_some_class a': 'some_method',
'change input': function (e) {
e.stopPropagation();
}
},
The selector is used for jQuery's event delegation, the
callback will only be triggered for descendants of the DOM root
matching the selector. If the selector is left out
(only an event name is specified), the event will be set directly on the
widget's DOM root.

Note: the use of an inline function is discouraged, and will probably be
removed sometimes in the future.

.. attribute:: Widget.custom_events

this is almost the same as the *events* attribute, but the keys
are arbitrary strings. They represent business events triggered by
some sub widgets. When an event is triggered, it will 'bubble up' the widget
tree (see the section on component communication for more details).

.. function:: Widget.isDestroyed()

:returns: ``true`` if the widget is being or has been destroyed, ``false``
otherwise

.. function:: Widget.$(selector)

Applies the CSS selector specified as parameter to the widget's
DOM root:

.. code-block:: javascript
this.$(selector);
is functionally identical to:

.. code-block:: javascript
this.$el.find(selector);
:param String selector: CSS selector
:returns: jQuery object

.. note:: this helper method is similar to ``Backbone.View.$``

.. function:: Widget.setElement(element)

Re-sets the widget's DOM root to the provided element, also
handles re-setting the various aliases of the DOM root as well as
unsetting and re-setting delegated events.

:param Element element: a DOM element or jQuery object to set as
the widget's DOM root

Inserting a widget in the DOM
-----------------------------

.. function:: Widget.appendTo(element)

Renders the widget and inserts it as the last child of the target, uses
`.appendTo()`_

.. function:: Widget.prependTo(element)

Renders the widget and inserts it as the first child of the target, uses
`.prependTo()`_

.. function:: Widget.insertAfter(element)

Renders the widget and inserts it as the preceding sibling of the target,
uses `.insertAfter()`_

.. function:: Widget.insertBefore(element)

Renders the widget and inserts it as the following sibling of the target,
uses `.insertBefore()`_

All of these methods accept whatever the corresponding jQuery method accepts
(CSS selectors, DOM nodes or jQuery objects). They all return a promise
and are charged with three tasks:

* rendering the widget's root element via :func:`~Widget.renderElement`
* inserting the widget's root element in the DOM using whichever jQuery
method they match
* starting the widget, and returning the result of starting it

Widget Guidelines
-----------------

* Identifiers (``id`` attribute) should be avoided. In generic applications
and modules, ``id`` limits the re-usability of components and tends to make
code more brittle. Most of the time, they can be replaced with nothing,
classes or keeping a reference to a DOM node or jQuery element.

If an ``id`` is absolutely necessary (because a third-party library requires
one), the id should be partially generated using ``_.uniqueId()`` e.g.:

.. code-block:: javascript
this.id = _.uniqueId('my-widget-');
* Avoid predictable/common CSS class names. Class names such as "content" or
"navigation" might match the desired meaning/semantics, but it is likely an
other developer will have the same need, creating a naming conflict and
unintended behavior. Generic class names should be prefixed with e.g. the
name of the component they belong to (creating "informal" namespaces, much
as in C or Objective-C).

* Global selectors should be avoided. Because a component may be used several
times in a single page (an example in Odoo is dashboards), queries should be
restricted to a given component's scope. Unfiltered selections such as
``$(selector)`` or ``document.querySelectorAll(selector)`` will generally
lead to unintended or incorrect behavior. Odoo Web's
:class:`~Widget` has an attribute providing its DOM root
(:attr:`~Widget.$el`), and a shortcut to select nodes directly
(:func:`~Widget.$`).

* More generally, never assume your components own or controls anything beyond
its own personal :attr:`~Widget.$el` (so, avoid using a reference to the
parent widget)

* Html templating/rendering should use QWeb unless absolutely trivial.

* All interactive components (components displaying information to the screen
or intercepting DOM events) must inherit from :class:`~Widget`
and correctly implement and use its API and life cycle.

* Make sure to wait for start to be finished before using $el e.g.:

.. code-block:: javascript
var Widget = require('web.Widget');
var AlmostCorrectWidget = Widget.extend({
start: function () {
this.$el.hasClass(....) // in theory, $el is already set, but you don't know what the parent will do with it, better call super first
return this._super.apply(arguments);
},
});
var IncorrectWidget = Widget.extend({
start: function () {
this._super.apply(arguments); // the parent promise is lost, nobody will wait for the start of this widget
this.$el.hasClass(....)
},
});
var CorrectWidget = Widget.extend({
start: function () {
var self = this;
return this._super.apply(arguments).then(function() {
self.$el.hasClass(....) // this works, no promise is lost and the code executes in a controlled order: first super, then our code.
});
},
});
.. _reference/javascript_reference/qweb:

QWeb Template Engine
====================

The web client uses the :doc:`qweb` template engine to render widgets (unless they
override the *renderElement* method to do something else).
The Qweb JS template engine is based on XML, and is mostly compatible with the
python implementation.

The web client will wait for that list of template (included into the current asset)
to be loaded, before starting its first widget.

Event system
============

There are currently two event systems supported by Odoo: a simple system which
allows adding listeners and triggering events, and a more complete system that
also makes events 'bubble up'.

Both of these event systems are implemented in the *EventDispatcherMixin*, in
the file *mixins.js*. This mixin is included in the *Widget* class.

Base Event system
-----------------

This event system was historically the first. It implements a simple bus
pattern. We have 4 main methods:

- *on*: this is used to register a listener on an event.
- *off*: useful to remove events listener.
- *once*: this is used to register a listener that will only be called once.
- *trigger*: trigger an event. This will cause each listeners to be called.

Here is an example on how this event system could be used:

.. code-block:: javascript
var Widget = require('web.Widget');
var Counter = require('myModule.Counter');
var MyWidget = Widget.extend({
start: function () {
this.counter = new Counter(this);
this.counter.on('valuechange', this, this._onValueChange);
var def = this.counter.appendTo(this.$el);
return Promise.all([def, this._super.apply(this, arguments)]);
},
_onValueChange: function (val) {
// do something with val
},
});
// in Counter widget, we need to call the trigger method:
... this.trigger('valuechange', someValue);
.. warning::
the use of this event system is discouraged, we plan to replace each
*trigger* method by the *trigger_up* method from the extended event system

Extended Event System
---------------------

The custom event widgets is a more advanced system, which mimic the DOM events
API. Whenever an event is triggered, it will 'bubble up' the component tree,
until it reaches the root widget, or is stopped.

- *trigger_up*: this is the method that will create a small *OdooEvent* and
dispatch it in the component tree. Note that it will start with the component
that triggered the event
- *custom_events*: this is the equivalent of the *event* dictionary, but for
odoo events.

The OdooEvent class is very simple. It has three public attributes: *target*
(the widget that triggered the event), *name* (the event name) and *data* (the
payload). It also has 2 methods: *stopPropagation* and *is_stopped*.

The previous example can be updated to use the custom event system:
While we do our best to provide extension points that don't require it, it is
sometimes necessary to modify the behavior of an existing class *in place*. The
goal is to have a mechanism to change a class and all future/present instances.
This is done by using the `patch` utility function:

.. code-block:: javascript
var Widget = require('web.Widget');
var Counter = require('myModule.Counter');
/** @odoo-module */
import { Hamster } from "@web/core/hamster"
import { patch } from "@web/core/utils/patch";
var MyWidget = Widget.extend({
custom_events: {
valuechange: '_onValueChange'
},
start: function () {
this.counter = new Counter(this);
var def = this.counter.appendTo(this.$el);
return Promise.all([def, this._super.apply(this, arguments)]);
},
_onValueChange: function(event) {
// do something with event.data.val
patch(Hamster.prototype, {
sleep() {
super.sleep(...arguments);
console.log("zzzz");
},
});
// in Counter widget, we need to call the trigger_up method:
When patching methods, you need to patch the class' prototype, but if you would
like to patch a static property of the class, you need to patch the class itself.

... this.trigger_up('valuechange', {value: someValue});
Patching is a dangerous operation and should be done with care as it will
modify all instances of the class, even if they have already been created. To
avoid weird issues, patches should be applied as soon as possible, at the
top-level of your module. Patching classes at runtime can result in extremely
difficult to debug issues if the class has already been instanciated.

Registries
==========

A common need in the Odoo ecosystem is to extend/change the behaviour of the
base system from the outside (by installing an application, i.e. a different
module). For example, one may need to add a new widget type in some views. In
module). For example, one may need to add a new field widget in some views. In
that case, and many others, the usual process is to create the desired component,
then add it to a registry (registering step), to make the rest of the web client
aware of its existence.

There are a few registries available in the system:
There are a few registries available in the system. The registries that are used
by the framework are categories on the main registry, that can be imported from
:js:data:`@web/core/registry`

field registry (exported by :js:data:`web.field_registry`)
field registry
The field registry contains all field widgets known to the web client.
Whenever a view (typically form or list/kanban) needs a field widget, this
is where it will look. A typical use case look like this:

.. code-block:: javascript
var fieldRegistry = require('web.field_registry');
var FieldPad = ...;
fieldRegistry.add('pad', FieldPad);
import { registry } from "@web/core/registry";
class PadField extends Component { ... }
Note that each value should be a subclass of *AbstractField*
registry.category("fields").add("pad", {
component: PadField,
supportedTypes: ["char"],
// ...
});
view registry
This registry contains all JS views known to the web client
(and in particular, the view manager). Each value of this registry should
be a subclass of *AbstractView*.
This registry contains all JS views known to the web client.

action registry
We keep track of all client actions in this registry. This
is where the action manager looks up whenever it needs to create a client
action. In version 11, each value should simply be a subclass of *Widget*.
However, in version 12, the values are required to be *AbstractAction*.

Communication between widgets
=============================

There are many ways to communicate between components.

From a parent to its child
This is a simple case. The parent widget can simply call a method on its
child:

.. code-block:: javascript
this.someWidget.update(someInfo);
From a widget to its parent/some ancestor
In this case, the widget's job is simply to notify its environment that
something happened. Since we do not want the widget to have a reference to
its parent (this would couple the widget with its parent's implementation),
the best way to proceed is usually to trigger an event, which will bubble up
the component tree, by using the ``trigger_up`` method:

.. code-block:: javascript
this.trigger_up('open_record', { record: record, id: id});
This event will be triggered on the widget, then will bubble up and be
eventually caught by some upstream widget:

.. code-block:: javascript
var SomeAncestor = Widget.extend({
custom_events: {
'open_record': '_onOpenRecord',
},
_onOpenRecord: function (event) {
var record = event.data.record;
var id = event.data.id;
// do something with the event.
},
});
Cross component
Cross component communication can be achieved by using a bus. This is not
the preferred form of communication, because it has the disadvantage of
making the code harder to maintain. However, it has the advantage of
decoupling the components. In that case, this is simply done by triggering
and listening to events on a bus. For example:

.. code-block:: javascript
// in WidgetA
var core = require('web.core');
var WidgetA = Widget.extend({
...
start: function () {
core.bus.on('barcode_scanned', this, this._onBarcodeScanned);
},
});
// in WidgetB
var WidgetB = Widget.extend({
...
someFunction: function (barcode) {
core.bus.trigger('barcode_scanned', barcode);
},
});
In this example, we use the bus exported by *web.core*, but this is not
required. A bus could be created for a specific purpose.
action. Client actions can be a function - the function will be called when the
action is invoked, and the returned value will be executed as a follow up action
if needed - or an Owl component that will be displayed when executing that action.

Services
========

In version 11.0, we introduced the notion of *service*. The main idea is to
give to sub components a controlled way to access their environment, in a way
that allow the framework enough control, and which is testable.

The service system is organized around three ideas: services, service providers
and widgets. The way it works is that widgets trigger (with *trigger_up*)
events, these events bubble up to a service provider, which will ask a service
to perform a task, then maybe return an answer.

Service
-------
Within the webclient, there are some concerns that cannot be handled by a single
component, as the concern is transversal, involves many components, or needs to
maintain some state for as long as the application is alive.

A service is an instance of the *AbstractService* class. It basically only has
a name and a few methods. Its job is to perform some work, typically something
depending on the environment.
Services are a solution to these problems: they are created during application
startup, are available to components through the hook `useService`, and stay
alive for the entire lifetime of the application.

For example, we have the *ajax* service (job is to perform a rpc), the
*localStorage* (interact with the browser local storage) and many others.
For example, we have the *orm* service whose job is to allow interacting with
business objects on the server.

Here is a simplified example on how the ajax service is implemented:
Here is a simplified example on how the orm service is implemented:

.. code-block:: javascript
var AbstractService = require('web.AbstractService');
var AjaxService = AbstractService.extend({
name: 'ajax',
rpc: function (...) {
return ...;
import { registry } from "@web/core/registry";
export const OrmService = {
start() {
return {
read(...) { ... },
write(...) { ... },
unlink(...) { ... },
...
}
},
});
This service is named 'ajax' and define one method, *rpc*.

Service Provider
----------------

For services to work, it is necessary that we have a service provider ready to
dispatch the custom events. In the *backend* (web client), this is done by the
main web client instance. Note that the code for the service provider comes from
the *ServiceProviderMixin*.

Widget
------
};
registry.category("services").add("orm", OrmService);
The widget is the part that requests a service. In order to do that, it simply
triggers an event *call_service* (typically by using the helper function *call*).
This event will bubble up and communicate the intent to the rest of the system.
Using services
--------------

In practice, some functions are so frequently called that we have some helpers
functions to make them easier to use. For example, the *_rpc* method is a helper
that helps making a rpc.
Services are available in the environment, but should generally be used through
the `useService` hook, which prevents calling methods on the service after a
component has been destroyed, and prevents further code from executing after a
method call if the component was destroyed during the call.

.. code-block:: javascript
var SomeWidget = Widget.extend({
_getActivityModelViewID: function (model) {
return this._rpc({
model: model,
method: 'get_activity_view_id'
});
},
});
.. warning::
If a widget is destroyed, it will be detached from the main component tree
and will not have a parent. In that case, the events will not bubble up, which
means that the work will not be done. This is usually exactly what we want from
a destroyed widget.
class SomeComponent extends Component {
setup() {
this.orm = useService("orm");
}
// ...
getActivityModelViewID(model) {
return this.orm.call(model, "get_activity_view_id", this.params);
}
}
RPCs
----
Talking to the server
---------------------

The rpc functionality is supplied by the ajax service. But most people will
probably only interact with the *_rpc* helpers.
There are typically two use cases when working on Odoo: one may need to call a
method on a (python) model (this goes through the controller `/web/dataset/call_kw`),
or one may need to directly call a controller (available on some route).

There are typically two usecases when working on Odoo: one may need to call a
method on a (python) model (this goes through a controller *call_kw*), or one
may need to directly call a controller (available on some route).
* Calling a method on a python model is done through the orm service:

* Calling a method on a python model:
.. code-block:: javascript
.. code-block:: javascript
return this.orm.call("some.model", "some_method", [some, args]);
return this._rpc({
model: 'some.model',
method: 'some_method',
args: [some, args],
});
* Directly calling a controller is done through the rpc service:

* Directly calling a controller:
.. code-block:: javascript
.. code-block:: javascript
return this.rpc("/some/route/", {
some: param,
});
return this._rpc({
route: '/some/route/',
params: { some: kwargs},
});
.. note::
The rpc service doesn't really perform what is generally understood as a
remote procedure call (RPC), but for historical reasons, within Odoo we
generally call any network request performed in JavaScript an RPC. As
highlighted in the previous paragraph, if you want to call a method on a
model, you should use the orm service.

Notifications
=============

The Odoo framework has a standard way to communicate various information to the
user: notifications, which are displayed on the top right of the user interface.
The types of notification follow the bootstrap toasts:

There are two types of notifications:

- *notification*: useful to display some feedback. For example, whenever a user
unsubscribed to a channel.
- *info*: useful to display some informational feedback as a consequence of an
action that cannot fail.

- *warning*: useful to display some important/urgent information. Typically
most kind of (recoverable) errors in the system.
- *success*: the user performed an action that can sometimes fail but didn't.

Also, notifications can be used to ask a question to the user without disturbing
its workflow. Imagine a phone call received through VOIP: a sticky notification
could be displayed with two buttons *Accept* and *Decline*.
- *warning*: the user performed an action that could only be partially completed.
Also useful if something is wrong but wasn't caused by the user directly, or
is not particularly actionable.

Notification system
-------------------
- *success*: the user tried to performed an action but it couldn't be completed.

The notification system in Odoo is designed with the following components:

- a *Notification* widget: this is a simple widget that is meant to be created
and displayed with the desired information
Notifications can also be used to ask a question to the user without disturbing
their workflow: e.g. a phone call received through VOIP: a sticky notification
could be displayed with two buttons to *Accept* or *Decline*.

- a *NotificationService*: a service whose responsibility is to create and
destroy notifications whenever a request is done (with a custom_event). Note
that the web client is a service provider.
Displaying notifications
------------------------

- a client action *display_notification*: this allows to trigger the display
of a notification from python (e.g. in the method called when the user
clicked on a button of type object).

- an helper function in *ServiceMixin*: *displayNotification*

Displaying a notification
-------------------------
There are two ways to display notifications in Odoo:

The most common way to display a notification is by using the method that come
from the *ServiceMixin*:
- The *notification* service allows component to display notifications from JS
code by calling the add method.

- *displayNotification(options)*:
Display a notification with the following *options*:
- The *display_notification* client action allows to trigger the display
of a notification from python (e.g. in the method called when the user
clicked on a button of type object). This client action uses the notification
service.

- *title*: string, optional. This will be displayed on the top as a title.
Notifications have a few *options*:

- *subtitle*: string, optional. This will be displayed on the top as a
subtitle.
- *title*: string, optional. This will be displayed on the top as a title.

- *message*: string, optional. The content of the notification. Do not forget
to escape your message via the markup function if needed.
- *message*: string, optional. The content of the notification. Can be a markup
object to display formatted text.

- *sticky*: boolean, optional (default false). If true, the notification
will stay until the user dismisses it. Otherwise, the notification will
be automatically closed after a short delay.
- *sticky*: boolean, optional (default false). If true, the notification
will stay until the user dismisses it. Otherwise, the notification will
be automatically closed after a short delay.

- *type*: string, optional (default 'warning'). Determines the style of the
notification. Possible values: 'info', 'success', 'warning', 'danger', ''.
- *type*: string, optional (default "warning"). Determines the style of the
notification. Possible values: "info", "success", "warning", "danger"

- *className*: string, optional. This is a css class name that will be
automatically added to the notification. This could be useful for styling
purpose, even though its use is discouraged.
- *className*: string, optional. This is a css class name that will be
automatically added to the notification. This could be useful for styling
purpose, even though its use is discouraged.

Here are two examples on how to use these methods:
Here are some examples on how to display notifications in JS:

.. code-block:: javascript
// note that we call _t on the text to make sure it is properly translated.
this.displayNotification({
this.notification.add({
title: _t("Success"),
message: _t("Your signature request has been sent.")
});
this.displayNotification({
this.notification.add({
title: _t("Error"),
message: _t("Filter name is required."),
type: 'danger',
type: "danger",
});
Here an example in python:
And in Python:

.. code-block:: python
@@ -1018,116 +381,97 @@ Here an example in python:
Systray
=======

The Systray is the right part of the menu bar in the interface, where the web
client displays a few widgets, such as a messaging menu.
The Systray is the right part of the navbar in the interface, where the web
client displays a few widgets, such as the messaging menu.

When the SystrayMenu is created by the menu, it will look for all registered
widgets and add them as a sub widget at the proper place.
When the systray is created by the navbar, it will look for all registered
systray items and display them.

There is currently no specific API for systray widgets. They are supposed to
be simple widgets, and can communicate with their environment just like other
widgets with the *trigger_up* method.
There is currently no specific API for systray items. They are Owl components,
and can communicate with their environment just like other components, e.g. by
interacting with services.

Adding a new Systray Item
-------------------------

There is no systray registry. The proper way to add a widget is to add it to
the class variable SystrayMenu.items.
Items can be added to the systray by adding them to the "systray" registry:

.. code-block:: javascript
var SystrayMenu = require('web.SystrayMenu');
var MySystrayWidget = Widget.extend({
import { registry } from "@web/core/registry"
class MySystrayComponent extends Component {
...
});
SystrayMenu.Items.push(MySystrayWidget);
Ordering
--------
}
registry.category("systray").add("MySystrayComponent", MySystrayComponent, { sequence: 1 });
Before adding the widget to himself, the Systray Menu will sort the items by
a sequence property. If that property is not present on the prototype, it will
use 50 instead. So, to position a systray item to be on the right, one can
set a very high sequence number (and conversely, a low number to put it on the
left).

.. code-block:: javascript
MySystrayWidget.prototype.sequence = 100;
The items are ordered in the systray according to their sequence in the systray
registry.

Translation management
======================

Some translations are made on the server side (basically all text strings rendered or
processed by the server), but there are strings in the static files that need
to be translated. The way it currently works is the following:
to be translated. The way it currently works is the following:

- each translatable string is tagged with the special function *_t* (available in
the JS module *web.core*
- each translatable string is tagged with the special function *_t*
- these strings are used by the server to generate the proper PO files
- whenever the web client is loaded, it will call the route */web/webclient/translations*,
which returns a list of all translatable terms
- in runtime, whenever the function *_t* is called, it will look up in this list
- at runtime, whenever the function `_t` is called, it will look up in this list
in order to find a translation, and return it or the original string if none
is found.

Note that translations are explained in more details, from the server point of
view, in the document :doc:`/developer/howtos/translations`.

There are two important functions for the translations in javascript: *_t* and
*_lt*. The difference is that *_lt* is lazily evaluated.

.. code-block:: javascript
var core = require('web.core');
import { _t } from "@web/core/l10n/translation";
var _t = core._t;
var _lt = core._lt;
var SomeWidget = Widget.extend({
exampleString: _lt('this should be translated'),
class SomeComponent extends Component {
static exampleString = _t("this should be translated");
...
someMethod: function () {
var str = _t('some text');
...
},
});
someMethod() {
const str = _t("some text");
}
}
In this example, the *_lt* is necessary because the translations are not ready
when the module is loaded.
Note that using the translation function requires some care: the string given as
an argument cannot be dynamic, as it is extracted statically from the code to
generate the PO files and serves as the identifier for the term to translate. If
you need to inject some dynamic content in the string, `_t` supports placeholders:

.. code-block:: javascript
import { _t } from "@web/core/l10n/translation";
const str = _t("Hello %s, you have %s unread messages.", user.name, unreadCount);
Notice how the string itself is fixed. This allows the translation function to
retrieve the translated string *before* using it for interpolation.

Note that translation functions need some care. The string given in argument
should not be dynamic.

Session
=======

There is a specific module provided by the web client which contains some
information specific to the user current *session*. Some notable keys are

- uid: the current user ID (its ID as a *res.users*)
- user_name: the user name, as a string
- the user context (user ID, language and timezone)
- partner_id: the ID of the partner associated to the current user
- db: the name of the database currently being in use
The webclient needs some information from the python to function properly. To
avoid an extra round-trip with the server by making a network request in JavaScript,
this information is serialized directly in the page, and can be accessed in JS
through the `@web/session` module.

Adding information to the session
---------------------------------

When the /web route is loaded, the server will inject some session information
in the template a script tag. The information will be read from the method
*session_info* of the model *ir.http*. So, if one wants to add a specific
information, it can be done by overriding the session_info method and adding it
to the dictionary.
When the `/web` route is loaded, the server injects this information in a script
tag. The information is obtained by calling the method `session_info` of
the model `ir.http`. You can override this method to add information to the
returned dictionary.

.. code-block:: python
from odoo import models
from odoo.http import request
class IrHttp(models.AbstractModel):
_inherit = 'ir.http'
@@ -1140,78 +484,28 @@ Now, the value can be obtained in javascript by reading it in the session:

.. code-block:: javascript
var session = require('web.session');
var myValue = session.some_key;
import { session } from "@web/session"
const myValue = session.some_key;
...
Note that this mechanism is designed to reduce the amount of communication
needed by the web client to be ready. It is more appropriate for data which is
needed by the web client to be ready. It is only appropriate for data which is
cheap to compute (a slow session_info call will delay the loading for the web
client for everyone), and for data which is required early in the initialization
process.

Views
=====

The word 'view' has more than one meaning. This section is about the design of
The word "view" has more than one meaning. This section is about the design of
the javascript code of the views, not the structure of the *arch* or anything
else.

In 2017, Odoo replaced the previous view code with a new architecture. The
main need was to separate the rendering logic from the model logic.

Views (in a generic sense) are now described with 4 pieces: a View, a
Controller, a Renderer and a Model. The API of these 4 pieces is described in
the AbstractView, AbstractController, AbstractRenderer and AbstractModel classes.

.. raw:: html

<svg width="550" height="173">
<!-- Created with Method Draw - https://github.com/duopixel/Method-Draw/ -->
<path id="svg_1" d="m147.42498,79.79206c0.09944,-8.18859 -0.06363,-16.38812 0.81774,-24.5623c21.65679,2.68895 43.05815,7.08874 64.35,11.04543c1.14304,-4.01519 0.60504,-7.34585 1.59817,-11.05817c13.67878,7.81176 27.23421,15.73476 40.23409,24.03505c-12.47212,9.41539 -26.77809,17.592 -40.82272,25.96494c-0.4548,-3.89916 -0.90967,-7.79828 -1.36448,-11.69744c-20.69972,3.77225 -42.59036,7.6724 -63.42391,11.12096c-1.41678,-7.95741 -1.37514,-16.62327 -1.38888,-24.84846z" stroke-width="1.5" stroke="#000" fill="#fff"/>
<rect id="svg_3" height="41" width="110" y="57.5" x="7" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="#fff"/>
<rect stroke="#000" id="svg_5" height="41" width="135" y="20.5" x="328" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="#fff"/>
<rect stroke="#000" id="svg_6" height="41" width="128" y="102.5" x="262" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="#fff"/>
<rect stroke="#000" id="svg_7" height="41" width="119" y="100.5" x="417" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="#fff"/>
<line stroke-linecap="null" stroke-linejoin="null" id="svg_8" y2="96.5" x2="317" y1="65.5" x1="364" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/>
<line stroke-linecap="null" stroke-linejoin="null" id="svg_9" y2="96.5" x2="467" y1="63.5" x1="425" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_10" y="83.5" x="38" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">View</text>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_11" y="44.5" x="346" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">Controller</text>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_12" y="128.5" x="276" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">Renderer</text>
<text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_13" y="127.5" x="442" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">Model</text>
</svg>

- the View is the factory. Its job is to get a set of fields, arch, context and
some other parameters, then to construct a Controller/Renderer/Model triplet.

The view's role is to properly setup each piece of the MVC pattern, with the correct
information. Usually, it has to process the arch string and extract the
data necessary for each other parts of the view.

Note that the view is a class, not a widget. Once its job has been done, it
can be discarded.

- the Renderer has one job: representing the data being viewed in a DOM element.
Each view can render the data in a different way. Also, it should listen on
appropriate user actions and notify its parent (the Controller) if necessary.

The Renderer is the V in the MVC pattern.

- the Model: its job is to fetch and hold the state of the view. Usually, it
represents in some way a set of records in the database. The Model is the
owner of the 'business data'. It is the M in the MVC pattern.

- the Controller: its job is to coordinate the renderer and the model. Also, it
is the main entry point for the rest of the web client. For example, when
the user changes something in the search view, the *update* method of the
controller will be called with the appropriate information.

It is the C in the MVC pattern.

.. note::
The JS code for the views has been designed to be usable outside of the
context of a view manager/action manager. They could be used in a client action,
or, they could be displayed in the public website (with some work on the assets).
While views are just owl components, the built-in views generally have the same
structure: a component called "SomethingController" which is the root of the view.
This component creates an instance of some "model" (an object responsible for
managing the data), and has a subcomponent called a "renderer" that handles the
display logic.

.. _reference/js/widgets:

@@ -2003,131 +1297,43 @@ Week Days (`week_days`)
Client actions
==============

The idea of a client action is a customized widget that is integrated into the
web client interface, just like an *act_window_action*. This is useful when
you need a component that is not closely linked to an existing view or a
specific model. For example, the Discuss application is actually a client
action.
A client action is a component that can be displayed as the main element in the
webclient, occupying all the space below the navbar, just like an `act_window_action`.
This is useful when you need a component that is not closely linked to an existing
view or a specific model. For example, the Discuss application is a client action.

A client action is a term that has various meanings, depending on the context:

- from the perspective of the server, it is a record of the model *ir_action*,
with a field *tag* of type char
- from the perspective of the web client, it is a widget, which inherits from
the class AbstractAction, and is supposed to be registered in the
action registry under the corresponding key (from the field char)
- from the perspective of the web client, it is an Owl component registered in the
action registry under the same key its tag

Whenever a menu item is associated with a client action, opening it will simply
fetch the action definition from the server, then lookup into its action
registry to get the Widget definition at the appropriate key, and finally, it
will instantiate and append the widget to the proper place in the DOM.
fetch the action definition from the server, then lookup its tag in the action
registry to get the component definition. This component will then be rendered by
the action container.

Adding a client action
----------------------

A client action is a widget that will control the part of the screen below the
menu bar. It can have a control panel, if necessary. Defining a client action
can be done in two steps: implementing a new widget and registering the widget
in the action registry.

Implementing a new client action.
This is done by creating a widget:

.. code-block:: javascript
var AbstractAction = require('web.AbstractAction');
var ClientAction = AbstractAction.extend({
hasControlPanel: true,
...
});
A client action is a component that will control the part of the screen below the
navbar. Defining a client action is as simple as creating an Owl component and
adding it to the action registry.

Registering the client action:
As usual, we need to make the web client aware of the mapping between
client actions and the actual class:

.. code-block:: javascript
var core = require('web.core');
core.action_registry.add('my-custom-action', ClientAction);
Then, to use the client action in the web client, we need to create a client
action record (a record of the model ``ir.actions.client``) with the proper
``tag`` attribute:

.. code-block:: xml
<record id="my_client_action" model="ir.actions.client">
<field name="name">Some Name</field>
<field name="tag">my-custom-action</field>
</record>
Using the control panel
-----------------------

By default, the client action does not display a control panel. To
do that, several steps should be done.

- Set the *hasControlPanel* to *true*.
In the widget code:

.. code-block:: javascript
.. code-block:: javascript
var MyClientAction = AbstractAction.extend({
hasControlPanel: true,
...
});
import { registry } from "@web/core/registry";
class MyClientAction extends Component { ... }
registry.category("actions").add("my-custom-action", ClientAction);
- Call the method *updateControlPanel* whenever we need to update the control panel.
For example:
Then, to use the client action in the web client, we need to create a client
action record (a record of the model ``ir.actions.client``) with the proper
``tag`` attribute:

.. code-block:: javascript
.. code-block:: xml
var SomeClientAction = Widget.extend({
hasControlPanel: true,
...
start: function () {
this._renderButtons();
this._update_control_panel();
...
},
do_show: function () {
...
this._update_control_panel();
},
_renderButtons: function () {
this.$buttons = $(QWeb.render('SomeTemplate.Buttons'));
this.$buttons.on('click', ...);
},
_update_control_panel: function () {
this.updateControlPanel({
cp_content: {
$buttons: this.$buttons,
},
});
}
The ``updateControlPanel`` is the main method to customize the content in the control panel.
For more information, look into the `control_panel_renderer.js <{GITHUB_PATH}/addons/web/static/src/js/views/control_panel/control_panel_renderer.js#L130>`_ file.
.. _glob:
https://en.wikipedia.org/wiki/Glob_(programming)
.. _.appendTo():
https://api.jquery.com/appendTo/
.. _.prependTo():
https://api.jquery.com/prependTo/
.. _.insertAfter():
https://api.jquery.com/insertAfter/
.. _.insertBefore():
https://api.jquery.com/insertBefore/
.. _event delegation:
https://api.jquery.com/delegate/
.. _datepicker: https://github.com/Eonasdan/bootstrap-datetimepicker
<record id="my_client_action" model="ir.actions.client">
<field name="name">Some Name</field>
<field name="tag">my-custom-action</field>
</record>