You are on page 1of 57

OpenERP Web Training

Release 1.0

OpenERP SA

September 16, 2013

CONTENTS

Introduction
1.1 Reminder about OpenERP Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 About this Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

JavaScript Basics
2.1 Preamble . . . . . . . . .
2.2 Command Line Interpreter
2.3 Basic Data Types . . . . .
2.4 Implicit Type Conversions
2.5 Control Structures . . . .
2.6 Functions . . . . . . . . .
2.7 Variables and Scopes . . .
2.8 Arrays . . . . . . . . . .
2.9 Objects . . . . . . . . . .

3
3
3

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

5
5
6
6
7
8
9
9
10
11

JavaScript Libraries
3.1 A First JavaScript Application . . .
3.2 Underscore.js . . . . . . . . . . . .
3.3 HTML Manipulations with jQuery
3.4 HTTP Requests with jQuery . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

13
13
16
17
20

OpenERP Web Framework


4.1 A Simple Module to Test the Web Framework
4.2 OpenERP JavaScript Module . . . . . . . . .
4.3 Classes . . . . . . . . . . . . . . . . . . . . .
4.4 Widgets Basics . . . . . . . . . . . . . . . . .
4.5 The QWeb Template Engine . . . . . . . . . .
4.6 Widget Events and Properties . . . . . . . . .
4.7 Widget Helpers . . . . . . . . . . . . . . . . .
4.8 Translations . . . . . . . . . . . . . . . . . .
4.9 Communication with the OpenERP Server . .
4.10 Exercises . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

27
27
29
29
31
34
39
42
44
45
47

Indices and tables

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

53

ii

OpenERP Web Training, Release 1.0

See also the OpenERP Web Reference Documentation.


Contents:

CONTENTS

OpenERP Web Training, Release 1.0

CONTENTS

CHAPTER

ONE

INTRODUCTION

1.1 Reminder about OpenERP Structure


OpenERP contains the following parts:

The OpenERP server contains the server-side framework and handles requests coming from clients.
The PostgreSQL database contains our data.
The modules implement the business logic.
The client web application communicates with the server and displays a graphical user interface.
This guide is all about web applications development and the OpenERPs web client.

1.2 About this Guide


This guide is a training material to teach OpenERP integrators how to create web modules for OpenERP. The covered
subjects are the following:
Javascript basics and good practices
Basics of jQuery and Underscore (Javascript libraries used in OpenERPs web client)
OpenERPs Javascript and web applications framework
Extension points for OpenERPs web client
This guide assumes the reader followed the technical training about OpenERP modules creation provided by OpenERP
SA or have a good knowledge of Python modules development. This guide also assume the reader is experienced in

OpenERP Web Training, Release 1.0

