Image Alt Text

Odoo Java script cheat sheet v16

Before starting

make sure you have good understanding of vanilla js/ plain javascript, jquery etc 

The Javascript framework is designed to work with three main use cases:

  • the web client: this is the private web application, where one can view and edit business data. This is a single page application (the page is never reloaded, only the new data is fetched from the server whenever it is needed)

  • the website: this is the public part of Odoo. It allows an unidentified user to browse some content, to shop or to perform many actions, as a client. This is a classical website: various routes with controllers and some javascript to make it work.

  • the point of sale: this is the interface for the point of sale. It is a specialized single page application.

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)Javascript Modules

Odoo supports three different kinds of javascript files:

its important to understand in when you have to do which one from above list
 
  1. Compatibility: If you have existing code or modules that are in the older format, it may be more convenient to continue using that format for consistency and to avoid migration efforts.

  2. Odoo-Specific Features: The older format may be necessary if your project relies on specific Odoo features or customizations that are tightly integrated with Odoo's module system.

  3. Legacy Code: If you are working on an older Odoo version or maintaining a legacy project, you might have no choice but to use the older format due to compatibility constraints.


Plain Javascript files


In Odoo, all external libraries are loaded as plain javascript files.


Native Javascript Module

 Odoo will look at the first line of a JS file and check if it contains the string  @odoo-module. If so, it will automatically be converted to an Odoo module.

For example, let us consider the following module, located in web/static/src/file_a.js:

/** @odoo-module **/
import { someFunction } from './file_b';
export function otherFunction(val) {
    return someFunction(val + 3);
}

Note the comment in the first line: it describes that this file should be converted. Any file without this comment will be kept as-is (which will most likely be an error). This file will then be translated into an Odoo module that look like this:

odoo.define('@web/file_a', function (require) {
'use strict';
let __exports = {};
const { someFunction } = require("@web/file_b");
__exports.otherFunction = function otherFunction(val) {
    return someFunction(val + 3);
};
return __exports;
)};

So, as you can see, the transformation is basically adding odoo.define on top, and updating the import/export statements.

Another important point is that the translated module has an official name:  @web/file_a. This is the actual name of the module. Every relative imports will be converted as well. Every file located in an Odoo addon some_addon/static/src/path/to/file.js will be assigned a name prefixed by the addon name like this:  @some_addon/path/to/file.

 
Relative imports work, but only if the modules are in the same Odoo addon. So, imagine that we have the following file structure:
addons/
    web/
        static/
            src/
                file_a.js
                file_b.js
    stock/
        static/
            src/
                file_c.js

The file file_b can import file_a like this:

/** @odoo-module **/
import {something} from `./file_a`

But file_c need to use the full name:

/** @odoo-module **/ import {something} from `@web/file_a`


Limitations

For performance reasons, Odoo does not use a full javascript parser to transform native modules. There are, therefore, a number of limitations including but not limited to:

  • an import or export keyword cannot be preceded by a non-space character,

  • a multiline comment or string cannot have a line starting by import or export

    // supported
    import X from "xxx";
    export X;
      export default X;
        import X from "xxx";
    /*
     * import X ...
     */
    /*
     * export X
     */
    // not supported
    var a= 1;import X from "xxx";
    /*
      import X ...
    */
    
  • when you export an object, it can’t contain a comment

    // supported
    export {
      a as b,
      c,
      d,
    }
    export {
      a
    } from "./file_a"
    // not supported
    export {
      a as b, // this is a comment
      c,
      d,
    }
    export {
      a /* this is a comment */
    } from "./file_a"
    
  • Odoo needs a way to determine if a module is described by a path (like ./views/form_view) or a name (like web.FormView). It has to use a heuristic to do just that: if there is a / in the name, it is considered a path. This means that Odoo does not really support module names with a / anymore.


Odoo Module System

Odoo has defined a small module system (located in the file addons/web/static/src/js/boot.js, which needs to be loaded first). The Odoo module system, inspired by AMD, works by defining the function define on the global odoo object. We then define each javascript module by calling that function. In the Odoo framework, a module is a piece of code that will be executed as soon as possible. It has a name and potentially some dependencies. When its dependencies are loaded, a module will then be loaded as well. The value of the module is then the return value of the function defining the module.

As an example, it may look like this:

// in file a.js
odoo.define('module.A', function (require) {
    "use strict";
    var A = ...;
    return A;
});
// in file b.js
odoo.define('module.B', function (require) {
    "use strict";
    var A = require('module.A');
    var B = ...; // something that involves A
    return B;
});

An alternative way to define a module is to give explicitly a list of dependencies in the second argument.

odoo.define('module.Something', ['module.A', 'module.B'], function (require) {
    "use strict";
    var A = require('module.A');
    var B = require('module.B');
    // some code
});

If some dependencies are missing/non ready, then the module will simply not be loaded. There will be a warning in the console after a few seconds.

Note that circular dependencies are not supported. It makes sense, but it means that one needs to be careful.


If an error happens, it will be logged (in debug mode) in the console:

  • Missing dependencies: These modules do not appear in the page. It is possible that the JavaScript file is not in the page or that the module name is wrong

  • Failed modules: A javascript error is detected

  • Rejected modules: The module returns a rejected Promise. It (and its dependent modules) is not loaded.

  • Rejected linked modules: Modules who depend on a rejected module

  • Non loaded modules: Modules who depend on a missing or a failed module

Asynchronous modules

It can happen that a module needs to perform some work before it is ready. For example, it could do an rpc to load some data. In that case, the module can simply return a promise. The module system will simply wait for the promise to complete before registering the module.

