Odoo for All Ages
Short Description
Odoo for All Ages is a document that enabvles you to get knowledge about odoo...
Description
OpenERP Web Training Release 1.0
OpenERP SA
September 16, 2013
CONTENTS
1
Introduction 1.1 Reminder about OpenERP Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 About this Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
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
4
5
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
i
ii
OpenERP Web Training, Release 1.0
See also the OpenERP Web Reference Documentation. Contents:
CONTENTS
1
OpenERP Web Training, Release 1.0
2
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 OpenERP’s 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 OpenERP’s web client) • OpenERP’s Javascript and web applications framework • Extension points for OpenERP’s 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
3
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.
4
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 application’s 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 user’s 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 user’s 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 OpenERP’s Web client you’ll 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 can’t 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 JavaScript’s 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
5
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 you’re 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 OpenERP’s 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.).
6
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
7
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. That’s 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 Chrome’s 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.)
8
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
9
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 Python’s 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 object’s 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 that’s 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 Chrome’s 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 don’t 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 Let’s take a look at the only HTML file of the application which is located in static/index.html: $(function() { app.main(); }); Click Me
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 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 module’s 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 Chrome’s 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 debugger’s 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 browser’s 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 Chrome’s 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 jQuery’s 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 elements $("div") // all 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 elements with the ’title’ class $("#content table") // all 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 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(’Hello world!’);
To avoid replacing the whole content and simply add some HTML at the end of the element, use the append() method: $(".main_content").append(’Hello world again!’);
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(’Hello world, I am the first one!’);
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 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("" + i + ""); }); }); }; 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 jQuery’s 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 jQuery’s 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 jQuery’s 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. Deferreds’s 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 jQuery’s 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 don’t 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 don’t 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 don’t 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... That’s 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 It’s 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 let’s 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... We’ll come back to these elements later because they will be useful to develop some example web module. Right now let’s 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 doesn’t do much things right now except display a blank page and print a small message in the console. Like OpenERP’s 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 user’s browser. That operation may seem complex, but it’s 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 OpenERP’s 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 OpenERP’s 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. That’s 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 doesn’t 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. OpenERP’s 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 Resig’s 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 it’s 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. It’s useful, but it’s 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, it’s 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 doesn’t do anything really interesting right now. We’ll 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: Inbox petstore.homepage
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 OpenERP’s web client “open the client action identified by the key petstore.homepage”. Let’s 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 OpenERP’s 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 let’s 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 HTML element. A 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 can’t see anything: it simply doesn’t 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("Hello dear OpenERP user!"); }, });
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 OpenERP’s 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("We are so happy to see you again in this menu!"); }, });
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("Hello dear OpenERP user!"); 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. It’s during the call to appentTo() that the start() method will be called. To check the consequences of that code, let’s use Chrome’s DOM explorer. But before that we will modify a little bit our widgets to have some classes on some of our 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: Hello dear OpenERP user! We are so happy to see you again in this menu!
Here we can clearly see the two 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 element which represents the GreetingsWidget instance is inside the 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 we’ve 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 it’s 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("Hello dear OpenERP user!");
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. That’s 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 OpenERP’s web client. Its name is QWeb. QWeb is an XML-based templating language, similar to Genshi, Thymeleaf or Facelets with a few peculiarities: • It’s 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 Web’s instance.web.Widget, though it can be used outside of OpenERP’s web client (and it’s 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 let’s define a simple QWeb template in oepetstore/static/src/xml/petstore.xml file, the exact meaning will be explained later: This is some simple HTML
Now let’s 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 Widget‘s 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 Widget‘s 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(): Hello QWeb.render("HomePageTemplate", {name: "Nicolas"});
Result:
4.5. The QWeb Template Engine
35
OpenERP Web Training, Release 1.0
Hello Nicolas
When you use Widget‘s 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: Hello instance.oepetstore.HomePage = instance.web.Widget.extend({ template: "HomePageTemplate", init: function(parent) { this._super(parent); this.name = "Nicolas"; }, start: function() { }, });
Result: :: Hello Nicolas Template Declaration Now that we know everything about rendering templates we can try to understand QWeb’s syntax. All QWeb directives use XML attributes beginning with the prefix t-. To declare new templates, we add a element into the XML template file inside the root element : This is some simple HTML
t-name simply declares a template that can be called using QWeb.render(). Escaping To put some text in the HTML, use t-esc: Hello
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:
Will render: 8
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:
If The basic alternative block of QWeb is t-if: true is true true is not true
Although QWeb does not contains any structure for else. Foreach To iterate on a list, use t-foreach and t-as: Hello
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: Input your name:
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’); }
View more...
Comments