object-oriented programming and has basic knowledge of at least one programming language whose syntax is based
on C (C++, Java, C#,...). It may also refer to the Bazaar version control system. Finally, it is necessary to have a
minimal knowledge of HTML and CSS.
All provided examples assume you are developing under a Linux operating system, most specifically Ubuntu or
Debian. If you are using Windows, we recommend you to get a virtual machine with Ubuntu. The website
http://virtualboxes.org/ provides pre-installed virtual machines images for a lot of Linux distributions for free. They
only require to install Virtual Box which is also free.

Chapter 1. Introduction

CHAPTER

TWO

JAVASCRIPT BASICS

2.1 Preamble
2.1.1 What is a Web Application ?
A web application is simply an application that is delivered and used through a web browser, but the term has recently
taken a more specific meaning.
The old way to make a web application, and the way OpenERP worked until version 6.0 is to make the server send
to the user complete HTML documents representing the state of the applications GUI. This means the server has to
compute and send a new HTML document for each interaction; buttons clicks, searches, history navigation all require
the server to resend a document.
This puts a large load on the server and thus severely impact the number of concurrent users that can be served. It also
creates a large latency in the application that makes the implementation of many features impossible, and limits what
can be achieved in terms of usability.
The solution is to create a complete and standalone application in JavaScript that runs on the users web browser.
That type of application comes to have much more in common with traditional desktop applications (example: GTK,
Swing, Qt, Windows Forms,...) than PHP-like web sites. The only difference with desktop applications, besides the
programming language and libraries used, is that the web client is downloaded and run by the users browser each time
he visits the OpenERP website.

2.1.2 A Note about JavaScript


JavaScript is the language understood by web browsers and thus a de-facto language for web applications. If you want
to develop for OpenERPs Web client youll need to know JavaScript.
Objectively, JavaScript is a not a good programming language. It was designed by Netscape in 1995 for commercial
purpose by a small team with a short development time. It was not meant to be the most universal programming
language in the History. It has a lot a initial design problems and, due to backward-compatibility necessity, it was not
really improved since its creation.
Additionally JavaScript suffers from its wide popularity. This results in a lot of Google search results about JavaScript
being articles written by people that do not have a formal training in computer programming or that cant even program
at all but still manage to get some effects by copy-pasting code.
Still, despite its problems, the core of the language contains good ideas allowing a lot of creativity for programmers.
(Like prototype-based programming and functional programming.) Most of JavaScripts shortcomings can be erased
by using the correct patterns and the correct tools. It also has strong advantages on its own. First of all, JavaScript
is very fast. Fast to start, fast to run and fast to deploy. The ability to use HTML and the multimedia API of the
browsers also allows to create very nice looking applications and good productivity compared to desktop application

OpenERP Web Training, Release 1.0

programming. The decisive point is probably the fact that JavaScript virtual machines are available on 99.99% of the
desktop computers on the planet.
In the end, if youre a good programmers with the good libraries, the advantages far outweighs the inconveniences and
makes JavaScript and the browser one of the best environment to develop applications for the general public.

2.2 Command Line Interpreter


To test the basic features of the language, we recommend you to begin by using a command line interpreter. Most
modern web browsers will provide a console to use JavaScript, but it is recommended to use Google Chrome for
OpenERPs web module development. All this guide will assume you this particular browser.
Once Chrome is installed, open any web page then go in the configuration menu of Chrome and select Tools >
Developer Tools or use Ctrl + Shift + I. This should reveal a new section at the bottom of the window. Now select the
Console panel. You should have a screen looking like this:

You will now be able to test the code snippets given in the next part.

2.3 Basic Data Types


2.3.1 Numbers
> ((3 + 3) / 1.5) * 2;
8

Please note JavaScript do not have integers. All numbers are floats. This is a major difference with most other
programming languages. This has impacts on some mathematical operations. Example:
> 3 / 2;
1.5

In C, Java or Python the result would be 1, except if one of the members was casted to float or explicitly declared as
float using a different notation (2.0 or 2.).

Chapter 2. JavaScript Basics

OpenERP Web Training, Release 1.0

2.3.2 Booleans
> 5 == 2;
false
> true == true;
true
> true == false;
false

As simple as booleans can be.

2.3.3 Strings
> "Hello World";
"Hello World"
> Hello World;
"Hello World"

Strings can be declared using single quotes or double quotes. Like most high level programming languages, strings
have methods for many operations.
> "Hello World".charAt(3);
"l" // like Python, there is not char type in JavaScript, a char is a string of size 1
> "Hello World".slice(6, 9);
"Wor" // slice() is used to obtain a sub-string
> "Hello World".length;
11

Strings also use the + operator for concatenation:


> "Hello " + "World";
"Hello World"

2.3.4 Null
> var x = null;
> x;
null

Similar to a lot of languages or None in Python.

2.3.5 Undefined
If you declare a variable but do not assign it it will have a special value. This value is not the same as null.
> var x;
> x;
undefined

2.4 Implicit Type Conversions


JavaScript provides automatic type conversion for most operators.

2.4. Implicit Type Conversions

OpenERP Web Training, Release 1.0

> "Test" + 5;
"Test5"

Practically that behavior can be useful in some cases, like implicit conversion to strings, but can also create strange
behaviors, the typical case being comparisons. Here are some examples:
> "5" == 5;
true
> "" == 0;
true
> "0" == false;
true

Like in C, numbers are considered as false if they are 0 and true otherwise. Strings are considered as true
except if they are empty.
During any operations involving different types, multiple castings can occur which are quite complicated to predict
for the programmer. Thats why it is considered safer to always use the === and !== operators.
> "5" === 5;
false
> "" === 0;
false
> "0" === false;
false

These operators will always return false if the types to compare are different.

2.5 Control Structures


JavaScript provides the same control structures than C. (To test with Chromes console, you can use Shift + Enter to
enter multiple lines of code.)
> if (true == true) {
console.log("true is true");
}
true is true
> var x = 0;
> while (x < 3) {
console.log(x);
x++;
}
1
2
3
> for (var i = 5; i < 8; i++) {
console.log(i);
}
5
6
7

JavaScript also provides a specialized for structure to loop on objects. (for (... in ...).) It should be noted
that, due to bad conception of the language involving variable scopes, functional programming, performances issues
and bad behavior with arrays, almost all experienced programmers will avoid the usage of that structure and rather use
functions provided by non-standard libraries like jQuery or Underscore. We recommend you to use _.each provided
by Underscore most of the time. (More information about that library is given later in this document.)

Chapter 2. JavaScript Basics

OpenERP Web Training, Release 1.0

2.6 Functions
Functions can be declared like this:
> function talk() {
console.log("Hello World");
}
> talk();
Hello World

In JavaScript, functions are also a complete type by themselves. They can be declared as expressions and stored in
variables.
> var talk = function() {
console.log("Hello World");
}
> talk();
Hello World
> var talkAgain = talk;
> talkAgain();
Hello World
> function executeFunc(func) {
func();
}
> executeFunc(talk);
Hello World

Function arguments are declared like in most languages, except without types. Please note that the JavaScript virtual
machine never checks the number of arguments when a function is called. If there are more arguments the function
will be called anyway, if there are fewer arguments the remaining ones will be undefined.
> var print = function(a, b) {
console.log(a);
console.log(b);
}
> print("hello");
hello
undefined
> print("nice", "to", "meet", "you");
nice
to

2.7 Variables and Scopes


A variable is declared by preceding its name by var. Unlike C++ and Java, a scope is not defined by the existence of
braces. A scope is defined by a function.
> function func1() {
var x; // x is inside the scope of func1
function func2() { // func2 is inside the scope of func1
var y; // y is in the scope of func2
}
while (true) {
var z; // z is not in a new scope, it is the same scope than x
}
}

2.6. Functions

OpenERP Web Training, Release 1.0

In this example, z is not a variable which is re-created at each iteration of the while loop, it is always the same
variable for each iteration because the variable is defined in the scope of func1.
Functions can also access the variables defined above them.
> function func1() {
var x = "hello";
function func2() {
console.log(x);
}
func2();
x = "world";
func2();
}
> func1();
hello
world

When a variable is declared directly at the root of a source file, not inside any function, it exists in the global scope.
Unlike Python, the global scope is not a scope specific to each source file. The global scope is shared amongst all
pieces of JavaScript code that are executed by an instance of the virtual machine, which means on the same web page.
// file source1.js
var x = "value1";
// file source2.js
var x = "value2";

If those two files are loaded by the same web page the variable x can only have one value, "value1" or "value2",
depending on which file was loaded last. This is obviously a problem and it can be solved using the Module Pattern
(see later).
One last note about scopes is that any value which is assigned but not declared will be implicitly considered as being
part of the global scope. This means if you ever forget to use the var keyword the code will not crash but the variable
will be global to the application instead of local to the current function. This is a very common source of errors in
JavaScript.
> function func1() {
x = "hello";
}
> function func2() {
console.log(x);
}
> func2();
ReferenceError: x is not defined
> func1();
> func2();
hello
> x = "world";
> func2();
world

2.8 Arrays
The syntax of the arrays is pretty similar to Python:

10

Chapter 2. JavaScript Basics

OpenERP Web Training, Release 1.0

> var array = ["hello", "world"];


> for (var i = 0; i < array.length; i++) {
console.log(array[i]);
}
hello world

Please note the above syntax to iterate on arrays works but is ineffective, for real use-cases you should use _.each()
or a similar function provided by a third-party library.
Like strings, the arrays have methods for different operations:
> var array = [];
> array.push("banana"); // adds an element at the end
> array.push("tomato");
> array;
["banana", "tomato"]
> array.pop(); // removes the last element and returns it
"tomato"
> array;
["banana"]

2.9 Objects
Object-oriented programming is possible in JavaScript, but it is very different compared to most other programming
language (except if you know Lua).
First of all, objects are dictionaries and dictionaries are objects. There is no difference in JavaScript. The syntax is
quite similar to Pythons dictionaries but has alternative syntactic sugar depending if you prefer use a dictionary-like
or an object-like syntax. Demonstration:
> var obj = {
"key1": "hello", // dictionary-like declaration
key2: "world", // object-like declaration
};
> console.log(obj["key1"]); // dictionary-like lookup
hello
> console.log(obj.key2); // object-like lookup
world

obj["key"] and obj.key have the exact same meaning. The first one will, by convention, be mostly used if you
want to make a lookup in a dictionary and the second one will be mostly used to access an objects attribute.
Methods can simply be defined by putting a function inside an object:
> var person = {
name: "John Smith",
presentYourself: function() {
return "Hello, my name is " + this.name;
},
};
> person.presentYourself();
"Hello, my name is John Smith"
> person.name = "John Doe";
> person.presentYourself();
"Hello, my name is John Doe"

In JavaScript, each time a function is called it has and additional, implicitly declared variable called this. When a
method is called on an object (using the usual object.method(arguments...) syntax), the this variable
2.9. Objects

11

OpenERP Web Training, Release 1.0

will be a reference to the current object.


In the above example, we define a unique object containing all the attributes and methods necessary to make it work.
But thats not how most programming languages will handle object-oriented programming. They have a concept of
class. A class will contain the properties common to all its instances. There are no classes in JavaScript but it is
possible to reproduce that concept using prototypes.
> var Person = function() { // this function will represent a class "Person"
this.name = "JohnSmith";
};
> Person.prototype = {
presentYourself: function() {
return "Hello, my name is " + this.name;
},
};
> var person = new Person();
> person.presentYourself();
"Hello, my name is John Smith"

Since prototype-based oriented object programming is a vast subject we will not cover it more in this guide, though
you can easily find some information about it on Internet.
Due to differences between prototype-based and class-based oriented object programming, and the habits of most
programmers, in OpenERP we chose to not use directly prototype-based oriented object programming. We use a high
level API that allows programmers to easily declare classes in a way that seems natural to people that are used to more
conventional programming languages. That subject will be covered later in this guide.

12

Chapter 2. JavaScript Basics

CHAPTER

THREE

JAVASCRIPT LIBRARIES

3.1 A First JavaScript Application


3.1.1 Download and Launch the Start Application
This is now time to stop using the Chromes command line interpreter, and start a complete JavaScript application.
We provide a basic structure for a sample application that you should download to continue reading this guide. That
sample application is located in a Bazaar branch. In the case you dont have Bazaar installed, you can type this
command in your Linux shell:
> sudo apt-get install bzr

Now you can download the sample application using:


> bzr branch lp:~niv-openerp/+junk/basicjssite jstraining -r 1

To demonstrate communication between a JavaScript web application and a server process, that sample application
integrates a minimalist Python web server. That web server may require some dependencies to install, you can do it
like so:
> sudo apt-get install python
> sudo easy_install flask

You can now launch that Python web application using this command:
> python app.py

Now you should be able to use your web browser to access the web server at the url http://localhost:5000. The web
page should look like this:

13

OpenERP Web Training, Release 1.0

3.1.2 Architecture of the Application


Lets take a look at the only HTML file of the application which is located in static/index.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="static/css/app.css" type="text/css"></link>
<script type="application/javascript" src="/static/js/jquery.js"></script>
<script type="application/javascript" src="/static/js/underscore.js"></script>
<script type="application/javascript" src="/static/js/app.js"></script>
<script type="application/javascript">
$(function() {
app.main();
});
</script>
</head>
<body>
<div class="main_block">
<div class="main_content">
</div>
<div class="right_side">
<button>Click Me</button>
</div>
</div>
</body>
</html>

This web page contains a few HTML elements which are styled by the static/css/app.css css file.
This HTML file also import three JavaScript files. Two of those are well-known libraries in the JavaScript community:
jQuery and Underscore. The last JavaScript file, /static/js/app.js is the file that will contain the logic of this
application.
The last <script/> block is a little harder to understand:
The call to the enigmatic $ function, to which we pass an anonymous function. The $ function is part of the

14

Chapter 3. JavaScript Libraries

OpenERP Web Training, Release 1.0

jQuery library. This function can have multiple uses that will be explained later. For now, just remember that
when you call it with that precise syntax it will execute a piece of JavaScript code once the browser has finished
loading the HTML file. This is the right time if we want to modify the content with JavaScript code.
The function app.main is part of our JavaScript file. See the next section.

3.1.3 The Module Pattern


As stated earlier in this guide, when you simply declare a variable in JavaScript, outside of any function, it will be a
global variable:
var my_var = "my value";

This means the my_var variable will be accessible in all the JavaScript files imported by the HTML file. This is
generally considered a bad programming style so we will try to avoid it.
Instead we will use what is known as the Module Pattern. It is a pattern used to declare variables and functions that
will be local to a file. Here is what it looks like for the app.js file:
(function() {
app = {};
function main() {
console.log("launch application");
};
app.main = main;
})();

We can decompose the Module Pattern this way:


All the declarations of the file are wrapped in an anonymous function which is directly called.
Now we can declare anything we want inside the function. Any var or function declaration will be local to
the modules function and thus it will be a private member of our module.
We declare one and only one global variable. That variable is a dictionary and it is declared using the syntax
app = {};. Remember: when you declare a variable in JavaScript without using the keyword var it will be
a global variable. The app dictionary is the namespace of our JavaScript module.
To expose anything to the outside of the module, to make it public, we add it to the namespace app.
In this module, we only have one public function: main. If you take a look in the HTML file you will see how to call
that function:
app.main();

The only thing this function does right now is printing a message in the debugging console.

3.1.4 Debugging Tools


Like in a lot of dynamic languages, in JavaScript it is very useful for development to be able to debug your code. But,
unlike languages like Java or C, most JavaScript editors do not provide a system to mark debug points. This can be
replaced with the debugger keyword. Modify your source to add that keyword before the call to console.log():

3.1. A First JavaScript Application

15

OpenERP Web Training, Release 1.0

(function() {
app = {};
function main() {
debugger;
console.log("launch application");
};
app.main = main;
})();

Launch the application and open the developer tools (Ctrl+Shift+I or select Tools > Developer Tools
in Chromes menu). Now reload the application using F5.

When the developer tools window is shown, Chrome will automatically stop when it reaches a debugger keyword
and jump to the Sources tab. Now you can use the debuggers buttons on the top right to advance step, resume, etc...
Also note you can put debugging marks in the debugger by clicking on the line numbers on the left, but this can be
less ergonomic if you were editing code in your text editor.
Another noteworthy debugging tool is the console.log() function. It will print messages that can be read in the
Console tab of the developer tools.

3.2 Underscore.js
Underscore.js is a library providing various utility functions for JavaScript. It is heavily used in OpenERP and we
recommend you to learn how to use it. Underscore.js functions are all wrapped in a namespace contained in the _
variable.
One the most used function of Underscore is _.each(). As explained in the JavaScript Basics part of this guide, it
is considered a bad practice to use the for loop of JavaScript. _.each() is a good replacement for it. Example:
var array = ["hello", "world"]
_.each(array, function(x) {
console.log(x);
});
// outputs:
// hello
// world

The first argument of _.each() must be an array or a dictionary. The second one must be a function. When called
with an array as first argument, the function will receive one parameter: the current value of the array.
When called with a dictionary, the function will receive two parameter. The first one is the current value and the
second one the current key. Example:

16

Chapter 3. JavaScript Libraries

OpenERP Web Training, Release 1.0

var dict = {"banana": 2, "potato": 3, "apple": 5};


_.each(dict, function(v, k) {
console.log(k, v);
});
// outputs:
// banana 2
// potato 3
// apple 5

Another example of a useful function in underscore is _.range(). It generates a list of numbers:


console.log(_.range(0, 5));
// outputs:
// [0, 1, 2, 3, 4]

Exercise - Usage of Underscore.js


In the main() function of the start application, create a piece a code that will add all numbers from 1 to 100
and print the result in the console. Use _.each() and _.range().
Solution:
(function() {
app = {};
function main() {
var x = 0;
_.each(_.range(1, 101), function(i) {
x += i;
});
console.log("Result", x);
};
app.main = main;
})();

3.3 HTML Manipulations with jQuery


jQuery is a JavaScript library just like Underscore whose main goal is to provide an abstraction layer over the web
browsers API to help common operations and improve compatibility between different browsers. One of the main
usages of jQuery is to help the developers to manipulate the HTML displayed in the browser.

3.3.1 The DOM


The DOM (Document Object Model) is a conceptual representation of the content of the web page. When a JavaScript
piece of code wants to modify the visual content of a page it will modify the DOM, and the browser will alter the
graphical aspect in consequence.
Use Chromes developer tools, you can consult the actual state of the DOM using the Elements tab:

3.3. HTML Manipulations with jQuery

17

OpenERP Web Training, Release 1.0

If we execute a simple JavaScript code in the Console tab to modify the DOM, we will see the modifications in the
Elements tab as well as in the browser itself. Example:
$("body").text("I changed the DOM")

This piece of code will erase the content of the web page and put the text I Changed the DOM instead. You can also
see the modifications in the DOM explorer:

If you want, you can also use the Elements tab to modify the DOM. Than can sometimes be useful to test multiple
layouts as example.

3.3.2 jQuery Selectors


To select a part of the DOM, we use what is called a jQuery selector. To do so, we call the jQuery method and give it
a string as argument:
$("body")

When the jQuery library is loaded in a project, it defines two variables: jQuery and $. These two variables are in
reality the exact same thing: the jQuery function. When called with as first argument, it will try to find one or more
elements in the DOM that match the given expression.
A lot of readers could already know XPath, which is an language used to define expression to search elements in XML.
That language could have been chosen by jQuerys creators to select HTML elements, but they decided to use another
syntax that is more similar to CSS.
18

Chapter 3. JavaScript Libraries

OpenERP Web Training, Release 1.0

To select all elements with a certain tag you can just put the name of the tag in the expression:
$("input") // all <input> elements
$("div") // all <div> elements

To select an element with a precise id you must put the # character in front of the identifier:
$("#content") // the element with id content

The select all the elements that have a precise CSS class you must use the . character:
$(".title") // the elements with the title class

Just like in CSS you can also combine these selectors:


$("span.title") // all <span> elements with the title class
$("#content table") // all <table> elements that are children of the element with id content

When the $() is called, it will return a jQuery object. That jQuery object can be seen as an array with a pointer to all
the elements in the DOM that were matched by given expression, except it has a lot of useful methods.
More documentation about the jQuery
http://api.jquery.com/category/selectors/ .

selectors

can

be

found

in

the

jQuery

documentation:

3.3.3 jQuery Events


Most HTML elements are able to produce events. jQuery can make it easy to catch these events and execute a piece
of JavaScript code. In the following example we catch the click event on the button displayed on the page to print a
message in the console:
$("button").click(function() {
console.log("someone clicked on the button");
});

Here we pass a function to the click() method on the jQuery object. When the event is fired, the function will be
called. Please note that, if there are multiple elements selected by our call of $(), the click event will be bound to
all these buttons.
There is a wide array of events that can be fired, e.g. the mouse passing over an element, the user pushing a key on the
keyboard, an <input> element being modified, etc... More documentation about events can be found in the jQuery
documentation: http://api.jquery.com/category/events/

3.3.4 DOM Modifications with jQuery


jQuery provides many functions that can modify DOM elements.
To replace the content of a jQuery tag and put new HTML inside it, you can use the html() method:
$(".main_content").html(<div style="color: white">Hello world!</div>);

To avoid replacing the whole content and simply add some HTML at the end of the element, use the append()
method:
$(".main_content").append(<div style="color: red">Hello world again!</div>);

To do the opposite, put some HTML at the beginning of the element, use prepend():

3.3. HTML Manipulations with jQuery

19

OpenERP Web Training, Release 1.0

$(".main_content").prepend(<div style="color: green">Hello world, I am the first one!</div>);

To put some text in an element it is a bad idea to use the html() method, because that text could contain text that
looks like HTML and the browser could try to interpret it as real HTML. To avoid that the text must be escaped, this
can be done automatically by the text() method:
$(".main_content").text(The <div> element will appear as-is in the browser.);

3.3.5 Exercise
Exercise - Usage of jQuery
Modify the code of the start application so, when the user click on the button, it counts from 1 to 10 and prints
each number on a separate line in the area on the left.
Solution:
(function() {
app = {};
function main() {
$("button").click(function() {
_.each(_.range(1, 11), function(i) {
$(".main_content").append("<div>" + i + "</div>");
});
});
};
app.main = main;
})();

3.4 HTTP Requests with jQuery


In database-centric applications like OpenERP, JavaScript code needs to communicate with the server. jQuery provides
a function to do so.
We will also use the JSON format. JSON stands for JavaScript Object Notation. It is a lightweight format used
to encode data to be exchanged between different computers. Due to its simplicity, it is very easy to implement
in any programming language and the vast majority of existing programming languages already have one or more
implementations of a JSON reader/writer. Here is a simple example that demonstrates the totality of JSON data types:
{
"string": "this is a string",
"number": 42,
"array": [null, true, false],
"dictionary": {"name": "a simple dictionary"}
}

To test the HTTP requests in JavaScript, we will use the app.py Python application that contains some JSON web
services:

20

Chapter 3. JavaScript Libraries

OpenERP Web Training, Release 1.0

@app.route(/service_plus, methods=["POST"])
def service_plus():
data = flask.request.json
a = data["a"]
b = data["b"]
delay = data.get("delay", 0)
time.sleep(delay)
return flask.jsonify(**{
"addition": a + b,
})
@app.route(/service_mult, methods=["POST"])
def service_mult():
data = flask.request.json
a = data["a"]
b = data["b"]
delay = data.get("delay", 0)
time.sleep(delay)
return flask.jsonify(**{
"multiplication": a * b,
})

It would be out of the scope of this guide to explain how works the Flask Python library which is used to create these
web services in Python. Just remember that this code declare two URLs that accept and return JSON and perform
simple mathematic operations (addition and multiplication). We will code some JavaScript that contact these web
services.

3.4.1 The $.ajax() method


The method used to perform an HTTP request in jQuery is the $.ajax() method. The AJAX acronym meaning
Asynchronous JavaScript and XML, a name which loosed its signification nowadays because developers use it to send
other data than XML. Anyway, that term is still used in jQuerys documentation.
Here is an example which will call the /service_plus web service:
$.ajax("/service_plus", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": 3,
"b": 5,
}),
contentType: "application/json",
}).then(function(a) {
console.log("3+5=", a.addition);
});

