• No results found

HTTP Requests with jQuery

In document Odoo for All Ages (Page 24-33)

});

};

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

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", {

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: "json"is used to inform jQuery that the server will return JSON, so it can read that JSON and return

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

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_plusweb 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 com-putation 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 program-ming not being a first-class concept and being implemented using callbacks-based programprogram-ming, 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 rejec-tion 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

// the message "operation succeeded" will appear, even if the deferred was already resolved when we call then()

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();

// the message "operation succeeded" will appear only once in the console, because the second call to resolve() // 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",

var def2 = $.ajax("/service_plus", { type: "POST",

3.4. HTTP Requests with jQuery 23

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",

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 def2are 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 represents that.

var def2 = def.then(...);

// And finally we don’t forget to return a deferred because func2() is, by transitivity, a function // 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",

3.4. HTTP Requests with jQuery 25

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 parame-ter 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

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 xmlis just a convention.

oepetstore/static/css/petstore.cssis our CSS file. It is empty right now but we will add any CSS we need later.

oepetstore/static/xml/petstore.xmlis 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.jsis probably the most interesting part. It contains the JavaScript of our application. Here is what it looks like right now:

openerp.oepetstore = function(instance) {

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 line-breaks 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

In document Odoo for All Ages (Page 24-33)

Related documents