Sunday, 22 March 2015

Odoo Web framework RequireJS style

Odoo Require Concept

Odoo web framework come up with new concept of module loading which is almost inherited from RequireJS.

Let me describe how modules are loaded, how dependencies are calculated ?

Everything else is similar in web framework but now It is more modular, each and every component is reusable by just fetching/calling it using require(Its a wrapper to load the object)

Previously modules where loaded using namespace(same name given as module name to JS namespace)

First of all note that now onwards global object openerp is replaced with odoo.

Now onwards if you want to create any module or any widget you can simply define it using odoo.define, lets have example of both style old style as well as new style.

Old Style: openerp.my_module = function(openerp) {
openerp.web.my_widget1 = openerp.web.wdiget.extend({
//your widget stuff here
popup = new openerp.web.FormPopup(...);

openerp.web.my_widget2 = openerp.web.wdiget.extend({
//your widget stuff here

New Style: odoo.define('openerp.my_module', function(require) {
popup = require('form_popup') //Where form_popup is separately defined in another file
web = require('odoo_web');
web.my_widget2 = openerp.web.wdiget.extend({
//your widget stuff here

No doubt in both cases we can have re-usability but in later one we can maintain small files for small features and can use that small feature in another file by requiring it, code is more readable and simpler.

Note that, new style you need to define files in proper order because one feature might depends on another and due to dependency issue you module might not be loaded.

Lets have a look how it works in background ?

we having one main file boot.js, boot.js file having anonymous function and it is in closure style which is called when file is loaded in DOM, the responsibility of boot.js is to load module, this file is the file which generates the concept of require method and dependency.

It creates global odoo object which having define method, now note that all other files will have define function called where second parameter will be callback function, whenever each file is loaded, each file will call define method of odoo, now define method of odoo global object checks for the arguments and second argument will have function in which there will be other require calls.
Define method checks for the require calls using Regular Expression, after gathering all require calls it fetches those objects from services object
Service object is a object which is module's/object's pool.

Say for example if I define one of my widget:
odoo.define('form.digital_signature', function() {....});
Then form.digital_signature is going to register in service pool

If there is dependency missing then its not going to load that module, if everything is fine, if it finds all dependent module are already loaded in service object pool then it calls function which is second parameter in define and also add that module/feature with name given as a first argument in service pool, if one tries to register service with same name twice then it will raise error that service with same name is already registered.

There are some core methods in boot.js, which you should go through:
define -> Finds require call, generates dependency list, creates wrapper of require
process_jobs -> loads module and add that module in module/object service and factories pool
init -> initializes webclient

So with this we can simply create small features and us it by calling it another file using require('feature_name')

All your generated module will generally return reference of your object like example given below:

odoo.define('web.ajax', function (require) {
"use strict";

var time = require('web.time');
var Dialog = require('web.dialog');
var my_dialog = new Dialog(....)

where web.dialog is:

odoo.define('web.Dialog', function (require) {
"use strict";
var Dialog = Widget.extend({
//your dialog stuff
return Dialog;

So here web.dialog returns reference of Dialog and we can then use new Dialog after calling var Dialog require('web.dialog');

Hope this will help, feel free to raise your query by comments.