The first argument of $.ajax() is the URL to send the request.


The second argument is a dictionary that can contains a lot of parameters. Here are the parameters we use:
type is the type of HTTP request. Here we use the POST method.
dataType:
and return

"json" is used to inform jQuery that the server will return JSON, so it can read that JSON

JavaScript objects instead of a simple string. * data is the data we send to the server. Here we want to send a JSON
string containing a dictionary with two keys a and b. We have to call the method JSON.stringify, a standard

3.4. HTTP Requests with jQuery

21

OpenERP Web Training, Release 1.0

method in JavaScript to convert objects to JSON strings. * contentType is the MIME type of the data we send to
the server. The value we use informs the server that we are sending JSON.
The $.ajax() returns what is called a promise. Promises are objects created by jQuery that we will explain later.
For now, just understand that when we call the then() method on that promise it will bind a function to be called
when we receive the result from the server. That first argument of that function is the JSON object returned by the
/service_plus web service.
The $.ajax() contains a lot more parameters to send any type of HTTP requests. To know more about them, please
read jQuerys documentation: http://api.jquery.com/jQuery.ajax/ .

3.4.2 Promises and Deferreds


As a language (and runtime), JavaScript is fundamentally single-threaded. This means any blocking request or computation will block the whole page (and, in older browsers, the software itself even preventing users from switching to
an other tab): a JavaScript environment can be seen as an event-based runloop where application developers have no
control over the runloop itself.
As a result, performing long-running synchronous network requests or other types of complex and expensive accesses
is frowned upon and asynchronous APIs are used instead.
Asynchronous code rarely comes naturally, especially to developers used to synchronous server-side code (in Python,
Java or C#) where the code will just block until the deed is gone. This is compounded by asynchronous programming not being a first-class concept and being implemented using callbacks-based programming, which is the case in
JavaScript.
This part will provide some tools to deal with asynchronous systems, and warn against common issues and pitfalls.
Deferreds are a form of promises. OpenERP Web currently uses jQuerys deferreds.
The core idea of deferreds is that potentially asynchronous methods will return a Deferred() object instead of an
arbitrary value or (most commonly) nothing. Deferreds can be seen as a promise to a value or error. This object can
then be used to track the end of the asynchronous operation by adding callbacks onto it, either success callbacks or
error callbacks.
A great advantage of deferreds over simply passing callback functions directly to asynchronous methods is the ability
to compose them.
Deferredss most important method is $.Deferred.then(). It is used to attach new callbacks to the deferred
object. The first parameter attaches a success callback, called when the deferred object is successfully resolved and
provided with the resolved value(s) for the asynchronous operation.
$.ajax(...).then(function(a) {
console.log("Asynchronous operation completed, the value is:", a);
});

The second parameter attaches a failure callback, called when the deferred object is rejected and provided with rejection values (often some sort of error message).
$.ajax(...).then(function(a) {
...
}, function() {
console.error("The asynchronous operation failed");
});

Callbacks attached to deferreds are never lost: if a callback is attached to an already resolved or rejected deferred,
the callback will be called (or ignored) immediately.
To demonstrate this we can create our own $.Deferred instance and call the resolve() method to resolve it:

22

Chapter 3. JavaScript Libraries

OpenERP Web Training, Release 1.0

var def = $.Deferred();


def.resolve();
def.then(function() {
console.log("operation succeeded");
});
// the message "operation succeeded" will appear, even if the deferred was already resolved when we c

A deferred is also only ever resolved or rejected once, and is either resolved or rejected: a given deferred can not call
a single success callback twice, or call both a success and a failure callbacks. Example:

var def = $.Deferred();


def.then(function() {
console.log("operation succeeded");
});
def.resolve();
def.resolve();
// the message "operation succeeded" will appear only once in the console, because the second call to
// will not make the deferred call its binded functions a second time

3.4.3 Composing Deferreds


Deferreds truly shine when code needs to compose asynchronous operations in some way or other, as they can be used
as a basis for such composition.
There are two main forms of compositions over deferred: multiplexing and chaining.
Deferred Multiplexing
The most common reason for multiplexing deferred is simply performing 2+ asynchronous operations and wanting to
wait until all of them are done before moving on (and perform more operations).
The jQuery multiplexing function for promises is $.when(). This function can take any number of promises and
will return a new promise. This returned promise will be resolved when all multiplexed promises are resolved, and
will be rejected as soon as one of the multiplexed promises is rejected.
var def1 = $.ajax("/service_plus", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": 3,
"b": 5,
}),
contentType: "application/json",
});
var def2 = $.ajax("/service_plus", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": 6,
"b": 7,
}),
contentType: "application/json",
});
$.when(def1, def2).then(function(result1, result2) {

3.4. HTTP Requests with jQuery

23

OpenERP Web Training, Release 1.0

console.log("3+5=", result1[0].addition);
console.log("6+7=", result2[0].addition);
});