odoo.define('module.Something', function (require) { "use strict"; var ajax = require('web.ajax'); return ajax.rpc(...).then(function (result) { // some code here return something; }); });


Asset types

There are three different asset types: code (js files), style (css or scss files) and templates (xml files).

Code

Odoo supports three different kinds of javascript files. All these files are then processed (native JS modules are transformed into odoo modules), then minified (if not in debug=assets mode) and concatenated. The result is then saved as a file attachment. These file attachments are usually loaded via a <script> tag in the <head> part of the page (as a static file).

Style

Styling can be done with either css or scss. Like the javascript files, these files are processed (scss files are converted into css), then minified (again, if not in debug=assets mode) and concatenated. The result is then saved as a file attachment. They are then usually loaded via a <link> tag in the <head> part of the page (as a static file).

Template

Templates (static xml files) are handled in a different way: they are simply read from the file system whenever they are needed, and concatenated.


Bundles

static resources

Odoo assets are grouped by bundles. Each bundle (a list of file paths of specific types: xml, js, css or scss) is listed in the module manifest. Files can be declared using glob syntax, meaning that you can declare several asset files using a single line.

The bundles are defined in each module’s __manifest__.py, with a dedicated assets key which contains a dictionary. The dictionary maps bundle names (keys) to the list of files they contain (values). It looks like this:

'assets': {
    'web.assets_backend': [
        'web/static/src/xml/**/*',
    ],
    'web.assets_common': [
        'web/static/lib/bootstrap/**/*',
        'web/static/src/js/boot.js',
        'web/static/src/js/webclient.js',
    ],
    'web.qunit_suite_tests': [
        'web/static/src/js/webclient_tests.js',
    ],
},

Here is a list of some important bundles that most odoo developers will need to know:
  • web.assets_common: this bundle contains most assets which are common to the web client, the website and also the point of sale. This is supposed to contain lower level building blocks for the odoo framework. Note that it contains the boot.js file, which defines the odoo module system.

  • web.assets_backend: this bundle contains the code specific to the web client (notably the web client/action manager/views/static XML templates)

  • web.assets_frontend: this bundle is about all that is specific to the public website: ecommerce, portal, forum, blog, …

  • web.qunit_suite_tests: all javascript qunit testing code (tests, helpers, mocks)

  • web.qunit_mobile_suite_tests: mobile specific qunit testing code


Operations

Typically, handling assets is simple: you just need to add some new files to a frequently used bundle like assets_common or assets_backend. But there are other operations available to cover some more specific use cases.

Note that all directives targeting a certain asset file (i.e. before, after, replace and remove) need that file to be declared beforehand, either in manifests higher up in the hierarchy or in ir.asset records with a lower sequence.

append

This operation adds one or multiple file(s). Since it is the most common operation, it can be done by simply using the file name:

'web.assets_common': [
    'my_addon/static/src/js/**/*',
],

By default, adding a simple string to a bundle will append the files matching the glob pattern at the end of the bundle. Obviously, the pattern may also be directly a single file path.

prepend

Add one or multiple file(s) at the beginning of the bundle.

Useful when you need to put a certain file before the others in a bundle (for example with css files). The prepend operation is invoked with the following syntax: ('prepend', <path>).

'web.assets_common': [
    ('prepend', 'my_addon/static/src/css/bootstrap_overridden.scss'),
],

before

Add one or multiple file(s) before a specific file.

Prepending a file at the beginning of a bundle might not be precise enough. The before directive can be used to add the given file(s) right before the target file. It is declared by replacing the normal path with a 3-element tuple ('before', <target>, <path>).

'web.assets_common': [
    ('before', 'web/static/src/css/bootstrap_overridden.scss', 'my_addon/static/src/css/bootstrap_overridden.scss'),
],

after

Add one or multiple file(s) after a specific file.

Same as before, with the matching file(s) appended right after the target file. It is declared by replacing the normal path with a 3-element tuple ('after', <target>, <path>).

'web.assets_common': [
    ('after', 'web/static/src/css/list_view.scss', 'my_addon/static/src/css/list_view.scss'),
],

include

Use nested bundles.

The include directive is a way to use a bundle in other bundles to minimize the size of your manifest. In Odoo we use sub bundles (prefixed with an underscore by convention) to batch files used in multiple other bundles. You can then specify the sub bundle as a pair ('include', <bundle>) like this:

'web.assets_common': [
    ('include', 'web._primary_variables'),
],

remove

Remove one or multiple file(s).

In some cases, you may want to remove one or multiple files from a bundle. This can be done using the remove directive by specifying a pair ('remove', <target>):

'web.assets_common': [
    ('remove', 'web/static/src/js/boot.js'),
],

replace

Replace an asset file with one or multiple file(s).

Let us say that an asset needs not only to be removed, but you also want to insert your new version of that asset at the same exact position. This can be done with the replace directive, using a 3-element tuple ('replace', <target>, <path>):

'web.assets_common': [
    ('replace', 'web/static/src/js/boot.js', 'my_addon/static/src/js/boot.js'),
],


Loading order

The order in which assets are loaded is sometimes critical and must be deterministic, mostly for stylesheets priorities and setup scripts. Assets in Odoo are processed as follows:

Assets declared in the manifest may need to be loaded in a particular order, for example jquery.js must be loaded before all other jquery scripts when loading the lib folder. One solution would be to create an ir.asset record with a lower sequence or a ‘prepend’ directive, but there is another simpler way to do so.

Since the unicity of each file path in the list of assets is guaranteed, you can mention any specific file before a glob that includes it. That file will thus appear in the list before all the others included in the glob.

'web.assets_common': [
    'my_addon/static/lib/jquery/jquery.js',
    'my_addon/static/lib/jquery/**/*',
],

 Note

A module b removing/replacing the assets declared in a module a will have to depend on it. Trying to operate on assets that have yet to be declared will result in an error.

Lazy loading

It is sometimes useful to load files and/or asset bundles dynamically, for example to only load a library once it is needed. To do that, the Odoo framework provides a few helper functions, located in @web/core/assets.

await loadAssets({ jsLibs: ["/web/static/lib/stacktracejs/stacktrace.js"], });


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.

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

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).

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.

var Animal = require('web.Animal');
var Dog = Animal.extend({
    move: function () {
        this.bark();
        this._super.apply(this, arguments);
    },
    bark: function () {
        console.log('woof');
    },
});
var dog = new Dog();
dog.move()

Mixins

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.

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:

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.

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…

 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).

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 another file. There are two ways of sharing code between files:

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

  • use 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 Odoo Module System).

However, Odoo also provides support for native javascript modules (see Native Javascript Modules). 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.


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 qweb

  • various utility functions to help interacting with the outside environment.

Here is an example of a basic counter widget:

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 'assets': {'web.assets_qweb': [...]}, see assets.) is given by:

<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:

// 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

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.


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

Arguments

  • parent (Widget()) – the new widget’s parent, used to handle automatic destruction and event propagation. Can be null for the widget to have no parent.

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

[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.

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

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.


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:

var Widget = require('web.Widget');
var Counter = require('myModule.Counter');
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
    },
});
// in Counter widget, we need to call the trigger_up method:
... this.trigger_up('valuechange', {value: someValue});

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 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:

field registry (exported by web.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:

var fieldRegistry = require('web.field_registry');
var FieldPad = ...;
fieldRegistry.add('pad', FieldPad);

Note that each value should be a subclass of AbstractField

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.

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:

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:

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:

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. }, });