The arguments given to the function bound to the promise returned by $.when() are a little complicated. For each
promise passed to $.when(), the function will receive a parameter. Each parameter is an array containing all the
parameters that would have been passed to a function bound on the original deferred. So, if we want the first parameter
of that function we have to use result1[0].
Deferred Chaining
A second useful composition is starting an asynchronous operation as the result of an other asynchronous operation,
and wanting the result of the second one.
To do so, the function you bind to the deferred using then() must return another deferred. Example:
var def1 = $.ajax("/service_mult", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": 3,
"b": 5,
}),
contentType: "application/json",
}).then(function(result) {
var def2 $.ajax("/service_mult", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": result.multiplication,
"b": 7,
}),
contentType: "application/json",
});
return def2;
});
def1.then(function(result) {
console.log("3*5*7=", result.multiplication);
});

To understand why this works, it is important to know that then() returns a new promise. That promise represents
the result of the execution of the given function after the deferred was resolved. If the bound function returns a new
promise the result of the call to then() becomes an equivalent of that new promise. So, in the code above def1 and
def2 are promises which will be resolved at the same time and with the same arguments (except if the first call to
ajax() fails, in that case the second call to ajax() will never happen and def2 will be marked as rejected).
Best Practices When Using Asynchronous Code
In real-world situations asynchronous calls can result in fairly complex code: big programs can call hundreds of
functions. Even if only a small number of those functions perform asynchronous operations they will affect the whole
program. This is why jQuerys deferreds are very useful: they provide a generic structure for asynchronous code
execution and, thanks to deferreds composition, it becomes possible to simplify any situation to a single deferred.
A simple practice to remember when using deferreds is to always return a deferred in all functions that perform
asynchronous operations. This is due to the fact that any other function calling that function could need to know when

24

Chapter 3. JavaScript Libraries

OpenERP Web Training, Release 1.0

it has finished.
function func1() {
var def1 = $.ajax(...); // A first call to the server.
var def2 = $.ajax(...); // A second call.
var def3 = $.when(def1, def2); // We multiplex all that.
var def4 = def3.then(...); // Some more complexity: we chain it.
// Now we dont forget to return a deferred that represents the complete operation.
return def4;
};
function func2() {
var def = func1(); // We want to call func1().

// Now if I need to know when func1() has finished all its operations I have a deferred that repr
var def2 = def.then(...);

// And finally we dont forget to return a deferred because func2() is, by transitivity, a functi
// that performs an asynchronous call. So it should return a deferred too.
return def2;
};

3.4.4 Exercises
Exercise - Usage of Deferreds
Create a function that takes 4 parameters: a, b, c and d. It must return a deferred resolved with the result of the
mathematical operation (a + b) * (c + d). Do not use any of the mathematical operators of JavaScript,
you must use the /service_plus and /service_mult web services and combine deferreds properly to
return a single deferred with the result of the whole operation.
Solution:
(function() {
app = {};
function main() {
$("button").click(function() {
plusmultplus(1, 2, 3, 4).then(function(result) {
console.log("(1+2)*(3+4)=", result.multiplication);
});
});
};
app.main = main;
function plusmultplus(a, b, c, d) {
var def1 = $.ajax("/service_plus", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": a,
"b": b,
}),

3.4. HTTP Requests with jQuery

25

OpenERP Web Training, Release 1.0

contentType: "application/json",
});
var def2 = $.ajax("/service_plus", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": c,
"b": d,
}),
contentType: "application/json",
});
return $.when(def1, def2).then(function(result1, result2) {
return $.ajax("/service_mult", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": result1[0].addition,
"b": result2[0].addition,
}),
contentType: "application/json",
});
});
};
})();

26

Chapter 3. JavaScript Libraries

CHAPTER

FOUR

OPENERP WEB FRAMEWORK

In the first part of this guide, we explained the JavaScript language and some libraries commonly used with it (jQuery
and Underscore.js). Still, we lack some serious features to be able to program effectively for a big program like
OpenERP. We dont really have tools for object oriented programming, no structure for graphical user interfaces
conception, our helpers to request data from the server are too basic, etc...
Thats why the OpenERP web client contains a web development framework to provide structure and the necessary
features for everyday programming. The current part will explain that framework.

4.1 A Simple Module to Test the Web Framework


Its not really possible to include the multiple JavaScript files that constitute the OpenERP web framework in a simple
HTML file like we did in the previous chapter. So we will create a simple module in OpenERP that contains some
configuration to have a web component that will give us the possibility to test the web framework.
To download the example module, use this bazaar command:
bzr branch lp:~niv-openerp/+junk/oepetstore -r 1

Now you must add that folder to your the addons path when you launch OpenERP (--addons-path parameter when you launch the openerp-server executable). Then create a new database and install the new module
oepetstore.
Now lets see what files exist in that module:
oepetstore
|-- __init__.py
|-- __openerp__.py
|-- petstore_data.xml
|-- petstore.py
|-- petstore.xml
-- static
-- src
|-- css
|
-- petstore.css
|-- js
|
-- petstore.js
-- xml
-- petstore.xml

This new module already contains some customization that should be easy to understand if you already coded an
OpenERP module like a new table, some views, menu items, etc... Well come back to these elements later because
they will be useful to develop some example web module. Right now lets concentrate on the essential: the files
dedicated to web development.

27

OpenERP Web Training, Release 1.0

Please note that all files to be used in the web part of an OpenERP module must always be placed in a static folder
inside the module. This is mandatory due to possible security issues. The fact we created the folders css, js and
xml is just a convention.
oepetstore/static/css/petstore.css is our CSS file. It is empty right now but we will add any CSS we
need later.
oepetstore/static/xml/petstore.xml is an XML file that will contain our QWeb templates. Right now
it is almost empty too. Those templates will be explained later, in the part dedicated to QWeb templates.
oepetstore/static/js/petstore.js is probably the most interesting part. It contains the JavaScript of our
application. Here is what it looks like right now:
openerp.oepetstore = function(instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.oepetstore = {};
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
console.log("pet store home page loaded");
},
});
instance.web.client_actions.add(petstore.homepage, instance.oepetstore.HomePage);
}

The multiple components of that file will explained progressively. Just know that it doesnt do much things right now
except display a blank page and print a small message in the console.
Like OpenERPs XML files containing views or data, these files must be indicated in the __openerp__.py file.
Here are the lines we added to explain to the web client it has to load these files:
js: [static/src/js/*.js],
css: [static/src/css/*.css],
qweb: [static/src/xml/*.xml],

These configuration parameters use wildcards, so we can add new files without altering __openerp__.py: they
will be loaded by the web client as long as they have the correct extension and are in the correct folder.
Warning: In OpenERP, all JavaScript files are, by default, concatenated in a single file. Then we apply an
operation called the minification on that file. The minification will remove all comments, white spaces and linebreaks in the file. Finally, it is sent to the users browser.
That operation may seem complex, but its a common procedure in big application like OpenERP with a lot of
JavaScript files. It allows to load the application a lot faster.
It has the main drawback to make the application almost impossible to debug, which is very bad to develop. The
solution to avoid this side-effect and still be able to debug is to append a small argument to the URL used to load
OpenERP: ?debug. So the URL will look like this:
http://localhost:8069/?debug

When you use that type of URL, the application will not perform all that concatenation-minification process on the
JavaScript files. The application will take more time to load but you will be able to develop with decent debugging
tools.

28

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

4.2 OpenERP JavaScript Module


In the previous chapter, we explained that JavaScript do not have a correct mechanism to namespace the variables
declared in different JavaScript files and we proposed a simple method called the Module pattern.
In OpenERPs web framework there is an equivalent of that pattern which is integrated with the rest of the framework.
Please note that an OpenERP web module is a separate concept from an OpenERP addon. An addon is a folder
with a lot of files, a web module is not much more than a namespace for JavaScript.
The oepetstore/static/js/petstore.js already declare such a module:
openerp.oepetstore = function(instance) {
instance.oepetstore = {};
instance.oepetstore.xxx = ...;
}

In OpenERPs web framework, you declare a JavaScript module by declaring a function that you put in the global
variable openerp. The attribute you set in that object must have the exact same name than your OpenERP addon
(this addon is named oepetstore, if I set openerp.petstore instead of openerp.oepetstore that will
not work).
That function will be called when the web client decides to load your addon. It is given a parameter named instance,
which represents the current OpenERP web client instance and contains all the data related to the current session as
well as the variables of all web modules.
The convention is to create a new namespace inside the instance object which has the same name than you addon.
Thats why we set an empty dictionary in instance.oepetstore. That dictionary is the namespace we will use
to declare all classes and variables used inside our module.

4.3 Classes
JavaScript doesnt have a class mechanism like most object-oriented programming languages. To be more exact, it
provides language elements to make object-oriented programming but you have to define by yourself how you choose
to do it. OpenERPs web framework provide tools to simplify this and let programmers code in a similar way they
would program in other languages like Java. That class system is heavily inspired by John Resigs Simple JavaScript
Inheritance.
To define a new class, you need to inherit from the instance.web.Class class. Here is the syntax to do so:
instance.oepetstore.MyClass = instance.web.Class.extend({
say_hello: function() {
console.log("hello");
},
});

As you can see, you have to call the instance.web.Class.extend() method and give it a dictionary. That
dictionary will contain the methods and class attributes of our new class. Here we simply put a method named
say_hello(). This class can be instantiated and used like this:
var my_object = new instance.oepetstore.MyClass();
my_object.say_hello();
// print "hello" in the console

You can access the attributes of a class inside a method using this:

4.2. OpenERP JavaScript Module

29

OpenERP Web Training, Release 1.0

instance.oepetstore.MyClass = instance.web.Class.extend({
say_hello: function() {
console.log("hello", this.name);
},
});
var my_object = new instance.oepetstore.MyClass();
my_object.name = "Nicolas";
my_object.say_hello();
// print "hello Nicolas" in the console

Classes can have a constructor, it is just a method named init(). You can pass parameters to the constructor like in
most language:
instance.oepetstore.MyClass = instance.web.Class.extend({
init: function(name) {
this.name = name;
},
say_hello: function() {
console.log("hello", this.name);
},
});
var my_object = new instance.oepetstore.MyClass("Nicolas");
my_object.say_hello();
// print "hello Nicolas" in the console

Classes can be inherited. To do so, use the extend() directly on your class just like you extended the
instance.web.Class class:
instance.oepetstore.MySpanishClass = instance.oepetstore.MyClass.extend({
say_hello: function() {
console.log("hola", this.name);
},
});
var my_object = new instance.oepetstore.MySpanishClass("Nicolas");
my_object.say_hello();
// print "hola Nicolas" in the console

When overriding a method using inheritance, you can use this._super() to call the original method.
this._super() is not a normal method of your class, you can consider its magic. Example:
instance.oepetstore.MySpanishClass = instance.oepetstore.MyClass.extend({
say_hello: function() {
this._super();
console.log("translation in Spanish: hola", this.name);
},
});
var my_object = new instance.oepetstore.MySpanishClass("Nicolas");
my_object.say_hello();
// print "hello Nicolas \n translation in Spanish: hola Nicolas" in the console

30

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

4.4 Widgets Basics


In previous chapter we discovered jQuery and its DOM manipulation tools. Its useful, but its not sufficient to structure
a real application. Graphical user interface libraries like Qt, GTK or Windows Forms have classes to represent visual
components. In OpenERP, we have the Widget class. A widget is a generic component dedicated to display content
to the user.

4.4.1 The Client Action


The start module you installed already contains a small widget, its also what we call a client action. See the
code:
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
console.log("pet store home page loaded");
},
});
instance.web.client_actions.add(petstore.homepage, instance.oepetstore.HomePage);

Here we create a simple widget by extending the instance.web.Widget class. This one defines a method named
start() that doesnt do anything really interesting right now. Well come back on that method later.
The last line registers our basic widget as a client action. To understand the usefulness of this, take a look at the
OpenERP action definition located at the beginning of the oepetstore/petstore.xml OpenERP XML data
file:
<record id="action_home_page" model="ir.actions.client">
<field name="name">Inbox</field>
<field name="tag">petstore.homepage</field>
</record>
<menuitem id="home_page_petstore_menu" name="Home Page" parent="petstore_menu"
action="action_home_page"/>

If you are an OpenERP addon programmer, should already know at least one type of action: the action act_window.
That type of action displays an OpenERP view like a form view, a list view or any other type of standard OpenERP
view. Here we declare a different type of OpenERP action: a client action.
Client actions are dedicated to be used with a web module part. That action simply tells OpenERPs web client open
the client action identified by the key petstore.homepage. Lets come back to the JavaScript code that declared
our client action:
instance.web.client_actions.add(petstore.homepage, instance.oepetstore.HomePage);

Calling the instance.web.client_actions.add() method like this simply tells the web
client If someone asks you to open a client action with key petstore.homepage, instantiate the
instance.oepetstore.HomePage class and show it to the user.
Due to this declaration, if you go in the menu Pet Store > Pet Store > Home Page you will see an empty
page, but the JavaScript console will print pet store home page loaded. This means the widget was correctly loaded
and we can start to use it to test OpenERPs widgets.

4.4. Widgets Basics

31

OpenERP Web Training, Release 1.0

4.4.2 Display Content


Widgets have a lot of methods and features, but lets start with the basics: display some data inside the widget and
how to instantiate a widget and display it.
The HomePage widget already has a start() method. That method is automatically called after the widget has
been instantiated and it has received the order to display its content. We will use it to display some content to the user.
To do so, we will also use the $el attribute that all widgets contain. That attribute is a jQuery object with a reference
to the HTML element that represent the root of our widget. A widget can contain multiple HTML elements, but they
must be contained inside one single element. By default, all widgets have an empty root element which is a <div>
HTML element.
A <div> element in HTML is usually invisible for the user if it does not have any content. That explains why when
the instance.oepetstore.HomePage widget is displayed you cant see anything: it simply doesnt have any
content. To show something, we will use some simple jQuery methods on that object to add some HTML in our root
element:
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
this.$el.append("<div>Hello dear OpenERP user!</div>");
},
});

That message will now appear when you go to the menu Pet Store Pet Store Home Page (remember you need to
refresh your web browser, although there is not need to restart OpenERPs server).
Now you should learn how to instantiate a widget and display its content. To do so, we will create a new widget:
instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
start: function() {
this.$el.append("<div>We are so happy to see you again in this menu!</div>");
},
});

Now we want to display the instance.oepetstore.GreetingsWidget inside the home page. To do so we


can use the append() method of Widget:
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
this.$el.append("<div>Hello dear OpenERP user!</div>");
var greeting = new instance.oepetstore.GreetingsWidget(this);
greeting.appendTo(this.$el);
},
});

Here, the HomePage instantiate a GreetingsWidget (the first argument of the constructor of
GreetingsWidget will be explained in the next part). Then it asks the GreetingsWidget to insert itself
inside the DOM, more precisely directly under the HomePage widget.
When the appendTo() method is called, it asks the widget to insert itself and to display its content. Its during the
call to appentTo() that the start() method will be called.
To check the consequences of that code, lets use Chromes DOM explorer. But before that we will modify a little bit
our widgets to have some classes on some of our <div> elements so we can clearly see them in the explorer:
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
this.$el.addClass("oe_petstore_homepage");
...
},

32

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

});
instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
start: function() {
this.$el.addClass("oe_petstore_greetings");
...
},
});

The result will be this if you can find the correct DOM part in the DOM explorer:
<div class="oe_petstore_homepage">
<div>Hello dear OpenERP user!</div>
<div class="oe_petstore_greetings">
<div>We are so happy to see you again in this menu!</div>
</div>
</div>

Here we can clearly see the two <div> created implicitly by the Widget class, because we added some classes on
them. We can also see the two divs containing messages we created using the jQuery methods on $el. Finally, note
the <div class="oe_petstore_greetings"> element which represents the GreetingsWidget instance
is inside the <div class="oe_petstore_homepage"> which represents the HomePage instance.

4.4.3 Widget Parents and Children


In the previous part, we instantiated a widget using this syntax:
new instance.oepetstore.GreetingsWidget(this);

The first argument is this, which in that case was a HomePage instance. This serves to indicate the Widget what
other widget is his parent.
As weve seen, widgets are usually inserted in the DOM by another widget and inside that other widget. This means
most widgets are always a part of another widget. We call the container the parent, and the contained widget the child.
Due to multiple technical and conceptual reasons, it is necessary for a widget to know who is his parent and who are
its children. This is why we have that first parameter in the constructor of all widgets.
The getParent() method can be used to get the parent of a widget:
instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
start: function() {
console.log(this.getParent().$el );
// will print "div.oe_petstore_homepage" in the console
},
});

The getChildren() method can be used to get a list of its children:


instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
var greeting = new instance.oepetstore.GreetingsWidget(this);
greeting.appendTo(this.$el);
console.log(this.getChildren()[0].$el);
// will print "div.oe_petstore_greetings" in the console
},
});

You should also remember that, when you override the init() method of a widget you should always put the parent
as first parameter are pass it to this._super():

4.4. Widgets Basics

33

OpenERP Web Training, Release 1.0

instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
init: function(parent, name) {
this._super(parent);
this.name = name;
},
});

Finally, if a widget does not logically have a parent (ie: because its the first widget you instantiate in an application),
you can give null as a parent instead:
new instance.oepetstore.GreetingsWidget(null);

4.4.4 Destroying Widgets


If you can display content to your users, you should also be able to erase it. This can simply be done using the
destroy() method:
greeting.destroy();

When a widget is destroyed it will first call destroy() on all its children. Then it erases itself from the DOM. The
recursive call to destroy from parents to children is very useful to clean properly complex structures of widgets and
avoid memory leaks that can easily appear in big JavaScript applications.

4.5 The QWeb Template Engine


The previous part of the guide showed how to define widgets that are able to display HTML to the user. The example
GreetingsWidget used a syntax like this:
this.$el.append("<div>Hello dear OpenERP user!</div>");

This technically allow use to display any HTML, even it is very complex and require to be generated by code. Although
generating text using pure JavaScript is not very nice, that would necessitate to copy-paste a lot of HTML lines inside
our JavaScript source file, add the " character at the beginning and the end of each line, etc...
The problem is exactly the same in most programming languages needing to generate HTML. Thats why they typically
use template engines. Example of template engines are Velocity, JSP (Java), Mako, Jinja (Python), Smarty (PHP), etc...
In OpenERP we use a template engine developed specifically for OpenERPs web client. Its name is QWeb.
QWeb is an XML-based templating language, similar to Genshi, Thymeleaf or Facelets with a few peculiarities:
Its implemented fully in JavaScript and rendered in the browser.
Each template file (XML files) contains multiple templates, where template engine usually have a 1:1 mapping
between template files and templates.
It has special support in OpenERP Webs instance.web.Widget, though it can be used outside of OpenERPs web client (and its possible to use instance.web.Widget without relying on QWeb).
The rationale behind using QWeb instead of existing javascript template engines is that its extension mechanism is
very similar to the OpenERP view inheritance mechanism. Like OpenERP views a QWeb template is an XML tree
and therefore XPath or DOM manipulations are easy to performs on it.

34

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

4.5.1 Using QWeb inside a Widget


First lets define a simple QWeb template in oepetstore/static/src/xml/petstore.xml file, the exact
meaning will be explained later:
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="HomePageTemplate">
<div style="background-color: red;">This is some simple HTML</div>
</t>
</templates>

Now lets modify the HomePage class. Remember that enigmatic line at the beginning the the JavaScript source file?
var QWeb = instance.web.qweb;

This is a line we recommend to copy-paste in all OpenERP web modules. It is the object giving access to all templates
defined in template files that were loaded by the web client. We can use the template we defined in our XML template
file like this:
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
this.$el.append(QWeb.render("HomePageTemplate"));
},
});

Calling the QWeb.render() method asks to render the template identified by the string passed as first parameter.
Another possibility commonly seen in OpenERP code is to use Widgets integration with QWeb:
instance.oepetstore.HomePage = instance.web.Widget.extend({
template: "HomePageTemplate",
start: function() {
...
},
});

When you put a template class attribute in a widget, the widget knows it has to call QWeb.render() to render
that template.
Please note there is a difference between those two syntaxes. When you use Widgets QWeb integration the
QWeb.render() method is called before the widget calls start(). It will also take the root element of the
rendered template and put it as a replacement of the default root element generated by the Widget class. This will
alter the behavior, so you should remember it.
QWeb Context
Like with all template engines, QWeb templates can contain code able to manipulate data that is given to the template.
To pass data to QWeb, use the second argument to QWeb.render():
<t t-name="HomePageTemplate">
<div>Hello <t t-esc="name"/></div>
</t>
QWeb.render("HomePageTemplate", {name: "Nicolas"});

Result:

4.5. The QWeb Template Engine

35

OpenERP Web Training, Release 1.0

<div>Hello Nicolas</div>

When you use Widgets integration you can not pass additional data to the template. Instead the template will have
a unique widget variable which is a reference to the current widget:
<t t-name="HomePageTemplate">
<div>Hello <t t-esc="widget.name"/></div>
</t>
instance.oepetstore.HomePage = instance.web.Widget.extend({
template: "HomePageTemplate",
init: function(parent) {
this._super(parent);
this.name = "Nicolas";
},
start: function() {
},
});

Result:
:: <div>Hello Nicolas</div>
Template Declaration
Now that we know everything about rendering templates we can try to understand QWebs syntax.
All QWeb directives use XML attributes beginning with the prefix t-. To declare new templates, we add a <t
t-name="..."> element into the XML template file inside the root element <template>:
<templates>
<t t-name="HomePageTemplate">
<div>This is some simple HTML</div>
</t>
</templates>

t-name simply declares a template that can be called using QWeb.render().


Escaping
To put some text in the HTML, use t-esc:
<t t-name="HomePageTemplate">
<div>Hello <t t-esc="name"/></div>
</t>

This will output the variable name and escape its content in case it contains some characters that looks like HTML.
Please note the attribute t-esc can contain any type of JavaScript expression:
<t t-name="HomePageTemplate">
<div><t t-esc="3+5"/></div>
</t>

Will render:
<div>8</div>

36

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

Outputting HTML
If you know you have some HTML contained in a variable, use t-raw instead of t-esc:
<t t-name="HomePageTemplate">
<div><t t-raw="some_html"/></div>
</t>

If
The basic alternative block of QWeb is t-if:
<t t-name="HomePageTemplate">
<div>
<t t-if="true == true">
true is true
</t>
<t t-if="true == false">
true is not true
</t>
</div>
</t>

Although QWeb does not contains any structure for else.


Foreach
To iterate on a list, use t-foreach and t-as:
<t t-name="HomePageTemplate">
<div>
<t t-foreach="names" t-as="name">
<div>
Hello <t t-esc="name"/>
</div>
</t>
</div>
</t>

Setting the Value of an XML Attribute


QWeb has a special syntax to set the value of an attribute. You must use t-att-xxx and replace xxx with the name
of the attribute:
<t t-name="HomePageTemplate">
<div>
Input your name:
<input type="text" t-att-value="defaultName"/>
</div>
</t>

To Learn More About QWeb


This guide does not pretend to be a reference for QWeb, please see the documentation for more information:
https://doc.openerp.com/trunk/web/qweb/ .
4.5. The QWeb Template Engine

37

OpenERP Web Training, Release 1.0

Exercise

Exercise - Usage of QWeb in Widgets


Create a widget whose constructor contains two parameters aside from parent: product_names and
color. product_names is a list of strings, each one being a name of product. color is a string containing a color in CSS color format (ie: #000000 for black). That widget should display the given product
names one under the other, each one in a separate box with a background color with the value of color and a
border. You must use QWeb to render the HTML. This exercise will necessitate some CSS that you should put
in oepetstore/static/src/css/petstore.css. Display that widget in the HomePage widget with
a list of five products and green as the background color for boxes.
Solution:
openerp.oepetstore = function(instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.oepetstore = {};
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
var products = new instance.oepetstore.ProductsWidget(this, ["cpu", "mouse", "keyboard",
products.appendTo(this.$el);
},
});
instance.oepetstore.ProductsWidget = instance.web.Widget.extend({
template: "ProductsWidget",
init: function(parent, products, color) {
this._super(parent);
this.products = products;
this.color = color;
},
});
instance.web.client_actions.add(petstore.homepage, instance.oepetstore.HomePage);
}
<?xml version="1.0" encoding="UTF-8"?>

<templates xml:space="preserve">
<t t-name="ProductsWidget">
<div>
<t t-foreach="widget.products" t-as="product">
<span class="oe_products_item" t-att-style="background-color: + widget.color + ;
</t>
</div>
</t>
</templates>
.oe_products_item {
display: inline-block;
padding: 3px;
margin: 5px;
border: 1px solid black;

38

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

border-radius: 3px;
}

4.6 Widget Events and Properties


Widgets still have more helper to learn. One of the more complex (and useful) one is the event system. Events are also
closely related to the widget properties.

4.6.1 Events
Widgets are able to fire events in a similar way most components in existing graphical user interfaces libraries (Qt,
GTK, Swing,...) handle them. Example:
instance.oepetstore.ConfirmWidget = instance.web.Widget.extend({
start: function() {
var self = this;
this.$el.append("<div>Are you sure you want to perform this action?</div>" +
"<button class=ok_button>Ok</button>" +
"<button class=cancel_button>Cancel</button>");
this.$el.find("button.ok_button").click(function() {
self.trigger("user_choose", true);
});
this.$el.find("button.cancel_button").click(function() {
self.trigger("user_choose", false);
});
},
});
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
var widget = new instance.oepetstore.ConfirmWidget(this);
widget.on("user_choose", this, this.user_choose);
widget.appendTo(this.$el);
},
user_choose: function(confirm) {
if (confirm) {

4.6. Widget Events and Properties

39

OpenERP Web Training, Release 1.0

console.log("The user agreed to continue");


} else {
console.log("The user refused to continue");
}
},
});

First, we will explain what this example is supposed to do. We create a generic widget to ask the user if he really
wants to do an action that could have important consequences (a type widget heavily used in Windows). To do so, we
put two buttons in the widget. Then we bind jQuery events to know when the user click these buttons.
Note: It could be hard to understand this particular line:
var self = this;

Remember, in JavaScript the variable this is a variable that is passed implicitly to all functions. It allows us to know
which is the object if function is used like a method. Each declared function has its own this. So, when we declare
a function inside a function, that new function will have its own this that could be different from the this of the
parent function. If we want to remember the original object the simplest method is to store a reference in a variable.
By convention in OpenERP we very often name that variable self because its the equivalent of this in Python.
Since our widget is supposed to be generic, it should not perform any precise action by itself. So, we simply make it
trigger and event named user_choose by using the trigger() method.
Widget.trigger(event_name [, ...]) takes as first argument the name of the event to trigger. Then it
can takes any number of additional arguments. These arguments will be passed to all the event listeners.
Then we modify the HomePage widget to instantiate a ConfirmWidget and listen to its user_choose event by
calling the on() method.
Widget.on(event_name, object, func) allows to bind a function to be called when the event identified
by event_name is triggered. The func argument is the function to call and object is the object to which that
function is related if it is a method. The binded function will be called with the additional arguments of trigger()
if it has any. Example:
start: function() {
var widget = ...
widget.on("my_event", this, this.my_event_triggered);
widget.trigger("my_event", 1, 2, 3);
},
my_event_triggered: function(a, b, c) {
console.log(a, b, c);
// will print "1 2 3"
}

4.6.2 Properties
Properties are very similar to normal object attributes. They allow to set data on an object but with an additional
feature: it triggers events when a propertys value has changed.
start: function() {
this.widget = ...
this.widget.on("change:name", this, this.name_changed);
this.widget.set("name", "Nicolas");
},
name_changed: function() {

40

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

console.log("The new value of the property name is", this.widget.get("name"));


}

Widget.set(name, value) allows to set the value of property. If the value changed (or it didnt had a value
previously) the object will trigger a change:xxx where xxx is the name of the property.
Widget.get(name) allows to retrieve the value of a property.

4.6.3 Exercise
Exercise - Widget Properties and Events
Create a widget ColorInputWidget that will display 3 <input type="text">. Each of these
<input> is dedicated to type a hexadecimal number from 00 to FF. When any of these <input> is modified by the user the widget must query the content of the three <input>, concatenate their values to have a
complete CSS color code (ie: #00FF00) and put the result in a property named color. Please note the jQuery
change() event that you can bind on any HTML <input> element and the val() method that can query
the current value of that <input> could be useful to you for this exercise.
Then, modify the HomePage widget to instantiate ColorInputWidget and display it. The HomePage
widget should also display an empty rectangle. That rectangle must always, at any moment, have the same
background color than the color in the color property of the ColorInputWidget instance.
Use QWeb to generate all HTML.
Solution:
openerp.oepetstore = function(instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.oepetstore = {};
instance.oepetstore.ColorInputWidget = instance.web.Widget.extend({
template: "ColorInputWidget",
start: function() {
var self = this;
this.$el.find("input").change(function() {
self.input_changed();
});
self.input_changed();
},
input_changed: function(confirm) {
var color = "#";
color += this.$el.find(".oe_color_red").val();
color += this.$el.find(".oe_color_green").val();
color += this.$el.find(".oe_color_blue").val();
this.set("color", color);
},
});
instance.oepetstore.HomePage = instance.web.Widget.extend({
template: "HomePage",
start: function() {
this.colorInput = new instance.oepetstore.ColorInputWidget(this);
this.colorInput.on("change:color", this, this.color_changed);

4.6. Widget Events and Properties

41

OpenERP Web Training, Release 1.0

this.colorInput.appendTo(this.$el);
},
color_changed: function() {
this.$el.find(".oe_color_div").css("background-color", this.colorInput.get("color"));
},
});
instance.web.client_actions.add(petstore.homepage, instance.oepetstore.HomePage);
}
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ColorInputWidget">
<div>
Red: <input type="text" class="oe_color_red" value="00"></input><br />
Green: <input type="text" class="oe_color_green" value="00"></input><br />
Blue: <input type="text" class="oe_color_blue" value="00"></input><br />
</div>
</t>
<t t-name="HomePage">
<div>
<div class="oe_color_div"></div>
</div>
</t>
</templates>
.oe_color_div {
width: 100px;
height: 100px;
margin: 10px;
}

Note: jQuerys css() method allows to set a css property.

4.7 Widget Helpers


Weve seen the basics of the Widget class, QWeb and the events/properties system. There are still some more useful
methods proposed by this class.

4.7.1 Widgets jQuery Selector


It is very common to need to select a precise element inside a widget. In the previous part of this guide weve seen a
lot of uses of the find() method of jQuery objects:
this.$el.find("input.my_input")...

Widget provides a shorter syntax that does the same thing with the $() method:
instance.oepetstore.MyWidget = instance.web.Widget.extend({
start: function() {
this.$("input.my_input")...
},
});

42

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

Note: We strongly advise you against using directly the global jQuery function $() like we did in the previous
chapter were we explained the jQuery library and jQuery selectors. That type of global selection is sufficient for
simple applications but is not a good idea in real, big web applications. The reason is simple: when you create a new
type of widget you never know how many times it will be instantiated. Since the $() global function operates in the
whole HTML displayed in the browser, if you instantiate a widget 2 times and use that function you will incorrectly
select the content of another instance of your widget. Thats why you must restrict the jQuery selections to HTML
which is located inside your widget most of the time.
Applying the same logic, you can also guess it is a very bad idea to try to use HTML ids in any widget. If the widget
is instantiated 2 times you will have 2 different HTML element in the whole application that have the same id. And
that is an error by itself. So you should stick to CSS classes to mark your HTML elements in all cases.

4.7.2 Easier DOM Events Binding


In the previous part, we had to bind a lot of HTML element events like click() or change(). Now that we have
the $() method to simplify code a little, lets see how it would look like:
instance.oepetstore.MyWidget = instance.web.Widget.extend({
start: function() {
var self = this;
this.$(".my_button").click(function() {
self.button_clicked();
});
},
button_clicked: function() {
..
},
});

Its still a bit long to type. Thats why there is an even more simple syntax for that:
instance.oepetstore.MyWidget = instance.web.Widget.extend({
events: {
"click .my_button": "button_clicked",
},
button_clicked: function() {
..
}
});

Warning: Its important to differentiate the jQuery events that are triggered on DOM elements and events of the
widgets. The event class attribute is a helper to help binding jQuery events, it has nothing to do with the widget
events that can be binded using the on() method.
The event class attribute is a dictionary that allows to define jQuery events with a shorter syntax.
The key is a string with 2 different parts separated with a space. The first part is the name of the event, the second one
is the jQuery selector. So the key click .my_button will bind the event click on the elements matching the
selector my_button.
The value is a string with the name of the method to call on the current object.

4.7. Widget Helpers

43

OpenERP Web Training, Release 1.0

4.7.3 Development Guidelines


As explained in the prerequisites to read this guide, you should already know HTML and CSS. But developing web
applications in JavaScript or developing web modules for OpenERP require to be more strict than you will usually be
when simply creating static web pages with CSS to style them. So these guidelines should be followed if you want to
have manageable projects and avoid bugs or common mistakes:
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. Just about all the time, they can be replaced with nothing,
with classes or with keeping a reference to a DOM node or a jQuery element around.
Note: If it is absolutely necessary to have an id (because a third-party library requires one and cant take a
DOM element), it should be generated with _.uniqueId().
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 OpenERP is dashboards), queries should be restricted to a given components scope. Unfiltered
selections such as $(selector) or document.querySelectorAll(selector) will generally lead
to unintended or incorrect behavior. OpenERP Webs Widget has an attribute providing its DOM root
(Widget.$el), and a shortcut to select nodes directly (Widget.$()).
More generally, never assume your components own or controls anything beyond its own personal $el.
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 Widget and correctly implement and use its API and life cycle.

4.8 Translations
The process to translate text in Python and JavaScript code is very similar. You could have noticed these lines at the
beginning of the petstore.js file:
var _t = instance.web._t,
_lt = instance.web._lt;

These lines are simply used to import the translation functions in the current JavaScript module. The correct to use
them is this one:
this.$el.text(_t("Hello dear user!"));

In OpenERP, translations files are automatically generated by scanning the source code. All piece of code that calls a
certain function are detected and their content is added to a translation file that will then be sent to the translators. In
Python, the function is _(). In JavaScript the function is _t() (and also _lt()).
If the source file as never been scanned and the translation files does not contain any translation for the text given to
_t() it will return the text as-is. If there is a translation it will return it.
_lt() does almost the exact same thing but is a little bit more complicated. It does not return a text but returns a
function that will return the text. It is reserved for very special cases.
var text_func = _lt("Hello dear user!");
this.$el.text(text_func());

44

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

To have more information about OpenERPs translations, please take a look at the reference documentation:
https://doc.openerp.com/contribute/translations/ .

4.9 Communication with the OpenERP Server


Now you should know everything you need to display any type of graphical user interface with your OpenERP modules. Still, OpenERP is a database-centric application so its still not very useful if you cant query data from the
database.
As a reminder, in OpenERP you are not supposed to directly query data from the PostgreSQL database, you will
always use the build-in ORM (Object-Relational Mapping) and more precisely the OpenERP models.

4.9.1 Contacting Models


In the previous chapter we explained how to send HTTP requests to the web server using the $.ajax() method and
the JSON format. It is useful to know how to make a JavaScript application communicate with its web server using
these tools, but its still a little bit low-level to be used in a complex application like OpenERP.
When the web client contacts the OpenERP server it has to pass additional data like the necessary information to
authenticate the current user. There is also some more complexity due to OpenERP models that need a higher-level
communication protocol to be used.
This is why you will not use directly $.ajax() to communicate with the server. The web client framework provides
classes to abstract that protocol.
To demonstrate this, the file petstore.py already contains a small model with a sample method:
class message_of_the_day(osv.osv):
_name = "message_of_the_day"
def my_method(self, cr, uid, context=None):
return {"hello": "world"}
_columns = {
message: fields.text(string="Message"),
color: fields.char(string="Color", size=20),
}

If you know OpenERP models that code should be familiar to you. This model declares a table named
message_of_the_day with two fields. It also has a method my_method() that doesnt do much except return a dictionary.
Here is a sample widget that calls my_method() and displays the result:

instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
var self = this;
var model = new instance.web.Model("message_of_the_day");
model.call("my_method", [], {context: new instance.web.CompoundContext()}).then(function(resu
self.$el.append("<div>Hello " + result["hello"] + "</div>");
// will show "Hello world" to the user
});
},
});

4.9. Communication with the OpenERP Server

45

OpenERP Web Training, Release 1.0

The class used to contact OpenERP models is instance.web.Model. When you instantiate it, you must
give as first argument to its constructor the name of the model you want to contact in OpenERP. (Here it is
message_of_the_day, the model created for this example, but it could be any other model like res.partner.)
call(name, args, kwargs) is the method of Model used to call any method of an OpenERP server-side
model. Here are its arguments:
name is the name of the method to call on the model. Here it is the method named my_method.
args is a list of positional arguments to give to the method. The sample my_method() method does not
contain any particular argument we want to give to it, so here is another example:
def my_method2(self, cr, uid, a, b, c, context=None): ...
model.call("my_method", [1, 2, 3], ...
// with this a=1, b=2 and c=3

kwargs is a list of named arguments to give to the method. In the example, we have one named argument which is a bit special: context. Its given a value that may seem very strange right now: new
instance.web.CompoundContext(). The meaning of that argument will be explained later. Right now
you should just know the kwargs argument allows to give arguments to the Python method by name instead of
position. Example:
def my_method2(self, cr, uid, a, b, c, context=None): ...
model.call("my_method", [], {a: 1, b: 2, c: 3, ...
// with this a=1, b=2 and c=3

Note: If you take a look at the my_method()s declaration in Python, you can see it has two arguments named cr
and uid:
def my_method(self, cr, uid, context=None):

You could have noticed we do not give theses arguments to the server when we call that method from JavaScript. That
is because theses arguments that have to be declared in all models methods are never sent from the OpenERP client.
These arguments are added implicitly by the OpenERP server. The first one is an object called the cursor that allows
communication with the database. The second one is the id of the currently logged in user.
call() returns a deferred resolved with the value returned by the models method as first argument. If you dont
know what deferreds are, take a look at the previous chapter (the part about HTTP requests in jQuery).

4.9.2 CompoundContext
In the previous part, we avoided to explain the strange context argument in the call to our models method:
model.call("my_method", [], {context: new instance.web.CompoundContext()})

In OpenERP, models methods should always have an argument named context:


def my_method(self, cr, uid, context=None): ...

The context is like a magic argument that the web client will always give to the server when calling a method. The
context is a dictionary containing multiple keys. One of the most important key is the language of the user, used by
the server to translate all the messages of the application. Another one is the time zone of the user, used to compute
correctly dates and times if OpenERP is used by people in different countries.

46

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

The argument is necessary in all methods, because if we forget it bad things could happen (like the application not
being translated correctly). Thats why, when you call a models method, you should always give it to that argument.
The solution to achieve that is to use the instance.web.CompoundContext class.
CompoundContext is a class used to pass the users context (with language, time zone, etc...) to the server as well
as adding new keys to the context (some models methods use arbitrary keys added to the context). It is created by
giving to its constructor any number of dictionaries or other CompoundContext instances. It will merge all those
contexts before sending them to the server.
model.call("my_method", [], {context: new instance.web.CompoundContext({new_key: key_value})})
def display_context(self, cr, uid, context=None):
print context
// will print: {lang: en_US, new_key: key_value, tz: Europe/Brussels, uid: 1}

You can see the dictionary in the argument context contains some keys that are related to the configuration of the
current user in OpenERP plus the new_key key that was added when instantiating CompoundContext.
To resume, you should always add an instance of instance.web.CompoundContext in all calls to a models
method.

4.9.3 Queries
If you know OpenERP module development, you should already know everything necessary to communicate with
models and make them do what you want. But there is still a small helper that could be useful to you : query().
query() is a shortcut for the usual combination of search and read methods in OpenERP models. It allows to
search records and get their data with a shorter syntax. Example:
model.query([name, login, user_email, signature])
.filter([[active, =, true], [company_id, =, main_company]])
.limit(15)
.all().then(function (users) {
// do work with users records
});

query() takes as argument a list of fields to query in the model.


instance.web.Query class.

It returns an instance of the

Query is a class representing the query you are trying to construct before sending it to the server. It has multiple
methods you can call to customize the query. All these methods will return the current instance of Query:
filter allows to specify an OpenERP domain. As a reminder, a domain in OpenERP is a list of conditions,
each condition is a list it self.
limit sets a limit to the number of records returned.
When you have customized you query, you can call the all() method. It will performs the real query to the server
and return a deferred resolved with the result. The result is the same thing return by the models method read() (a
list of dictionaries containing the asked fields).
To have more information about
https://doc.openerp.com/trunk/web/rpc/ .

the

query()

helper,

see

the

reference

documentation:

4.10 Exercises

4.10. Exercises

47

OpenERP Web Training, Release 1.0

Exercise - Message of the Day


Create a widget MessageOfTheDay that will display the message contained in the last record of the
message_of_the_day. The widget should query the message as soon as it is inserted the the DOM and
display the message to the user. Display that widget on the home page of the OpenERP Pet Store module.
Solution:
openerp.oepetstore = function(instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.oepetstore = {};
instance.oepetstore.HomePage = instance.web.Widget.extend({
template: "HomePage",
start: function() {
var motd = new instance.oepetstore.MessageOfTheDay(this);
motd.appendTo(this.$el);
},
});
instance.web.client_actions.add(petstore.homepage, instance.oepetstore.HomePage);

instance.oepetstore.MessageOfTheDay = instance.web.Widget.extend({
template: "MessageofTheDay",
init: function() {
this._super.apply(this, arguments);
},
start: function() {
var self = this;
new instance.web.Model("message_of_the_day").query(["message"]).first().then(function(res
self.$(".oe_mywidget_message_of_the_day").text(result.message);
});
},
});
}
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="HomePage">
<div class="oe_petstore_homepage">
</div>
</t>
<t t-name="MessageofTheDay">
<div class="oe_petstore_motd">
<p class="oe_mywidget_message_of_the_day"></p>
</div>
</t>
</templates>
.oe_petstore_motd {
margin: 5px;
padding: 5px;

48

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

border-radius: 3px;
background-color: #F0EEEE;
}

Exercise - Pet Toys List


Create a widget PetToysList that will display 5 toys on the home page with their names and their images.
In this OpenERP addon, the pet toys are not stored in a new table like for the message of the day. They are in
the table product.product. If you click on the menu item Pet Store > Pet Store > Pet Toys
you will be able to see them. Pet toys are identified by the category named Pet Toys. You could need to
document yourself on the model product.product to be able to create a domain to select pet toys and not
all the products.
To display the images of the pet toys, you should know that images in OpenERP can be queried from the
database like any other fields, but you will obtain a string containing Base64-encoded binary. There is a little
trick to display images in Base64 format in HTML:
<img class="oe_kanban_image" src="data:image/png;base64,${replace this by base64}"></image>

The PetToysList widget should be displayed on the home page on the right of the MessageOfTheDay
widget. You will need to make some layout with CSS to achieve this.
Solution:
openerp.oepetstore = function(instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.oepetstore = {};
instance.oepetstore.HomePage = instance.web.Widget.extend({
template: "HomePage",
start: function() {
var pettoys = new instance.oepetstore.PetToysList(this);
pettoys.appendTo(this.$(".oe_petstore_homepage_left"));
var motd = new instance.oepetstore.MessageOfTheDay(this);
motd.appendTo(this.$(".oe_petstore_homepage_right"));
},
});
instance.web.client_actions.add(petstore.homepage, instance.oepetstore.HomePage);

instance.oepetstore.MessageOfTheDay = instance.web.Widget.extend({
template: "MessageofTheDay",
init: function() {
this._super.apply(this, arguments);
},
start: function() {
var self = this;
new instance.web.Model("message_of_the_day").query(["message"]).first().then(function(res
self.$(".oe_mywidget_message_of_the_day").text(result.message);
});
},
});
instance.oepetstore.PetToysList = instance.web.Widget.extend({
template: "PetToysList",

4.10. Exercises

49

OpenERP Web Training, Release 1.0

start: function() {
var self = this;
new instance.web.Model("product.product").query(["name", "image"])
.filter([["categ_id.name", "=", "Pet Toys"]]).limit(5).all().then(function(result) {
_.each(result, function(item) {
var $item = $(QWeb.render("PetToy", {item: item}));
self.$el.append($item);
});
});
},
});
}
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="HomePage">
<div class="oe_petstore_homepage">
<div class="oe_petstore_homepage_left"></div>
<div class="oe_petstore_homepage_right"></div>
</div>
</t>
<t t-name="MessageofTheDay">
<div class="oe_petstore_motd">
<p class="oe_mywidget_message_of_the_day"></p>
</div>
</t>
<t t-name="PetToysList">
<div class="oe_petstore_pettoyslist">
</div>
</t>
<t t-name="PetToy">
<div class="oe_petstore_pettoy">
<p><t t-esc="item.name"/></p>
<p><img t-att-src="data:image/jpg;base64,+item.image"/></p>
</div>
</t>
</templates>
.oe_petstore_homepage {
display: table;
}
.oe_petstore_homepage_left {
display: table-cell;
width : 300px;
}
.oe_petstore_homepage_right {
display: table-cell;
width : 300px;
}
.oe_petstore_motd {
margin: 5px;
padding: 5px;
border-radius: 3px;

50

Chapter 4. OpenERP Web Framework

OpenERP Web Training, Release 1.0

background-color: #F0EEEE;
}
.oe_petstore_pettoyslist {
padding: 5px;
}
.oe_petstore_pettoy {
margin: 5px;
padding: 5px;
border-radius: 3px;
background-color: #F0EEEE;
}

4.10. Exercises

51

OpenERP Web Training, Release 1.0

52

Chapter 4. OpenERP Web Framework

CHAPTER

FIVE

INDICES AND TABLES

genindex
modindex
search

53

You might also like