openerp formation web

67
OpenERP V7.0 04/08/14 Développement Web Traduit de https://doc.openerp.com/trunk/training/ par Guillaume JULLIEN et Antoine LANNELUC – Aquilog. Table des matières Introduction.............................................................................................................................................. 4 Rappel concernant la structure d'OpenERP............................................................................................................. 4 Au sujet de ce guide..................................................................................................................................................... 4 Les bases de javascript ............................................................................................................................. 5 Préambule..................................................................................................................................................................... 5 Qu'est-ce qu'une application web ?..................................................................................................................... 5 Une note sur JavaScript......................................................................................................................................... 5 L’interpréteur de ligne de commande...................................................................................................................... 6 Types de base............................................................................................................................................................... 7 Nombres.................................................................................................................................................................. 7 Booléens.................................................................................................................................................................. 7 Chaînes..................................................................................................................................................................... 8 null............................................................................................................................................................................ 8 Undefined................................................................................................................................................................ 8 Conversions implicites de types................................................................................................................................. 8 Structures de contrôle................................................................................................................................................. 9 Fonctions..................................................................................................................................................................... 10 Variables et portée.................................................................................................................................................... 10 Listes (array)................................................................................................................................................................ 12 Objets.......................................................................................................................................................................... 12 Bibliothèques javascript ......................................................................................................................... 14 Une première application Javascript...................................................................................................................... 14 Téléchargez et lancez l'application de démarrage.......................................................................................... 14 Architecture de l'application.............................................................................................................................. 15 Le modèle « Module ».......................................................................................................................................... 16 Outils de déboguage........................................................................................................................................... 17 Underscore.js.............................................................................................................................................................. 18 Exercice – Utilisation d'Underscore.js.......................................................................................................... 18 Manipulations HTML avec jQuery............................................................................................................................ 19 Le DOM.................................................................................................................................................................. 19 Sélecteurs jQuery................................................................................................................................................. 20 Événements jQuery.............................................................................................................................................. 21 Modifications du DOM avec jQuery................................................................................................................... 21 Exercice.................................................................................................................................................................. 22 Requêtes HTTP avec jQuery..................................................................................................................................... 22 1/67

Upload: guillaume-jullien

Post on 20-Jun-2015

2.820 views

Category:

Software


1 download

DESCRIPTION

Traduction française de la documentation : OpenERP Web Training Manuel de développement des applications web OpenERP

TRANSCRIPT

Page 1: OpenERP Formation Web

OpenERP V7.0 04/08/14

Développement WebTraduit de https://doc.openerp.com/trunk/training/ par Guillaume JULLIEN et Antoine LANNELUC – Aquilog.

Table des matièresIntroduction.............................................................................................................................................. 4

Rappel concernant la structure d'OpenERP............................................................................................................. 4

Au sujet de ce guide..................................................................................................................................................... 4

Les bases de javascript............................................................................................................................. 5

Préambule..................................................................................................................................................................... 5Qu'est-ce qu'une application web ?..................................................................................................................... 5Une note sur JavaScript......................................................................................................................................... 5

L’interpréteur de ligne de commande...................................................................................................................... 6

Types de base............................................................................................................................................................... 7Nombres.................................................................................................................................................................. 7Booléens.................................................................................................................................................................. 7Chaînes..................................................................................................................................................................... 8null............................................................................................................................................................................ 8Undefined................................................................................................................................................................ 8

Conversions implicites de types................................................................................................................................. 8

Structures de contrôle................................................................................................................................................. 9

Fonctions..................................................................................................................................................................... 10

Variables et portée.................................................................................................................................................... 10

Listes (array)................................................................................................................................................................ 12

Objets.......................................................................................................................................................................... 12

Bibliothèques javascript......................................................................................................................... 14

Une première application Javascript...................................................................................................................... 14Téléchargez et lancez l'application de démarrage..........................................................................................14Architecture de l'application.............................................................................................................................. 15Le modèle « Module ».......................................................................................................................................... 16Outils de déboguage........................................................................................................................................... 17

Underscore.js.............................................................................................................................................................. 18Exercice – Utilisation d'Underscore.js..........................................................................................................18

Manipulations HTML avec jQuery............................................................................................................................ 19Le DOM.................................................................................................................................................................. 19Sélecteurs jQuery................................................................................................................................................. 20Événements jQuery.............................................................................................................................................. 21Modifications du DOM avec jQuery................................................................................................................... 21Exercice.................................................................................................................................................................. 22

Requêtes HTTP avec jQuery..................................................................................................................................... 22

1/67

Page 2: OpenERP Formation Web

OpenERP V7.0 04/08/14

La méthode $.ajax()............................................................................................................................................. 23Promesses et différés.......................................................................................................................................... 24Combiner des différés......................................................................................................................................... 25Différé multiplexé................................................................................................................................................ 25Différés chaînés.................................................................................................................................................... 26Meilleures pratiques pour l'utilisation de code asynchrone..........................................................................27

Framework Web OpenERP..................................................................................................................... 28

Un module simple pour tester le framework........................................................................................................ 28

Le module javascript OpenERP................................................................................................................................ 30

Les classes................................................................................................................................................................... 30

Les bases des widgets............................................................................................................................................... 32Votre premier widget.......................................................................................................................................... 32Affichage du contenu.......................................................................................................................................... 32Widgets parents et enfants................................................................................................................................ 34Supprimer des widgets........................................................................................................................................ 35

Le moteur de template QWeb................................................................................................................................. 35Utiliser Qweb dans un widget............................................................................................................................ 36Contexte QWeb.................................................................................................................................................... 37Déclaration de template..................................................................................................................................... 37

Escaping........................................................................................................................................................... 38Afficher du HTML............................................................................................................................................ 38If........................................................................................................................................................................ 38Foreach............................................................................................................................................................. 38Définir de la valeur d'un attribut XML......................................................................................................... 39

Pour en savoir plus QWeb................................................................................................................................... 39Exercice.................................................................................................................................................................. 39

Solution............................................................................................................................................................ 39

Événements et propriétés des Widgets................................................................................................................. 41Événements.......................................................................................................................................................... 41Propriétés.............................................................................................................................................................. 43Exercice.................................................................................................................................................................. 43

Assistants de Widgets............................................................................................................................................... 45Connexion plus facile avec les événements du DOM.....................................................................................45Recommandations pour le développement....................................................................................................46

Modifier des Widgets et des classes existants...................................................................................................... 47

Traductions................................................................................................................................................................. 48

Communication avec le serveur OpenERP............................................................................................................. 48Contacter les modèles......................................................................................................................................... 48CompoundContext.............................................................................................................................................. 50Requêtes................................................................................................................................................................ 51

Exercices...................................................................................................................................................................... 52

Les composants web OpenERP............................................................................................................................... 55

Action Manager.......................................................................................................................................................... 55Utiliser l'ActionManager...................................................................................................................................... 55

Actions Client.............................................................................................................................................................. 57

Architecture des vues................................................................................................................................................ 58Le gestionnaire de vues....................................................................................................................................... 58Les vues................................................................................................................................................................. 58

Les champs de la vue formulaire............................................................................................................................. 58Créer un nouveau type de champ...................................................................................................................... 60

Champ simple en lecture seule.................................................................................................................... 60Champ en lecture-écriture............................................................................................................................ 61

Exercice – Créer un champ Couleur................................................................................................................... 62

2/67

Page 3: OpenERP Formation Web

OpenERP V7.0 04/08/14

Les widgets personnalisés de la vue formulaire....................................................................................................63Exercice – Afficher des coordonnées sur Google Map...................................................................................64

3/67

Page 4: OpenERP Formation Web

OpenERP V7.0 04/08/14

Introduction

Rappel concernant la structure d'OpenERPOpenERP est composé des éléments suivants :

Le serveur OpenERP contient le framework côté serveur et gère les demandes provenant des clients.

La base de données PostgreSQL contient nos données.

Les modules implémentent la logique métier.

L'application web client communique avec le serveur et affiche une interface graphique utilisateur.

Ce guide a pour objet le développement d'applications web et le client Web d'OpenERP.

Au sujet de ce guideCe guide sert à enseigner aux intégrateurs OpenERP la création de modules web pour OpenERP. Les sujets abordés sont les suivants:

• Bases de Javascript et bonnes pratiques

• Bases de jQuery et Underscore (bibliothèques Javascript utilisées dans la partie client Web de

OpenERP)

• Framework Javascript et web d'OpenERP

• Points d'extensions pour le client web OpenERP.

Ce guide suppose que le lecteur a suivi la formation technique sur la création de modules OpenERP ou qu'il aune bonne connaissance du développement des modules Python. Ce guide suppose également que le

4/67

Page 5: OpenERP Formation Web

OpenERP V7.0 04/08/14

lecteur a de l'expérience dans le domaine de la programmation orientée objet et des connaissances de base d'au moins un langage de programmation dont la syntaxe est basée sur C (C + +, Java, C #, ...). Il peut également se référer au système de contrôle de version Bazaar. Enfin, il est nécessaire d'avoir un minimum de connaissances en HTML et CSS.

Tous les exemples fournis supposent que vous développez sous un système d'exploitation Linux, et plus particulièrement Ubuntu ou Debian. Si vous utilisez Windows, nous vous recommandons de créer une machine virtuelle Ubuntu. Le site http://virtualboxes.org/ fournit des machines virtuelle pré-installées, gratuitement, pour un grand nombre de distributions Linux. Il suffit d'installer VirtualBox qui est également gratuit.

Les bases de javascript

Préambule

Qu'est-ce qu'une application web ?

Une application web est simplement une application envoyée et utilisée à travers un navigateur web, mais le terme a récemment pris un sens plus précis.

L'ancienne façon de faire une application web, et la façon dont OpenERP l'a fait jusqu'à la version 6.0 est de faire en sorte que le serveur envoie à l'utilisateur les documents HTML complets représentant l'état de l'interface graphique de l'application

Ceci implique que le serveur doit calculer et envoyer un nouveau document HTML pour chaque action :

Clics, recherches, historique de navigation demandent au serveur de renvoyer un document.

Ceci crée une charge importante sur le serveur et impacte le nombre d'utilisateurs simultanés. Cela crée génère également un temps de latence important dans l'application qui rend la mise en œuvre de nombreuses fonctionnalités impossible, et limite ce qui peut être réalisé en termes d'usabilité.

La solution est de créer une application complète et autonome qui s'exécute sur le navigateur web de l'utilisateur.

Ce type d'application à beaucoup plus de choses en commun avec les applications traditionnelles de bureau (exemple: GTK, Swing, Qt, Windows Forms, ...) que les sites web type PHP. La seule différence avec les applications de bureau, outre le langage de programmation et les bibliothèques utilisées, est que le client Web est téléchargé et qu'il est exécuté par le navigateur de l'utilisateur à chaque fois qu'il visite le site Web OpenERP.

Une note sur JavaScript

JavaScript est le langage compris par les navigateurs et de ce fait le langage de-facto des applications web.

Si vous voulez développer pour le client Web d'OpenERP vous aurez besoin de connaître le JavaScript.

Objectivement, JavaScript n'est pas un bon langage de programmation. Il a été conçu par Netscape en 1995 dans un but commercial par une petite équipe et en peu de temps, Il n'avait pas pour but de devenir le langage de programmation le plus universel de l'histoire. Il a beaucoup de problèmes de conception initiale, dus à des nécessités de rétro-compatibilité. Il n'a pas été réellement amélioré depuis sa création.

5/67

Page 6: OpenERP Formation Web

OpenERP V7.0 04/08/14

De plus, Javascript souffre de sa grande popularité. Il en résulte un grand nombre de résultats de recherche Google sur JavaScript avec des articles écrits par des gens qui n'ont pas eu de formation en programmation ou qui ne peuvent pas programmer du tout, mais qui réussissent toujours à obtenir certains effets grâce au copier-coller de code.

Pourtant, malgré ses problèmes, le noyau du langage contient de bonnes idées permettant aux programmeur beaucoup de créativité. (Comme la programmation orientée prototype et la programmation fonctionnelle.) La plupart des lacunes de JavaScript peuvent être contounrées en utilisant les modèles corrects et les bons outils. Il a également de sérieux avantages. Tout d'abord, le JavaScript est très rapide. Rapide à démarrer, rapide à exécuter et rapide à déployer. La possibilité d'utiliser l'HTML et l'API multimédia des navigateurs permet également de créer de très jolies applications ainsi qu'une bonne productivité par rapport à la programmation d'applications de bureau. Le point décisif est sans doute le fait que les machines virtuelles JavaScript sont disponibles sur 99,99% des ordinateurs de bureau de la planète.

En fin de compte, si vous êtes un bon programmeur avec les bonnes bibliothèques, les avantages dépassent de loin les inconvénients et font de JavaScript et du navigateur l'un des meilleur environnement pour développer des applications pour le grand public.

L’interpréteur de ligne de commandePour tester les fonctionnalités de base du langage, nous vous recommandons de commencer en utilisant un interpréteur en ligne de commande. La plupart des navigateurs modernes fournissent une console pour utiliser javascript, mais il est recommande d'utiliser Google Chrome pour le développement de modules web OpenERP. Ce guide considère que vous utilisez ce navigateur.

Une fois Chrome installé, ouvrez n'importe quelle page web puis allez dans le menu configuration de Chrome et choisissez : Outils > Outils développeur ou utilisez Ctrl + Shift + I. Ceci devrait faire apparaître un nouveau cadre en bas de la fenêtre. Sélectionnez maintenant le panneau « Console ». Vous devriez avoir l'écran suivant :

6/67

Page 7: OpenERP Formation Web

OpenERP V7.0 04/08/14

Vous pouvez maintenant tester les extraits de code de la section suivante.

Types de base

Nombres

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

A noter : JavaScript n'a pas d'entiers. Tous les nombres sont des (floats). C'est la grosse différence avec la plupart des autres langages de programmation. Cela a des répercussions sur certaines opérations mathématiques. Exemple:

> 3 / 2;1.5

En C, Java ou Python le résultat serait 1, sauf si l'un des membres a été casté en float ou explicitement déclaré en float en utilisant une notation différente (2,0 ou 2.).

Booléens

> 5 == 2;false> true == true;true

7/67

Page 8: OpenERP Formation Web

OpenERP V7.0 04/08/14

> true == false;false

Aussi simple que ce que peuvent être les booléens.

Chaînes

> "Hello World";"Hello World"> 'Hello World';"Hello World"

Les chaînes peuvent être déclarées en utilisant des apostrophes ou des guillemets. Comme dans la plupart des langages de haut niveau, les chaînes ont des méthodes pour de nombreuses opérations

> "Hello World".charAt(3);"l" // like Python, there is not char type in JavaScript, a char is a string ofsize 1 > "Hello World".slice(6, 9);"Wor" // slice() is used to obtain a sub-string > "Hello World".length;11

Les chaînes utilisent aussi l'opérateur + pour la concaténation :

> "Hello " + "World";"Hello World"Null> var x = null;> x;

null

Similaire à de nombreux autres langages ou None en Python

Undefined

Si vous déclarez une variable mais que vous ne l'assignez pas, elle aura une valeur spéciale. Cette valeur n'est pas identique à 'null'.

> var x;> x;undefined

Conversions implicites de typesJavaScript fournit une conversion de type automatique pour la plupart des opérateurs.

> "Test" + 5;"Test5"

Dans la pratique, ce comportement peut être utile dans certains cas, comme la conversion implicite de en chaînes, mais peut aussi créer des comportements étranges, l'exemple typique étant les comparaisons. En

8/67

Page 9: OpenERP Formation Web

OpenERP V7.0 04/08/14

voici quelques exemples :

> "5" == 5;true> "" == 0;true> "0" == false;true

Comme en C, les nombres sont considérés comme 'false' s'ils sont 0 et 'true' sinon. Les chaînes sont considéres comme 'vrai' sauf si elles sont vides.

Pendant des opérations impliquant différents types, plusieurs cas peuvent se produire ce qui est assez compliqué à prévoir pour le programmeur. C'est pourquoi on considère comme plus sûr d'utiliser toujours les opérateurs === et ! ==.

> "5" === 5;false> "" === 0;false> "0" === false;false

Ces opérateurs retourneront toujours false si les types à comparer sont différents.

Structures de contrôleJavaScript fournit les mêmes structures de contrôle que C. (Pour tester avec la console Chrome, vous pouvezutiliser le raccourci Shift + Enter pour saisir plusieurs lignes de code).

> if (true == true) { console.log("true is true"); }true is true> var x = 0;> while (x < 3) { console.log(x); x++; }123> for (var i = 5; i < 8; i++) { console.log(i); }567

JavaScript fournit également une structure de boucle sur les objets (for (... in ...).) Il convient de noter que, enraison de la mauvaise conception du langage dans la portée des variables, la programmation fonctionnelle, les performances et la gestion des listes, presque tous les programmeurs expérimentés évitent l'utilisation de cette structure et vont plutôt utiliser les fonctions fournies par les bibliothèques non standard comme jQuery ou Underscore. Nous vous recommandons d'utiliser la plupart du temps _.each() fourni par Underscore (vous trouverez plus d'informations sur cette bibliothèque plus loin dans ce document)

9/67

Page 10: OpenERP Formation Web

OpenERP V7.0 04/08/14

FonctionsLes fonctions peuvent être déclarées comme suit:

> function talk () { console.log("Hello World"); }> talk();Hello WorldEn JavaScript, les fonctions sont aussi un type complet par elles-mêmes. Elles peuvent être déclarées comme des expressions et stockées dans des 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

Les arguments de fonctions sont déclarés comme dans la plupart des langages, sauf qu'ils n'ont pas de type. Notez que la machine virtuelle JavaScript ne vérifie jamais le nombre d'arguments quand une fonction est appelée. S'il y a plus d'arguments, la fonction sera appelée quand même. S'il y en a moins, les paramètres restants seront «undefined».

> var print = function(a, b) { console.log(a); console.log(b); }> print("hello");helloundefined> print("nice", "to", "meet", "you");niceto

Variables et portéeUne variable est déclarée en précédant son nom par « var ». A la différence de C++ et Java, une portée n'est pas définie par l'existence d'accolades, mais par une fonction.

10/67

Page 11: OpenERP Formation Web

OpenERP V7.0 04/08/14

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

Dans cet exemple, z n'est pas une variable qui est recrée à chaque itération de la boucle. C'est toujours la même variable pour chaque itération car la variable est définie dans la portée de la fonction func1.

Les fonctions peuvent aussi accéder les variables définies au-dessus d'elles.

> function func1() { var x = "hello"; function func2() { console.log(x); } func2(); x = "world"; func2(); }> func1();helloworld

Lorsqu'une variable est déclarée directement à la racine d'un fichier source, et pas à l'intérieur d'une fonction, elle existe dans la portée globale. A la différence de Python, la portée globale n'est pas spécifique à chaque fichier source. La portée globale est partagée entre tout les codes Javascript exécutés par une instance de la machine virtuelle Javascript. Ce qui signifie sur la même page web

// file source1.js

var x = "value1";

// file source2.js

var x = "value2";

Si ces deux fichiers sont chargés par la même page web, la variable x ne peut avoir qu'une seule valeur, "value1" ou "value2", selon le fichier chargé en dernier. C'est évidemment un problème et on peut le solutionner en utilisant le pattern « module » (voir plus loin).

Une dernière remarque au sujet des portées est que toute valeur assignée mais non déclarée sera implicitement considérée comme faisant partie de la portée globale. Ceci veut dire que si vous oubliez d'utiliser le mot-clé 'var', le code ne plantera pas mais la variable sera globale à l'application et non locale à la fonction courante. C'est une source d'erreurs commune en Javascript.

11/67

Page 12: OpenERP Formation Web

OpenERP V7.0 04/08/14

> function func1() { x = "hello"; }> function func2() { console.log(x); }> func2();ReferenceError: x is not defined> func1();> func2();hello> x = "world";> func2();world

Listes (array)La syntaxe des listes est très similaire à celle de Python :

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

Notez que la syntaxe ci-dessus fonctionne mais est inefficace. Pour la production, utilisez _.each() ou une fonction similaire fournie par une librairie tierce.

Comme les chaînes, les listes ont des méthodes pour différentes opérations.

> var array = [];> array.push("banana"); // ajoute un élément à la fin> array.push("tomato");> array;["banana", "tomato"]> array.pop(); // retire le dernier élément et le retourne"tomato"> array;["banana"]

ObjetsLa programmation Orientée Objet est possible en Javascript, mais très différente de celle des autres langages de programmation (sauf si vous connaissez Lua).

D'abord, les objets sont des dictionnaires et les dictionnaires des objets. Il n'y a pas de différence en Javascript. La syntaxe est similaire aux dictionnaires Python mais il a une forme différente, selon que vous préférez utiliser une vision objet ou dictionnaire. Exemple :

12/67

Page 13: OpenERP Formation Web

OpenERP V7.0 04/08/14

> var obj = { "key1": "hello", // déclaration de type dictionnaire key2: "world", // déclaration de type objet };> console.log(obj["key1"]); // dictionary-like lookuphello > console.log(obj.key2); // object-like lookupworld

obj["key"] et obj.key ont exactement la même signification. Le premier sera, par convention, utilisé pour la recherche dans un dictionnaire, et le second pour accéder à l'attribut d'un objet.

Les méthodes peuvent être définies simplement en écrivant une fonction dans un objet :

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

En javascript, chaque fois qu'une méthode est appelée, elle a une variable implicitement déclarée et nommée « this » quand elle est appelée sur un objet (en utilisant la syntaxe habituelle object.method(arguments. ..) ), La variable « this » fait référence à l'objet courant.

Dans l'exemple ci-dessus, nous définissons un objet unique contenant tous les attributs et méthodes nécessaires pour son fonctionnement. Mais ce n'est pas la façon dont la plupart des langages de programmation géreront la programmation orientée objet. Ils ont un concept de classe. Une classe contient les propriétés communes à toutes ses instances. Il n'y a pas de classe en Javascript mais il est possible de reproduire ce concept en utilisant les 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"

La programmation orientée objet basé sur les prototypes est un vaste sujet, nous ne couvrons pas cette partie dans ce guide, mais vous pouvez facilement trouver des informations à ce sujet sur Internet

En raison des différences entre prototype et classe dans les programmations Orientées Objet et des habitudes de la plupart des programmeurs, nous avons choisi, Dans OpenERP, de ne pas utiliser directement une Programmation Objet basée sur les prototypes. Nous utilisons une API de haut niveau qui permet aux programmeurs de déclarer facilement des classes d'une manière qui semble naturelle aux personnes habituées à des langages de programmation plus conventionnels. Ce sujet sera couvert plus loin dans ce

13/67

Page 14: OpenERP Formation Web

OpenERP V7.0 04/08/14

guide.

Bibliothèques javascript

Une première application Javascript

Téléchargez et lancez l'application de démarrage

Le moment est venu de cesser d'utiliser la ligne de commande de Chrome, et de démarrer une application Javascript. Nous fournissons une application exemple que vous devriez télécharger pour continuer à lire ce guide. Cette application exemple est située dans une branche Bazaar. Au cas où bazaar n'est pas installé, vous pouvez taper cette commande dans votre shell Linux :

> sudo apt-get install bzr

Maintenant, vous pouvez télécharger l'application exemple en tapant :

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

Pour démontrer la communication entre une application web JavaScript et un processus serveur, cette application exemple intègre un serveur web minimal Python. Ce serveur web peut exiger d'installer certaines dépendances, vous pouvez le faire comme ceci:

> sudo apt-get install python> sudo easy_install flask

Vous pouvez à présent lancer cette application web Python en utilisant cette commande :

> python app.py

14/67

Page 15: OpenERP Formation Web

OpenERP V7.0 04/08/14

Maintenant, vous devriez être en mesure d'utiliser votre navigateur web pour accéder au serveur web à l'url http://localhost:5000.

Architecture de l'application

Regardons l'unique fichier HTML de l'application : 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>

15/67

Page 16: OpenERP Formation Web

OpenERP V7.0 04/08/14

</html>

Cette page Web contient quelques éléments HTML avec des styles définis dans le fichier css static/css/app.css

Ce fichier HTML importe aussi trois fichiers JavaScript. Deux d'entre eux sont des bibliothèques bien connues dans la communauté JavaScript: jQuery et Underscore. Le dernier fichier JavaScript /static/js/app.js est le fichier qui va contenir la logique de notre application.

Le dernier bloc <script/> est un peu plus difficile à comprendre:

L'appel à la fonction énigmatique '$' à laquelle nous passons une fonction anonyme. La fonction $ fait partie de la bibliothèque jQuery. Cette fonction peut avoir plusieurs utilisations qui seront expliquées plus tard. Pour le moment, n'oubliez pas que lorsque vous l'appelez avec cette syntaxe précise, elle va exécuter un morceau de code JavaScript une fois que le navigateur a fini de charger le fichier HTML. C'est le bon momentsi l'on veut modifier le contenu avec du code JavaScript.

La fonction app.main fait partie de notre fichier JavaScript. Voir la section suivante.

Le modèle « Module »

Comme indiqué plus haut dans ce guide, lorsque vous déclarez simplement une variable dans Javascript, en dehors de toute fonction, ce sera une variable globale :

var my_var = "my value";

Ceci signifie que la variable my_var sera accessible depuis tous les fichiers Javascript importés par le fichier HTML. Ce qui est généralement considéré comme un mauvais style de programmation. Nous essaierons de l'éviter.

A la place, nous utiliserons ce que l'on nomme le pattern « module ». C'est un pattern utilisé pour déclarer des variables et des fonctions qui seront locales à un fichier. Voici le fichier app.js :

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

Nous pouvons décomposer le modèle Module comme suit :

Toutes les déclarations du fichier sont enveloppées dans une fonction anonyme qui est directement appelée.

Nous pouvons alors déclarer tout ce que nous voulons à l'intérieur de la fonction. Toute déclaration de variable ou fonction sera locale à la fonction du module et sera ainsi un membre privé de notre module

Nous ne déclarons q'une seule variable globale. Cette variable est un dictionnaire déclaré en utilisant la syntaxe app = {};. Rappelez-vous : quand vous déclarez une variable en Javascript sans utiliser le mot clé « var », elle sera globale . Le dictionnaire app est l'espace de nommage de notre module Javascript.

Pour exposer quoi que ce soit à l'extérieur du module, pour le rendre public, nous l'ajouterons à l'espace de nommage de app

16/67

Page 17: OpenERP Formation Web

OpenERP V7.0 04/08/14

Dans ce module, nous n'avons qu'une fonction publique : main. Si vous regardez le fichier HTML, vous verrez comment appeler cette fonction.:

app.main();

La seule chose que fait cette fonction pour l'instant est d'écrire un message dans la console de déboguage.

Outils de déboguage

Comme dans beaucoup de langages dynamiques, dans Javascript, il est très utile pour le développement de pouvoir déboguer votre code. Mais à la différence de langages comme Java ou C, la plupart des éditeurs Javascript ne fournissent pas de moyen de marquer des points d'arrêt. Ceci peut être remplacé par le mot clé « debugger » dans le code. Modifiez votre source en ajoutant ce mot clé avant l'appel à console.log() :

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

Lancez l'application et ouvrez les outils développeur (Ctrl+Shift+I or select Tools > Developer Tools dans le menu Chrome). Relancez maintenant l'application en utilisant F5.

Quand la fenêtre des outils développeur est affichée, Chrome s'arrête automatiquement lorsqu'il atteint le mot clé « debugger » et se positionne sur l'onglet « sources ». Vous pouvez alors utiliser les boutons en haut à droite pour avancer, reprendre, etc. Notez que vous pouvez fixer les points d'arrêt en cliquant sur les numéros de lignes à gauche ; mais cela peut être moins ergonomique si vous éditez le code dans votre éditeur de texte.

Un autre outil de débogage à signaler est la fonction console.log(). Il permet d'imprimer des messages qui peuvent être lus dans l'onglet Console des outils développeur.

17/67

Page 18: OpenERP Formation Web

OpenERP V7.0 04/08/14

Underscore.jsUnderscore.js est une bibliothèque fournissant diverses fonctions utilitaires pour Javascript. Elle est largement utilisée dans OpenERP et nous vous recommandons d'apprendre comment l'utiliser. Les fonctionsUnderscore.js sont toutes enveloppées dans un espace de nommage contenu dans la variable _.

Une des fonctions les plus utilisée de Underscore est _.each(). Comme expliqué dans la partie « bases de Javascript » de ce guide, il est déconseillé d'utiliser une boucle pour Javascript. _.each() en est un bon remplacant. Exemple:

var array = ["hello", "world"]_.each(array, function(x) { console.log(x);});// outputs:// hello// world

Le premier argument de _.each() doit être une liste ou un dictionnaire. Le second doit être une fonction. Si elle est appelée avec une liste comme premier argument, la fonction reçevra un paramètre : la valeur courante de la liste.

Si elle est appelée avec un dictionnaire , la fonction recevra deux paramètres. Le premier est la valeur courrante et la seconde la clé courante. Exemple :

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

Un autre exemple de fonction utile dans underscore.js est _.range(). Elle génère une liste de nombres :

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

Exercice – Utilisation d'Underscore.js

Dans la fonction main() de l'application de démarrage, créez un fragment de code qui additionne tous les nombres de 1 à 100 et imprime le résultat dans la console. Utilisez _.each() et _.range().

Solution:

(function() { app = {}; function main() { var x = 0; _.each(_.range(1, 101), function(i) { x += i; }); console.log("Result", x);

18/67

Page 19: OpenERP Formation Web

OpenERP V7.0 04/08/14

}; app.main = main;})();

Manipulations HTML avec jQueryjQuery est une bibliothèque Javascript tout comme Underscore dont le but principal est de fournir une couche d'abstraction vers l'API du navigateur web pour faciliter les opérations courantes et améliorer la compatibilité entre les différents navigateurs. Un des usages principaux de Jquery est d'aider à manipuler le HTML affiché dans le navigateur.

Le DOM

Le DOM (Document Object Model) est une représentation conceptuelle du contenu de la page web. Quand une portion de code Javascript veut modifier le contenu visuel d'une page, il modifie le DOM et le navigateurmodifiera l'affichage en conséquence.

Utilisez les outils développeur de Chrome, vous pouvez visualiser l'état du DOM grâce à l'onglet « Elements » .

Si nous exécutons un code Javascript simple dans l'onglet Console pour modifier le DOM, nous verrons ces modifications dans l'onglet Elements comme dans le navigateur lui-même. Exemple :

$("body").text("I changed the DOM")

Ce fragment de code effacera le contenu de la page web et écrira le texte « I Changed the DOM » à la place. Vous pouvez aussi voir les modification dans l'explorateur de DOM.

19/67

Page 20: OpenERP Formation Web

OpenERP V7.0 04/08/14

Vous pouvez aussi utiliser l'onglet Elements pour modifier le DOM. Ce qui peut être parfois utile pour tester différentes présentations par exemple.

Sélecteurs jQuery

Pour sélectionner une partie du DOM, nous utilisons ce qu'on appelle un sélecteur Jquery. Pour cela, nous appelons la méthode Jquery en lui donnant une chaîne comme argument.

$("body")

Lorsque la bibliothèque Jquery est chargé dans un projet, elle définit deux variables : Jquery et $. Ces deux variables sont en réalité la même chose : La fonction Jquery. Lorsqu'appelée avec un premier argument, elle essaiera de trouver un ou plusieurs éléments dans le DOM qui correspondent à cette expression.

Beaucoup de lecteurs peuvent déjà connaître Xpath, qui est un langage utilisé pour définir des expression à rechercher dans les éléments XML. Ce langage aurait pu être choisi par les créateurs de Jquery pour sélectionner les éléments HTML, mais ils ont décidé d'utiliser une autre syntaxe plus proche des CSS.

Pour sélectionner tous les éléments ayant une certaine balise vous pouvez simplement inscrire le nom de la balise dans l'expression.:

$("input") // all <input> elements$("div") // all <div> elements

Pour sélectionner un élément par un ID précis, vous devez écrire le signe # avant l'identifiant.

20/67

Page 21: OpenERP Formation Web

OpenERP V7.0 04/08/14

$("#content") // the element with id 'content'

Pour sélectionner tous les éléments d'une classe CSS donnée, utilisez le caractère « . ».

$(".title") // the elements with the 'title' class

Tout comme en CSS, vous pouvez aussi combiner ces sélecteurs :

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

Lorsque $() est appelée, elle renvoie un objet Jquery. Cet objet peut être vu comme une liste avec un pointeur sur tous les éléments du DOM correspondant à l'expression donnée, avec en plus un grand nombre de méthodes utiles.

Vous trouverez plus de documentation au sujet des sélecteurs Jquery dans la documentation Jquery :

http://api.jquery.com/category/selectors/ .

Événements jQuery

La plupart des éléments HTML sont capables de générer des événements. jQuery permet de capter les événements et d'exécuter du code Javascript. Dans l'exemple suivant, nous capturons l'événement « clic » sur le bouton affiché sur la page pour imprimer un message dans la console.

$("button").click(function() { console.log("someone clicked on the button");});

Ici, nous passons une fonction à la méthode click() sur l'objet jQuery. Lorsque l'événement est déclenché, la fonction sera appelée. Notez que, s'il y'a plusieurs éléments sélectionnés par notre appel à $(), l'événement 'clic' sera lié à tous ces boutons.

De nombreux événements peuvent être détectés, ex : la souris passant sur un élément, l'appui sur une touche du clavier par l'utilisateur, un champ « input » modifié, etc. Vous trouverez plus de documentation surles événements dans la documentation Jquery :

http://api.jquery.com/category/events/

Modifications du DOM avec jQuery

Jquery fournit de nombreuses fonctions pour modifier le DOM

Pour remplacer le contenu d'une balise Jquery et par du code HTML, vous pouvez utiliser la méthode html() :

$(".main_content").html('<div style="color: white">Hello world!</div>');

Pour éviter de remplacer tout le contenu et ajouter simplement du code HTML, utilisez la méthode append() :

$(".main_content").append('<div style="color: red">Hello world again!</div>');

Pour faire le contraire, ajouter du HTML avant un élément, utilisez prepend() :

21/67

Page 22: OpenERP Formation Web

OpenERP V7.0 04/08/14

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

Pour ajouter du texte à un élément, il est déconseillé d'utiliser la méthode html(), car ce texte pourrait contenir des caractères ressemblant à du code HTML et le navigateur pourrait essayer de l’interpréter comme du vrai HTML. Pour éviter cela, le texte doit être échappé, ce qui peut être réalisé automatiquement en utilisant la méthode text() :

$(".main_content").text('The <div> element will appear as-is in the browser.');

Exercice

Exercice - Usage de jQuery

Modifiez le code de l'application de démarrage afin que lorsque l'utilisateur clique sur le bouton, il compte de 1 à 10 et imprime chaque chiffre sur une nouvelle ligne dans la zone de gauche

Solution:

(function() { app = {}; function main() { $("button").click(function() { _.each(_.range(1, 11), function(i) { $(".main_content").append("<div>" + i + "</div>"); }); }); }; app.main = main;

})();

Requêtes HTTP avec jQueryDans une application de bases de données comme OpenERP, le code Javascript doit communiquer avec le serveur. Jquery fournit une fonction pour cela.

Nous utiliserons aussi le format JSON. JSON signifie JavaScript Object Notation. C'est un format léger utilisé pour encoder des données à échanger entre différents ordinateurs. Du fait de sa simplicité, il est très facile àimplémenter dans n'importe quel langage de programmation et la grande majorité des langages existants ont déjà une ou plusieurs implémentations d'un emetteur/récepteur JSON. Voicie un exemple qui montre l'ensemble des types JSON :

{ "string": "this is a string", "number": 42, "array": [null, true, false], "dictionary": {"name": "a simple dictionary"}}

Pour tester les requêtes HTTP en Javascript, nous utiliserons l'application Python app.py qui contient certains web service jSON :

22/67

Page 23: OpenERP Formation Web

OpenERP V7.0 04/08/14

@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, })

Expliquer comment fonctionne la bibliothèque Python Flask utilisée pour créer ces services web dépasserait le cadre de ce guide. Souvenez-vous seulement que ce code déclare deux URL qui acceptent et renvoient duJSON et effectuent des opérations mathématiques simples (addition et multiplication). Nous allons écrire ducode Javascript pour contacter ces services web.

La méthode $.ajax()

La méthode utilisée pour effectuer une requête HTTP en Jquery est la méthode $.ajax(). L'acronyme AJAX signifiant Asynchronous JavaScript and XML, un nom qui a perdu sa signification aujourd'hui car les développeurs l'utilisent pour envoyer d'autres données que du XML. Cependant, le terme est toujours utilisédans la documentation Jquery.

Voici un exemple qui appellera le web service /service_plus :

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

• Le premier argument de $.ajax() est l'URL à laquelle envoyer la requête.

• Le second argument est un dictionnaire qui peut contenir beaucoup de paramètres. Voici le

paramètres que nous utilisons :

• type test le type de requête HTTP. Ici, nous utilisons la méthode POST.

• dataType: "json" est utilisé pour informer Jquery que le serveur retournera du JSON afin qu'il lise ce

JSON et retourne des objets Javascript au-lieu d'une simple chaîne.

23/67

Page 24: OpenERP Formation Web

OpenERP V7.0 04/08/14

• data désigne les données que nous envoyons au serveur. Ici, nous voulons envoyer une chaîne JSON

contenant un dictionnaire avec deux clés 'a' et 'b'. Nous devons appeler la méthode JSON.stringify, une méthode standard en Javascript pour convertir les objets en chaînes JSON.

• contentType est le type MIME des données que nous envoyons au serveur. La valeur que nous

utilisons informe le serveur que nous envoyons du JSON .

$.ajax() retourne ce qu'on appelle une promesse. Les promesses sont des objets créés par Jquery que nous expliquerons plus tard. Pour l'instant, comprenez que lorsque nous appelons la méthode then() sur cette promesse, elle sera reliée à une fonction à appeler lorsque nous reçevrons la réponse du serveur. Ce premier argument de cette fonction est l'objet JSON retourné par le web service /service_plus.

$.ajax() contient bien plus de paramètres pour envoyer tous types de requêtes HTTP. Pour en savoir plus, reportez-vous à la documentation Jquery :

http://api.jquery.com/jQuery.ajax/ .

Promesses et différés

En tant que langage, JavaScript est fondamentalement mono threadé. Ceci veut dire que toute requête ou calcul bloquant, figera la page entière (et dans les anciens navigateurs, le logiciel lui-même jusqu'à empêcherl'utilisateur de passer à un autre onglet) : Un environnement Javascript peut être vu comme une boucle infinie basé sur les événements dans laquelle les développeurs n'ont pas le contrôle sur la boucle elle-même.

En conséquence, l'exécution de longues requêtes synchrones sur le réseau ou d'autres types d'accès complexes et coûteux sont déconseillés et on utilise les API asynchrones à la place.

Le code asynchrone n'est pas naturel, en particulier pour les développeurs habitués à du code synchrone côté serveur (en Python, Java ou C#) où le code sera bloqué jusqu'à la fin de l'action. Ceci est agravé par le fait que la programmation asynchrone n'est pas un concept excellent et est implémenté en utilisant des appels 'callback'.

Cette partie apportera quelques solutions pour gérer les systèmes asynchrones, et vous avertir des problèmes et des pièges courants.

Les différés sont une forme de promesse. Le Web OpenERP utilise actuellement les différés Jquery.

L'idée de base des différés est que les méthodes potentiellement asynchrones renvoient un objet Deferred()à la place d'une valeur arbitraire ou (plus communément) rien. Les Différés peuvent être vus comme une promesse de valeur ou erreur. Cet objet peut alors être utilisé pour suivre la fin de l'opération asynchrone eny ajoutant des appels en retour. Des appels de succès ou d'echec.

Un grand avantage des différés sur le fait de passer simplement des fonctions d'appels en retour aux méthodes asynchrones est la possibilité de les assembler.

La méthode principale pour les différés est $.Deferred.then(). Elle est utilisée pour rattacher de nouveaux appels en retour à l'objet différé. Le premier paramètre attache un appel de succès, appelé quand l'objet différé est résolu avec succès et renveigné avec la(les) valeur(s) résolue(s) pour l'opération asynchrone.

$.ajax(...).then(function(a) { console.log("Asynchronous operation completed, the value is:", a);});

Le second paramètre rattache une méthode sur échec appelée lorsque l'objet différé est rejeté et renseigné

24/67

Page 25: OpenERP Formation Web

OpenERP V7.0 04/08/14

avec des valeurs de rejet (souvent un message d'erreur).

$.ajax(...).then(function(a) { ...}, function() { console.error("The asynchronous operation failed");});

Les appels en retour attachés aux différés ne sont jamais « perdus » : si un appel en retour est attaché à un différé déjà résolu ou rejeté, l'appel sera appelé (ou ignoré) immédiatement.

Pour mettre cela en évidence, nous pouvons créer notre propre instance $.Deferred et appeler la méthode resolve() pour la résoudre :

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 call then()

Un différé est résolu ou rejeté une fois seulement. Un différé donné ne peut pas appeler un même appel en retour deux fois, ou appeler à la fois des appels en retour de succès et d'échec.

Exemple :

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 resolve()// will not make the deferred call its binded functions a second time

Combiner des différés

Les différés sont vraiment utiles lorsque le code nécessite l'enchaînement d'opérations asynchrones d'une manière ou d'une autre, car ils peuvent être utilisés comme bases de ces assemblages.

Il y a deux formes principales de combinaisons sur les différés : multiplexage et chaînage

Différé multiplexé

La raison la plus courante de multiplexer les différés est simplement d'opérer 2 ou plus opérations asynchrones et souhaiter attendre qu'elles soient toutes terminées avant d'aller plus loin. (et d'effectuer d'autres opérations).

La fonction Jquery de multiplexage pour les promesses est $.when(). Cette fonction peut prendre n'importe quelle quantité de promesses et retournera une nouvelle promesse. Cette promesse retournée sera résolue quand toutes les promesses multiplexées seront résolues, et sera rejetée dès qu'une des promesses multiplexées sera rejetée.

25/67

Page 26: OpenERP Formation Web

OpenERP V7.0 04/08/14

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) { console.log("3+5=", result1[0].addition); console.log("6+7=", result2[0].addition);});

Les arguments donnés à la fonction liée à la promesse retournée par $.when() sont un peu compliqués. Pour chaque promesse passée à $.when(), la fonction recevra un paramètre. Chaque paramètre est une tableau contenant tous les paramètres qui auraient pu être passés à une fonction liée au différé original. Ainsi, si nous voulons le premier paramètre de cette fonction, nous devrons utiliser le résultat1[0].

Différés chaînés

Une deuxième combinaison utile consiste à démarrer une opération asynchrone comme le résultat d'une autre opération asynchrone, et vouloir le résultat de la seconde

Pour ce faire, la fonction que vous liez au différé en utilisant then() doit retourner un autre différé.

Exemple :

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;

26/67

Page 27: OpenERP Formation Web

OpenERP V7.0 04/08/14

});def1.then(function(result) { console.log("3*5*7=", result.multiplication);});

Pour comprendre comment ceci marche, il est important de savoir que then() retourne une nouvelle promesse. Cette promesse représente le résultat de l'exécution de la fonction donnée après que la promesse ait été résolue. Si la fonction liée renvoie une nouvelle promesse, le résultat de l'appel à then() devient un équivalent de cette nouvelle promesse. Ainsi dans le code ci-dessus, def1 et def2 sont des promesses qui seront résolues en même temps et avec les mêmes arguments (sauf si le premier appel à ajax() échoue, et dans ce cas, le second appel à ajax() ne se produira jamais et def2 sera marqué comme rejeté).

Meilleures pratiques pour l'utilisation de code asynchrone

Dans le monde réel les appels à des situations asynchrones peut mener à du code très complexe ; de gros programmes peuvent appeler des centaines de fonctions. Même si un petit nombre de ces fonctions effectuent des opérations asynchrones, elles affecteront tout le programme. C'est pourquoi les différés Jquery sont très utiles ; Ils fournissent une structure génériqe pour l'exécution de code asynchrone et, grâceà la combinaison de différés, il devient possible de simplifier toutes les situations en un unique différé.

Une pratique simple consiste à toujours retourner un différé pour toutes les fonctions qui effectuent de opérations asynchrones. Ceci est dû au fait que toute autre fonction appelant cette fonction pourrait avoir besoin de savoir quand elle est terminée.

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, bytransitivity, a function // that performs an asynchronous call. So it should return a deferred too. return def2;

27/67

Page 28: OpenERP Formation Web

OpenERP V7.0 04/08/14

Framework Web OpenERPDans la première partie de ce guide, nous avons expliqué le langage Javascript et quelques bibliothèques communément utilisées avec lui (jQuery et Underscore.js). Néanmoins il nous manque certaines fonctionnalités primordiales pour pouvoir programmer efficacement une application d'une certaine taille comme OpenERP, nous n'avons pas vraiment d'outils pour une Programmation Orientée Objet. Pas de structure pour l'interface utilisateur graphique, nos assistants pour récupérer des données du serveur sont trop basiques, etc.

C'est pourquoi le client web OpenERP contient un framework de développement web pour apporter une structure et les fonctionnalités nécessaires à la programmation de tous les jours. Ce chapitre présente ce framework.

Un module simple pour tester le frameworkIl n'est pas réellement possible d'inclure les multiples fichiers Javascript du framework web d'OpenERP dans un simple fichier HTML comme nous l'avons fait dans le chapitre précédent. Aussi, nous allons créer un module dans OpenERP qui contiendra la configuration nécessaire pour pouvoir utiliser le framework web.

Pour télécharger le module exemple, utilisez la commande bazaar :

bzr branch lp:~niv-openerp/+junk/oepetstore -r 1

Maintenant, ajoutez ce dossier au chemin de vos addons lorsque vous lancez OpenERP ( le paramètre –addons-path ajouté au lancement de l'éxécutable openerp-server). Créez ensuite une base de donnée et installez le module oepetstore

oepetstore

|-- __init__.py

|-- __openerp__.py

|-- petstore_data.xml

|-- petstore.py

|-- petstore.xml

`-- static

`-- src

|-- css

| `-- petstore.css

|-- js

| `-- petstore.js

`-- xml

`-- petstore.xml

Ce nouveau module contient déjà certaines personnalisations qui devraient être faciles à comprendre si vousavez déjà écrit un module OpenERP. Un nouvelle table, quelques vues, des menus, etc.

28/67

Page 29: OpenERP Formation Web

OpenERP V7.0 04/08/14

Nous reviendrons plus tard sur ces éléments car ils seront utiles pour développer notre exemple. Pour l'instant concentrons nous sur l'essentiel : les fichiers dédiés au développement web.

Notez que tous les fichiers nécessaires à un module web OpenERP doivent toujours être placés dans le dossier « static » du module. C'est obligatoire du fait de possibles problèmes de sécurité. Le fait que nous ayons créé les dossiers css, js et xml est une simple convention.

oepetstore/static/css/petstore.css est notre fichier CSS. Il est vide pour l'instant et recevra les directives CSS don nous aurons besoin.

oepetstore/static/xml/petstore.xml est un fichier XML qui contiendra nos templates Qweb. Pour l'instant, il est presque vide. Ces templates seront expliqués plus tard dans la partie consacrée aux templates QWeb

oepetstore/static/js/petstore.js est probablement le fichier le plus intéressant. Il contient le code Javascript de notre application. Voici à quoi il ressemble pour l'instant :

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');}

Les divers éléments de ce fichier seront expliqués progressivement. Sachez seulement qu'il ne fait pas grand-chose pour l'instant à part afficher une page vide et imprimer un petit message dans la console.

Comme les fichiers XML OpenERP contenant des vues ou des données, ce fichier doit être déclaré dans le ficher __openerp__.py. Ci-dessous les lignes ajoutés à ce fichier pour indiquer les fichiers à charger par le client web:

'js': ['static/src/js/*.js'],'css': ['static/src/css/*.css'],'qweb': ['static/src/xml/*.xml'],

Ces paramètres de configuration utilisent des jokers, ainsi nous pouvons ajouter des fichiers sans modifier __openerp__.py: Ils seront chargés par le client web pour peu qu'ils aient la bonne extension dans le bon dossier.

Attention

Dans OpenERP, tous les fichiers Javascript sont, par défaut, contractés en un seul fichier. Nous effectuons alors une opération appelée minification sur ce fichier. La minification retire tous les commentaires, espaces vides et fins de lignes du fichier avant de l'envoyer au navigateur.

Cette opération peut paraître complexe, mais c'est une procédure courant dans les applications complexes comme OpenERP contenant de nombreux fichiers Javascript. Cela permet de charger l'application bien plus vite.

Cela a pour inconvénient majeur de rendre l'application pratiquement impossible à déboguer. La solution

29/67

Page 30: OpenERP Formation Web

OpenERP V7.0 04/08/14

pour éviter cet effet de bord et pouvoir déboguer est d''ajouter un argument à l'URL utilisé pour charger OpenERP : ?debug. L'URL ressemblera à ceci:

http://localhost:8069/?debug

Lorsque vous utilisez ce type d'URL, l'application n'opère pas la minification des fichiers Javascript. Le chargement sera plus lent mais vous pourrez déboguer.

Le module javascript OpenERPDans le chapitre précédent, nous avons expliqué que Javascript n'a pas de mécanisme correct pour gérer lesespaces de nommage des variables déclarées dans les différents fichiers Javascript et nous avons proposé une méthode simple appelé pattern de module.

Dans le framework web d'OpenERP il y a un équivalent à ce pattern intégré avec le reste du framework. Notez qu'un module web OpenERP est un concept différent de celui d'un addon OpenERP. Un addon est un dossier avec de nombreux fichiers, un module web n'est pas beaucoup plus qu'un espace de nommage pour Javascript.

Le fichier oepetstore/static/js/petstore.js déclare déjà un tel module :écutable openerp-server). Créez ensu

openerp.oepetstore = function(instance) { instance.oepetstore = {}; instance.oepetstore.xxx = ...;}

Dans le framework web OpenERP, vous déclarez un module Javascript en déclarant une fonction que vous placez dans la variable globale openerp. L'attribut que vous définissez dans cet objet doit avoir exactement le même nom que votre module OpenERP (ce module est nommé oepetstore, si vous définissez openerp.petstore au lieu de openerp.oepetstore, cela ne fonctionnera pas ).

Cette fonction sera appelée lorsque le client web décide de charger votre module. On lui passe un paramètre nommé instance, qui représente l'instance courante du client web OpenERP et contient toutes les données relatives à la session courante ainsi que les variables de tous les modules web.

La convention est de créer un nouvel espace de nommage dans l'objet instance ayant le même nom que votre module. C'est pourquoi nous définissons un dictionnaire vide dans instance.oepetstore. Ce dictionnaireest l'espace de nommage que nous utiliserons pour déclarer toutes les classes et variables utilisées à l'intérieur de notre module web.

Les classesJavaScript n'a pas de mécanisme de classe comme la plupart des langages de programmation orientés objet.Pour être plus exact, il fournit des éléments de langage pour la programmation objet, mais vous devez définir vous-même de le faire. Le framework web OpenERP fournit des outils pour simplifier cela et laisser les programmeurs coder d'une manière similaire à celle qu'ils utiliseraient dans d'autres langages comme Java. Ce système de classe est largement inspiré de l'héritage Javascript simple de John Resig .

Pour définir une classe, vous avez besoin d'hériter de la classe web « instance » . En voici la syntaxe :

instance.oepetstore.MyClass = instance.web.Class.extend({ say_hello: function() { console.log("hello");

30/67

Page 31: OpenERP Formation Web

OpenERP V7.0 04/08/14

},});

Comme vous pouvez le voir, vous devez appeler la méthode instance.web.Class.extend() et lui donner en paramètre un dictionnaire. Ce dictionnaire contiendra les méthodes et attributs de classe/. Ici, nous définissons seulement une méthode nommée say_hello(). Cette classe peut être instanciée et utilisée comme suit :

var my_object = new instance.oepetstore.MyClass();my_object.say_hello();// print "hello" in the console

Vous pouvez accéder aux attributs d'une classe dans une méthode comme ceci :

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

Les classes peuvent avoir un constructeur. Cette méthode sera nommée init(). Vous pouvez passer des paramètres au constructeur comme dans la plupart des langages :

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

Les classes peuvent être héritées. Pour ce faire, utilisez extend() directement sur votre classe de la même manière que vous avez surchargé la classe instance.web.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

Lorsque vous surchargez une méthode en utilisant l'héritage, vous pouvez utiliser this._super() pour appeler la méthode originale. this._super() n'est pas une méthode normale de votre classe. Vous pouvez considérer que c'est « magique ». Exemple :

31/67

Page 32: OpenERP Formation Web

OpenERP V7.0 04/08/14

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

Les bases des widgetsDans le chapitre précédent, nous avons découvert jQuery et ses outils de manipulation du DOM. C'est utile, mais pas suffisant pour structurer une véritable application. Les bibliothèques d'interfaces graphiques Utilisateur comme Qt, GTK ou Windows Forms ont des classes pour représenter les composants visuels. DansOpenERP, nous avons la classe Widget, Un widget est un composant générique dédié à l'affichage de contenu pour l'utilisateur.

Votre premier widget

Le module de démarrage que vous avez installé contient un petit widget :

instance.oepetstore.HomePage = instance.web.Widget.extend({ start: function() { console.log("pet store home page loaded"); },});

Ici, un widget simple est créé en étendant la classe instance.web.Widget. Celui-ci définit une méthode nommée start(), qui ne fait rien de spécial pour l'instant,

Vous avez peut-être remarqué la ligne à la fin du fichier :

instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');

Cette ligne enregistre notre widget basique en tant qu'action client. Les actions client seront expliquées dans la suite de ce guide. Pour l'instant, souvenez-vous que cela permet à notre widget d'être affiché lorsque nous cliquons sur l'item de menu Pet Store > Store > Home Page.

Affichage du contenu

Les widgets ont de multiples méthodes et fonctionnalités, mais commençons avec les bases : afficher des données dans un widget comment instancier un widget et l'afficher.

Le widget HomePage a déjà une méthode start(). Cette méthode est automatiquement appelée après que lewidget ait été instancié et ait reçu l'ordre d'afficher son contenu. Nous l'utiliserons pour afficher du contenu pour l'utilisateur.

Pour cela, nous utiliserons aussi l'attribut $el que tous les widgets contiennent. Cet attribut est un objet Jquery avec une référence à l'élément HTML qui représente la racine de notre widget. Un widget peut contenir plusieurs éléments HTML, mais ils doivent être contenus à l'intérieur d'un même élément. Par

32/67

Page 33: OpenERP Formation Web

OpenERP V7.0 04/08/14

défaut, tous les widgets ont un élément racine vide qui est un élément HTML <div>.

Un élément <div> en HTML est d'habitude invisible à l'utilisateur s'il n'a aucun contenu. C'est pourquoi vous ne voyez rien lorsque le widget instance.oepetstore.HomePage est affiché. Pour voir quelque chose, nous ajouterons quelques méthodes simples Jquery pour ajouter du HTML à notre élément racine.

instance.oepetstore.HomePage = instance.web.Widget.extend({ start: function() { this.$el.append("<div>Hello dear OpenERP user!</div>"); },});

Ce message apparaîtra lorsque vous cliquerez sur le menu Petstore > Petstore > Home Page. (N'oubliez pas de rafraîchir votre navigateur, bien qu'il ne soit pas nécessaire de redémarrer le serveur OpenERP.

Maintenant, vous devriez apprendre comment instancier un widget et afficher son contenu. Créons un autre 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>"); },});

Pour afficher instance.oepetstore.GreetingsWidget dans la page d'accueil, nous pouvons utiliser la méthode append de 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); },});

Ici, HomePage instancie un widget Greetings (le premier argument du constructeur de GreetingsWidget seraexpliqué plus bas). Puis, il demande au widget Greetings de s'insérer dans le DOM, et plus précisément sous le widget HomePage.

Lorsque la méthode append() est appelée, elle demande au widget de s'insérer et d'afficher son contenu. C'est pendant l'appel à append() que la méthode start() sera appelée.

Pour vérifier l'effet de ce code, ouvrons l'explorateur de DOM de Chrome. Mais avant cela, nous modifieronsun peu nos widgets pour avoir des classes sur certains de nos <div> afin de les distinguer clairement dans l'explorateur :

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

33/67

Page 34: OpenERP Formation Web

OpenERP V7.0 04/08/14

... },});

Dans l'explorateur, vous devriez voir :

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

Ici, nous voyons les deux <div> créés par la classe Widget. Nous voyons aussi les contenus des <div> générés par les méthodes Jquery sur $el.

Widgets parents et enfants

Dans la partie précédente, nous avons instancié un widget en utilisant la syntaxe :

new instance.oepetstore.GreetingsWidget(this);

Le premier argument de l'instanciation, qui dans ce cas, une instance de HomePage indique au Widget quel est son widget parent.

Comme nous l'avons vu, les widgets sont généralement insérés dans le DOM par une autre widget et à l'intérieur de cet autre widget. Ceci signifie que la plupart des widgets sont toujours inclus dans d'autres widgets. Nous appelons parent le container et enfant le contenu.

Pour des raisons techniques et conceptuelles, il est nécessaire que chaque Widget connaisse son parent et qui sont ses enfants. C'est pourquoi, nous utilisons ce paramètre dans le constructeur de tous les Widgets.

La méthode getParent() peut être utilisée pour obtenir le parent d'un widget :

instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({ start: function() { console.log(this.getParent().$el ); // will print "div.oe_petstore_homepage" in the console },});

La méthode getChildren() peut être utilisée pour récupérer la liste des enfants:

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

Notez que, lorsque vous surchargez la méthode init() d'un widget, vous devriez toujours indiquer le parent

34/67

Page 35: OpenERP Formation Web

OpenERP V7.0 04/08/14

comme premier paramètre et le passer à this._super() :

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

Enfin, si un widget n'a pas logiquement de parent (ex : c'est le premier widget que vous instanciez dans une application), vous pouvez donner comme parent :

new instance.oepetstore.GreetingsWidget(null);

Supprimer des widgets

Si vous pouvez afficher du contenu, vous devriez aussi pouvoir l'effacer. Utilisez pour cela, la méthode destroy().

greeting.destroy();

Lorsqu'un widget est détruit il appellera d'abord destroy() sur tous ses enfants. Ensuite, il s'efface lui-même du DOM. L'appel récursif à destroy depuis les parents vers les enfants est très utile pour nettoyer correctement des structures complexes de widgets et éviter des fuites mémoires qui peuvent facilement apparaître dans des applications Javascript importantes.

Le moteur de template QWebLes chapitres précédents ont montré comment définir des widgets capables d'afficher du HTML. L'exemple GreatingsWidget a utilisé la syntaxe :

this.$el.append("<div>Hello dear OpenERP user!</div>");

Ceci permet techniquement d'afficher n'importe quel code HTML, même si c'est très compliqué et nécessite d'être généré par du code. Générer du texte en utilisant seulement du Javascript n'est pas très élégant, cela nécessiterait de copier-coller de nombreuses lignes HTML dans le fichier source Javascript, ajouter le caractère « en début et fin de chaque ligne, etc.

Le problème est le même dans la plupart des langages de programmation demandant de générer du HTML. C'est pourquoi on utilise des moteurs de template. Des exemples de moteurs de template sont Velocity, JSP(Java), Mako, Jinja (Python), Smarty (PHP), etc...

Dans OpenERP, nous utilisons un moteur de template développé spécifiquement pour le client web OpenERP. Son nom est QWeb.

QWeb est un langage de template basé sur XML, similaire à Genshi, Thymeleaf or Facelets avec quelques particularités :

• Il est implémenté entièrement en Javascript et affiché dans le navigateur.

• Chaque fichier (fichiers XML) contient plusieurs templates, alors que les moteurs de template ont

généralement un fichier par template

35/67

Page 36: OpenERP Formation Web

OpenERP V7.0 04/08/14

• Il gère le widget du Web OpenERP, bien qu'il puisse être utilisé en dehors du client web OpenERP (et

il est possible d'utiliser instance.web.Widget sans se servir de Qweb).

La raison d'utiliser Qweb à la place des moteurs de template existants est que son mécanisme d'extension est très similaire au mécanisme d'héritage des vues d'OpenERP. Comme les vues OpenERP un template Qweb est un arbre XML et donc les opérations Xpath ou DOM sont faciles à effectuer.

Utiliser Qweb dans un widget

Définissons d'abord un template simple Qweb dans le fichier oepetstore/static/src/xml/petstore.xml, son sens exact sera expliqué plus tard :

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

Maintenant, modifions la classe HomePage. Vous vous souvenez de cette ligne au début du fichier source Javascript ?

var QWeb = instance.web.qweb;

Nous vous avons recommandé de coller cette phrase dans tous les modules web OpenERP. C'est l'objet donnant accès à tous les templates définis dans les fichiers template chargés par le client web. Nous pouvons utiliser le template défini dans notre fichier de template XML comme ceci :

instance.oepetstore.HomePage = instance.web.Widget.extend({ start: function() { this.$el.append(QWeb.render("HomePageTemplate")); },});

L'appel à la méthode QWeb.render() demande d'afficher le template identifié par la chaîne passée en premier paramètre.

Une autre possibilité fréquemment rencontrée dans le code OpenERP est d'utiliser l'intégration du Widget avec Qweb :

instance.oepetstore.HomePage = instance.web.Widget.extend({ template: "HomePageTemplate", start: function() { ... },});

Lorsque vous ajoutez un attribut template dans une classe Widget, le widget sait qu'il doit appeler Qweb.render() pour afficher ce template.

Notez qu'il y a une différence entre ces deux syntaxes. Lorsque nous utilisons l'intégration du Widget à Qweb.la méthode Qweb.render() est appelée avant que le widget appelle start(). Elle prendra aussi l'élément racine du template affiché et le mettra à la place de l'élément racine par défaut généré par la

36/67

Page 37: OpenERP Formation Web

OpenERP V7.0 04/08/14

classe Widget. Ceci modifiera le comportement, il faut vous en souvenir.

Contexte QWeb

Comme avec les moteurs de template, Les templates Qweb peuvent contenir du code capable de manipuler des données envoyées au template. Pour envoyer des données à Qweb, utilisez un second argument à Qweb.render():

<t t-name="HomePageTemplate"> <div>Hello <t t-esc="name"/></div></t>

QWeb.render("HomePageTemplate", {name: "Nicolas"});

Resultat:

<div>Hello Nicolas</div>

Lorsque vous utilisez l'intégration des Widgets vous ne pouvez pas passer de donnée additionnelles au template, à la place, le template aura une variable unique qui est une référence au widget courant :

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

Déclaration de template

Maintenant que nous savons comment afficher des templates, nous pouvons essayer de comprendre la syntaxe Qweb.

Toutes les directives Qweb utilisent des attributs XML commençant avec le préfixe t-. Pour déclarer de nouveaux templates, nous ajoutons un élément <t t-name="..."> dans le fichier de template XML à l'intérieur de l'élément racine : <templates>:

<templates> <t t-name="HomePageTemplate"> <div>This is some simple HTML</div>

37/67

Page 38: OpenERP Formation Web

OpenERP V7.0 04/08/14

</t></templates>

t-name déclare simplement un template qui peut être appelé par la méthode QWeb.render().

Escaping

Pour placer du texte dans le HTML, utilisez t-esc:

<t t-name="HomePageTemplate"> <div>Hello <t t-esc="name"/></div></t>

Ceci affichera la variable name et échappera son contenu au cas où il contiendrait des caractères ressemblant à du HTML. Notez que l'attribut t-esc peu contenir tout type d'expression Javascript :

<t t-name="HomePageTemplate"> <div><t t-esc="3+5"/></div></t>

Ceci produira :

<div>8</div>

Afficher du HTML

Si vous savez que vous avez du HTML contenu dans un variable, utilisez t-raw au lieu de t-esc :

<t t-name="HomePageTemplate"> <div><t t-raw="some_html"/></div></t>

If

le bloc conditionnel de base de Qweb est 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>

Bien que Qweb ne contienne aucune structure pour else :

Foreach

Pour itérer sur une liste, utilisez t-foreach et t-as:

<t t-name="HomePageTemplate"> <div>

38/67

Page 39: OpenERP Formation Web

OpenERP V7.0 04/08/14

<t t-foreach="names" t-as="name"> <div> Hello <t t-esc="name"/> </div> </t> </div></t>

Définir de la valeur d'un attribut XML

QWeb a une syntaxe spéciale pour définir la valeur d'un attribut. Vous devez utiliser t-att-xxx et remplacer xxx par le nom de l'attribut:

<t t-name="HomePageTemplate"> <div> Input your name: <input type="text" t-att-value="defaultName"/> </div></t>

Pour en savoir plus QWeb

Ce guide ne prétend pas être une référence pour Qweb, consultez la documentation pour plus d'informations :

https://doc.openerp.com/trunk/web/qweb

Exercice

Exercice - Usage de QWeb dans les Widgets

Créez un widget dont le constructeur contient deux paramètres autres que parent. Product_names et color. Product_names est une liste de chaînes, chacune étant le nom d'un produit.Color est une couleur au format CSS (ex: #000000 pour le noir).Ce widget devrait afficher les noms de produits figurant les uns sous les autres, chacun dans une case séparée avec sa couleur de fond et une bordure. Vous devez utiliser Qweb pour afficher le HTML. Cet exercice nécessitera du CSS à placer dans :

oepetstore/static/src/css/petstore.css

Affichez ce widget dans le widget HomePage avec une liste de 5 produits et 'vert' en couleur de fond des cases.

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", "graphic card", "screen"], "#00FF00");

39/67

Page 40: OpenERP Formation Web

OpenERP V7.0 04/08/14

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 t-esc="product"/></span><br/> </t> </div> </t></templates>

.oe_products_item { display: inline-block; padding: 3px; margin: 5px; border: 1px solid black; border-radius: 3px;}

40/67

Page 41: OpenERP Formation Web

OpenERP V7.0 04/08/14

Événements et propriétés des WidgetsIl y a d'autres assistants à connaître sur les Widgets. Un des plus complexe (et utile) est la gestion d'événements étroitement liée aux propriétés de widget.

Événements

Les Widgets sont capables de déclencher des événements comme la plupart des composants des interfaces graphiques existantes (Qt, GTK, Swing,...) Exemple :

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

41/67

Page 42: OpenERP Formation Web

OpenERP V7.0 04/08/14

widget.on("user_choose", this, this.user_choose); widget.appendTo(this.$el); }, user_choose: function(confirm) { if (confirm) { console.log("The user agreed to continue"); } else { console.log("The user refused to continue"); } },});

Nous expliquerons d'abord ce que cet exemple est supposé faire. Nous créons un widget générique pour demander à l'utilisateur s'il veut réellement exécuter une action qui pourrait avoir des conséquences importantes (un widget largement utilisé sous Windows). Pour cela, nous créons deux boutons dans le widget. Puis nous lions les événements Jquery pour savoir si l'utilisateur clique sur ces boutons.

Note

Cette ligne peut être difficile à comprendre :

var self = this;

Rappelez-vous, dans Javascript, la variable this est une variable passée implicitement à toutes les fonctions. Elle nous permet de savoir quel est l'objet si la fonction est utilisée comme une méthode. Chaque fonction déclarée a son propre 'this'. Ainsi, lorsque nous déclarons une fonction à l'intérieur d'une autre fonction, cette nouvelle fonction aura son propre 'this' qui pourra être différent de celui de la fonction parente. Si nous voulons nous rappeler de l'objet original, la méthode la plus simple est de stocker une référence dans une variable. Par convention, dans OpenERP nous nommons très souvent cette variable 'self', car c'est l'équivalent de 'this' en Python.

Du fait que notre widget est supposé être générique, il ne devrait exécuter aucune action par lui-même. Aussi, nous lui faisons simplement déclencher une événement nommé 'user_choose' en utilisant la méthode 'trigger()'.

Widget.trigger(event_name [, ...]) prend comme premier argument le nom de l'événement à déclencher. Ils prend ensuite n'importe quel nombre d'arguments additionnels. Ces arguments seront passés aux récepteurs de l'événement.

Nous modifions alors le widget HomePage pour instancier un ConfirmWidget et être à l'écoute de son événement user_choose en appelant la méthode 'on()'.

Widget.on(event_name, object, func) permet de lier une fonction à appeler lorsque l'événement identifié par event_name est déclenché. L'argument 'func' est la fonction à appeler et 'object' est l'objet auquel cettefonction est reliée si c'est une méthode. La fonction liée sera appelée avec les arguments additionnels de 'trigger() si'il y en a.

Exemple :

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

42/67

Page 43: OpenERP Formation Web

OpenERP V7.0 04/08/14

console.log(a, b, c); // will print "1 2 3"}

Propriétés

Les propriétés sont très similaires aux attributs normaux des objets. Elles permettent d'attribuer des données à un objet mais avec une fonctionnalité supplémentaire : Elles déclenchent un événement lorsque la valeur de la propriété a changé..

start: function() { this.widget = ... this.widget.on("change:name", this, this.name_changed); this.widget.set("name", "Nicolas");},name_changed: function() { console.log("The new value of the property 'name' is", this.widget.get("name"));}

Widget.set(name, value) permet de modifier la valeur de la propriété. Si la valeur est changée (ou s'il n'y en avait pas précédemment), l'objet déclenchera un change:xxx où xxx est le nom de la propriété.

Widget.get(name) permet de récupérer la valeur de la propriété

Exercice

Propriétés et événements des Widgets

Créez une widget ColorInputWidget qui affichera 3 <input type="text">. Chacun de ces <input> est destiné àrecevoir un nombre hexadécimal de 00 à FF. Dès qu'un de ces <input> est modifié par l'utilisateur, le widget doit récupérer le contenu des trois champs, concaténer leur valeur pour obtenir un code couleur CSS complet (ex : #00FF00) et inscrire le résultat dans une propriété nommée 'color'. Notez que l'événement Jquery 'change()' que vous pouvez lier à n'importe quel élément HTML <input> et la méthode 'val()' qui peutrécupérer la valeur courante d'un <input> peuvent vous être utiles pour cet exercice.

Modifiez alors le widget HomePage pour instancier le widget ColorInputWidget et l'afficher. Le widget HomePage devrait aussi afficher un rectangle vide. Ce rectangle doit toujours avoir la même couleur de fondque la couleur définie dans la propriété 'color' de l'instance de ColorInputWidget.

Utilisez QWeb pour générer votre 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();

43/67

Page 44: OpenERP Formation Web

OpenERP V7.0 04/08/14

}); self.input_changed(); }, input_changed: function() { 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); 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;}

44/67

Page 45: OpenERP Formation Web

OpenERP V7.0 04/08/14

Note

La méthode JQuery css() permet de d'attribuer une propriété css.

Assistants de WidgetsVous avez vu les bases de la classe Widget, QWeb et le système d'événements / propriétés. Cette classe propose encore d'autres méthodes utiles.

Widget's jQuery Selector

Il est très fréquent d'avoir besoin de sélectionner un élément précis à l'intérieur d'un widget. Dans la partie précédente de ce guide, nous avons vu plusieurs utilisations de la méthode find() des objets jQuery:

this.$el.find("input.my_input")...

Widget fournit une syntaxe plus courte qui fait la même chose avec la méthodes $ ():

instance.oepetstore.MyWidget = instance.web.Widget.extend({ start: function() { this.$("input.my_input")... },});

Note

Nous vous déconseillons vivement d'utiliser directement la fonction globale Jquery $() comme nous l'avons fait dans le chapitre précédent où nous avons expliqué la bibliothèque Jquery et les sélecteurs Jquery. Ce type de sélection globale est suffisante pour des applications simples, mais n'est pas une bonne idée pour devéritables applications web. La raison en est simple : lorsque vous créez un nouveau type de widget vous ne savez jamais combien de fois il sera instancié.

Du fait que la fonction globale $() opère sur tout le HTML affiché dans le navigateur, si vous instanciez un widget 2 fois et utilisez cette fonction, vous aurez du mal à sélectionner le contenu de l'autre instance de votre widget. C'est pourquoi vous devez restreindre les sélections jQuery au HTML situé dans votre widget laplupart du temps.

Dans la même logique, vous pouvez également deviner que c'est une très mauvaise idée d'essayer d'utiliser des identifiants HTML dans vos widgets. Si le widget est instancié deux fois, vous aurez 2 éléments HTML différent avec le même ID. Ceci est une erreur en soi. Vous devriez vous en tenir à des classes CSS pour marquer vos éléments HTML dans tous les cas.

Connexion plus facile avec les événements du DOM

Dans le précédent chapitre, nous avons dû lier plusieurs événements d'éléments HTML comme click() ou change(). Maintenant que nous avons la méthode $() pour simplifier un peu le code, voyons ce que cela donne :

instance.oepetstore.MyWidget = instance.web.Widget.extend({ start: function() { var self = this; this.$(".my_button").click(function() { self.button_clicked(); });

45/67

Page 46: OpenERP Formation Web

OpenERP V7.0 04/08/14

}, button_clicked: function() { .. },});

C'est toujours un peu long à taper. C'est pourquoi il y a une syntaxe encore plus simple :I

instance.oepetstore.MyWidget = instance.web.Widget.extend({ events: { "click .my_button": "button_clicked", }, button_clicked: function() { .. }});

Attention :

Il est important de faire la différence entre les événements Jquery qui sont déclenchés par des éléments du DOM et les événements des widgets. L'attribut de classe 'event' est un assistant qui fait le lien avec les événements Jquery, il n'a rien à voir avec les événements de widgets qui peuvent être liés en utilisant la méthode on().

L'attribut de classe 'event' est un dictionnaire qui permet de définir des événements jQuery avec une syntaxeplus courte.

La clé est une chaîne avec deux parties différentes séparées par un espace. La première partie est le nom de l'événement, le second est le sélecteur jQuery. Donc, la clé clic. my_button va lier l'événement « click » sur leséléments correspondant au sélecteur my_button.

La valeur est une chaîne contenant le nom de la méthode à appeler sur l'objet courant.

Recommandations pour le développement

Comme expliqué dans les pré-requis à la lecture de ce guide, vous devriez déjà connaître CSS et HTML. Mais développer des applications web en Javascript ou développer des modules web pour OpenERP exige d'être plus strict que s'il s'agissait de créer des des pages web statiques et des feuilles de styles CSS. Vous devriez suivre ces recommandations si vous voulez avoir des projets maintenables et éviter les bugs et les erreurs courantes.

Les identifiants (attribut id) devraient être évités. Dans les applications génériques et les modules, les id limitent la réutilisation des composants et tendent à rendre le code fragile. Dans presque tous les cas, ils peuvent être remplacés par : rien, des classes ou en gardant une référence à un nœud DOM ou un élément jQuery proche.

Note

S'il est absolument nécessaire d'avoir un id. (parce qu'une bibliothèque tierce partie en nécessite un et ne peut pas prendre un élément DOM), il doit être généré avec _.uniqueId ().

Évitez les noms de classes trop communs dans les CSS. Des noms comme « content » ou « navigation » peuvent correspondre à la signification sémantique/souhaités, mais il est probable qu'un autre développeur

46/67

Page 47: OpenERP Formation Web

OpenERP V7.0 04/08/14

aura le même besoin, créant un conflit de noms et un comportement inattendu. Les noms de classes génériques doivent par exemple être précédés par le nom de l'élément auquel elles appartiennent (création d'espaces de noms «informelles», un peu comme en C ou Objective-C).

Évitez les sélecteurs globaux.

Du fait qu'un composant peut être utilisé plusieurs fois dans une seule page (par exemple dans OpenERP : le tableaux de bord), les requêtes doivent être limitées à la portée d'un composant donné. Les sélections non filtrées telles que $(selector) ou document.querySelectorAll(selector) mèneront généralement à un comportement inattendu ou incorrect. Le Widget Web d'OpenERP possède un attribut fournissant sa racine DOM (Widget.$el), et un raccourci pour sélectionner des nœuds directement (Widget.$()).

Plus généralement, ne supposez jamais que vos composants possèdent ou contrôlent quelque chose au-delàde leur propre el$.

Sauf situation triviale, utilisez Qweb pour afficher ou utiliser des templates sur votre HTML

Tous les composants interactifs (composants affichant des informations à l'écran ou interceptant les événements DOM) doivent hériter de Widget et correctement mettre en œuvre et utiliser son API et son cycle de vie.

Modifier des Widgets et des classes existantsLe système de classes du framework web d'OpenERP permet des modifications directes des classes existantes en utilisant la méthode include() de la classe.

var TestClass = instance.web.Class.extend({ testMethod: function() { return "hello"; },});

TestClass.include({ testMethod: function() { return this._super() + " world"; },});

console.log(new TestClass().testMethod());// will print "hello world"

Ce système est similaire au mécanisme d'héritage, sauf qu'il modifiera directement la classe. Vous pouvez utiliser this._super() pour appeler l'implémentation originale des méthodes que vous êtes en train de redéfinir. Si la classe avait déjà une sous-classes, tous les appels à this._super() dans les sous-classes appelleront les nouvelles implémentations définies dans l'appel à include(). Cela fonctionnera également si certaines instances de la classe (ou de l'une de ses sous-classes) ont été créés avant l'appel à include().

Avertissement

Notez que, même si include() peut être un outil puissant, il n'est pas considéré comme une très bonne pratique de programmation, car il peut facilement créer des problèmes s'il est utilisé de la mauvaise manière. Donc, vous devriez l'utiliser pour modifier le comportement d'un composant existant seulement quand il n'y a pas d'autres options. Essayez de limiter son usage au strict minimum.

47/67

Page 48: OpenERP Formation Web

OpenERP V7.0 04/08/14

TraductionsLe processus de traduction de texte en Python et Javascript sont très similaires. Vous avez peut-être remarqué ces lignes au début du fichier petstore.js :

var _t = instance.web._t, _lt = instance.web._lt;

Ces lignes permettent d'importer les fonctions de traduction dans le module Javascript en cours. Leur usage correct est le suivant :

this.$el.text(_t("Hello dear user!"));

Dans OpenERP, les fichiers de traduction sont automatiquement générés en scannant le code source. Tout fragment de code appelant une certaine fonction sont détectés et leur contenu est ajouté à un fichier de traduction qui sera alors envoyé aux traducteurs. En Python, la fonction est _ , En Javascript, la fontion est _t() (et aussi _lt()).

Si le fichier source n'a jamais été scanné et les fichiers de traduction ne contiennent aucune traduction pour le texte passé à _t(), ce texte sera retourné tel quel. S'il existe une traduction, elle sera renvoyée par la fonction.

_lt() fait pratiquement la même chose en légèrement plus compliqué. Elle ne retourne pas un texte mais unefonction qui renverra du texte. On l'utilise dans des cas spécifiques.

var text_func = _lt("Hello dear user!");this.$el.text(text_func());

Pour avoir plus d'information sur les traductions dans OpenERP, consultez la documentation de référence :

https://doc.openerp.com/contribute/translations

Communication avec le serveur OpenERPMaintenant, vous devriez savoir tout ce dont vous avez besoin pour afficher tout type d'interface utiilisateur graphique avec vos modules OpenERP. Néamoins, OpenERP est un application centrée sur une base de données. Il vous faut maintenant interroger cette base pour obtenir vos données.

Pour mémoire, dans OpenERP vous n'êtes pas supposé requêter directement dans la base SQL. Vous utiliserez toujours la bibliothèque jointe à l'ORM. (Object-Relational Mapping) et plus précisemment les modèles OpenERP.

Contacter les modèles

Dans le précédent chapitre, nous avons expliqué comment envoyer des requêtes HTTP au serveur web en utilisant la méthode $.ajax() et le format JSON. Il est très utile de savoir faire communiquer une application Javascript avec le serveur web en utilisant ces outils, mais c'est toujours un peu « bas niveau » pour être utilisé dans des applications complexes.

Lorsque le client web contacte le serveur OpenERP, il doit transmettre des données supplémentaires comme les données d'authentification de l'utilisateur courant. Il y a encore d'autres complexités dues aux modèles OpenERP qui nécessitent d'utiliser un protocole de communication de plus haut niveau.

48/67

Page 49: OpenERP Formation Web

OpenERP V7.0 04/08/14

Voilà pourquoi vous n'utiliserez pas $.ajax() directement pour communiquer avec le serveur. Le framework du client web fournit des classes pour représenter ce protocole.

Pour mettre cela en évidence, le fichier petstore.py contient déjà un petit modèle avec une méthode exemple :

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

Si vous connaissez les modèles OpenERP, ce code devrait vous être familier. Ce modèle déclare une table nommée message_of_the_day avec deux champs. Il a aussi une méthode my_method() qui renvoie un dictionnaire.

Voici un widget qui appelle my_method() et affiche le résultat :

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(result) { self.$el.append("<div>Hello " + result["hello"] + "</div>"); // will show "Hello world" to the user }); },});

La classe utilisée pour contacter les modèles OpenERP est instance. Lorsque vous l'instanciez, vous devez donner en premier argument à son constructeur le nom du modèle que vous souhaitez contacter dans OpenERP (ici, message_of_the_day, le modèle créé pour cet exemple. Ce pourrait être res.partner)

call(name, args, kwargs) est la méthode du modèle utilisée pour appeler n'importe quelle méthode d'un modèle OpenERP coté serveur. Voici ses arguments :

• name est le nom de la méthode à appeler sur le modèle. Ici, la méthode my_method

• args est une liste d'arguments positionnels à envoyer à la méthode. La méthode my_method() ne

contient aucun argument particulier, voici donc un autre exemple :

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 est une liste d'arguments nommés à passer à la méthode. Dans l'exemple, nous avons un

49/67

Page 50: OpenERP Formation Web

OpenERP V7.0 04/08/14

argument nommé un peu spécial : context. On lui passe une valeur qui peut paraître étrange pour l'instant : new instance.web.CompoundContex(). Le sens de cet argument sera expliqué plus tard. Pour l'instant, sachez que l'argument kwargs vous permet de passer des arguments à la méthode Python par nom plutôt que par position. Exemple :

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

Si vous examinez la déclaration de la méthode my_method() en Python, vous pouvez voir qu'elle a deux arguments cr, et uid

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

Vous avez peut-être noté que vous ne passez pas ces arguments au serveur lorsque vous appelez cette méthode depuis Javascript. C'est parce que ces arguments qui doivent être déclarés dans toutes les méthodes de modèles ne sont jamais envoyées depuis le client OpenERP. Ces arguments sont ajoutés implicitement par le serveur OpenERP. Le premier est un objet appelé curseur et permettant la communication avec la base de données. Le second est l'identifiant de l'utilisateur actuellement connecté.

call() renvoie un différé résolu avec la valeur retournée par la méthode du modèle comme premier argument. Si vous ne savez pas ce que sont les différés, revenez au chapitre précédent (la partie concernant les requêtes HTTP en Jquery).

CompoundContext

Dans le paragraphe précédent, nous avons évité d'expliquer l'étrange argument de contexte dans l'appel à notre méthode de modèle :

model.call("my_method", [], {context: new instance.web.CompoundContext()})

Dans OpenERP, les méthodes des modèles devraient toujours avoir un argument nommé 'context' :

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

Le contexte est une sorte d'argument « magique » que le client web envoie toujours au serveur lorsqu'il appelle une méthode. C'est un dictionnaire contenant diverses clés. Une des plus importantes est la langue de l'utilisateur, utilisée par le serveur pour traduire tous les messages de l'application. Une autre est le fuseau horaire de l'utilisateur, utilisé pour calculer correctement les dates et heures si OpenERP est utilisé par des utilisateurs dans différents pays.

L'argument est nécessaire dans toutes les méthodes, car si nous l'omettons, des bizarres peuvent se produire (une interface mal traduite par exemple). Pour passer cet argument, nous utilisons la classe instance.web.CompoundContext.

50/67

Page 51: OpenERP Formation Web

OpenERP V7.0 04/08/14

CompoundContext est une classe utilisée pour passer le contexte utilisateur au serveur ainsi que pour ajouter de nouvelles clés au contexte (certaines méthodes de modèles ajoutent des clés arbitraires au contexte). On le crée en donnant à son constructeur un certain nombre de dictionnaires ou d'autres instances de CompoundContext. Tous ces contextes seront fusionnés avant d'être envoyés au serveur.

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.

En résumé, vous devriez toujours ajouter une instance de instance.web.CompoundContext dans tous les appels aux méthodes de modèles.

Requêtes

Si vous connaissez le développement de modules OpenERP, vous devriez déjà savoir tout ce qui est nécessaire pour communiquer avec les modèles et leur faire faire ce que vous voulez. Mais il y a encore un petit assistant qui pourrait vous être utile : query().

query() est un raccourci pour la combinaison habituelle « search », « read » des modèles OpenERP. Il permet de rechercher et de lire des valeurs dans les enregistrements avec une syntaxe plus courte. Exemple :

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() prend en argument une liste de champs à récupérer dans le modèle. Il renvoie une instance de la classe instance.web.

Query est une classe représentant la requête que vous essayez de construire avant de l'envoyer au serveur. Elle a de multiples méthodes que vous pouvez appeler pour modifier la requête. Toutes ces méthodes renvoient l'instance courante de Query. :

• filter permet de préciser un domaine OpenERP. Pour mémoire, un domaine dans OpenERP est une

liste de conditions et chaque condition est elle-même une liste.

• Limits limite le nombre de résultats renvoyés

Lorsque vous avez précisé votre requête, vous pouvez appeler la méthode all() qui envoie la requête au serveur et retourne un différé résolu avec le résultat. Le résultat est le même que celui renvoyé par la méthode read(). (une liste de dictionnaires contenant les champs demandés).

51/67

Page 52: OpenERP Formation Web

OpenERP V7.0 04/08/14

Pour avoir plus d'informations sur l'assistant query(), reportez-vous à la documentation :

https://doc.openerp.com/trunk/web/rpc

ExercicesExercice - Message du jour

Créez un widget MessageOfTheDay qui affichera le message contenu dans le dernier enregistrement de message_of_the_day. Le widget devrait le message dès qu'il est inséré dans le DOM et afficher le message à l'utilisateur. Affichez ce widget dans la page d'accueil du module Pet Store.

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(result) { 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">

52/67

Page 53: OpenERP Formation Web

OpenERP V7.0 04/08/14

<p class="oe_mywidget_message_of_the_day"></p> </div> </t></templates>

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

Exercice – Liste d'animaux de compagnie

Créez un widget PetToysList qui affichera 5 jouets sur la page d'accueil avec leur nom et leur image.

Dans ce module OpenERP, les animaux ne sont pas stockés dans une nouvelle table comme c'est le cas pour le message du jour. Ils sont dans la table product.product. Si vous cliquez sur l'item de menu

Pet Store > Pet Store > Pet Toys vous pourrez les voir. Les jouets pour animaux sont identifiés par la catégorie nommée 'Jouets pour animaux'. Vous pouvez avoir besoin de vous documenter pour pouvoir sélectionner uniquement les produits dont la catégorie est 'jouets pour animaux'.

Pour afficher les images des jouets pour animaux, sachez que vous pouvez dans OpenERP récupérer une image comme vous récupérez la valeur d'un champ standard, mais vous obtiendrez une chaîne contenant un binaire encodé en Base62. Il y a une astuce pour afficher des images en Base64 au format HTML :

<img class="oe_kanban_image" src="data:image/png;base64,${replace this by base64}"></image>

Le widget PetToysList devrait être affiché sur la page d'accueil à droite du widget MessageOfTheDay. Vous aurez besoin d'utiliser CSS pour réaliser cet affichage.

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

53/67

Page 54: OpenERP Formation Web

OpenERP V7.0 04/08/14

this._super.apply(this, arguments); }, start: function() { var self = this; new instance.web.Model("message_of_the_day").query(["message"]).first().then(function(result) { self.$(".oe_mywidget_message_of_the_day").text(result.message); }); }, }); instance.oepetstore.PetToysList = instance.web.Widget.extend({ template: "PetToysList", 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>

54/67

Page 55: OpenERP Formation Web

OpenERP V7.0 04/08/14

.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; background-color: #F0EEEE;}.oe_petstore_pettoyslist { padding: 5px;}.oe_petstore_pettoy { margin: 5px; padding: 5px; border-radius: 3px; background-color: #F0EEEE;}

Les composants web OpenERPDans la partie précédente, nous avons expliqué le framework web OpenERP, un framework de développement pour créer et architecturer des applications graphiques Javascript. Cette partie est dédiée aux composants existants du composant Web OpenERP Client et principalement ceux contenant des points d'entrée permettant aux développeurs de créer des widgets qui seront insérés dans les vues et les composants existants.

Action ManagerPour afficher une vue ou montrer un pop-up, par exemple lorsque vous cliquez sur un item de menu, OpenERP utilise le concept d'action. Les actions sont des fragments de code expliquant le travail que doit faire le client Web. Elles peuvent être chargées depuis la base ou créées à la volée. Le composant gérant les actions dans le client Web est l' »Action Manager. »

Utiliser l'ActionManager

Une manière de lancer une action est d’utiliser un élément de menu visant une action enregistrée dans la base de données. Pour mémoire, voici comment est définie une action typique et son item de menu associé :

<record model="ir.actions.act_window" id="message_of_the_day_action"> <field name="name">Message of the day</field> <field name="res_model">message_of_the_day</field> <field name="view_type">form</field> <field name="view_mode">tree,form</field></record>

55/67

Page 56: OpenERP Formation Web

OpenERP V7.0 04/08/14

<menuitem id="message_day" name="Message of the day" parent="petstore_menu" action="message_of_the_day_action"/>

Il est aussi possible de demander au client OpenERP de charger une action depuis le code Javascript. Pour cela, vous devez créer un dictionnaire décrivant l'action puis demander à l »action manager » de rediriger le client web vers la nouvelle action.

Pour envoyer un message au gestionnaire d'action, la classe Widget a un raccourci qui trouvera automatiquement le gestionnaire d'action courant et exécutera l'action. Voici un exemple d'appel à cette méthode :

instance.web.TestWidget = instance.web.Widget.extend({ dispatch_to_new_action: function() { this.do_action({ type: 'ir.actions.act_window', res_model: "product.product", res_id: 1, views: [[false, 'form']], target: 'current', context: {}, }); },});

La méthode utilisée pour demander à l'action manager d'exécuter une nouvelle action est do_action(). Elle reçoit comme argument un dictionnaire définissant les propriétés de l'action. Voici une description des propriétés les plus usuelles (toutes ne peuvent pas être utilisées simultanément dans tous les types d'actions) :

• type: Le type de l'action, c'est à dire le nom du modèle dans lequel l'action est enregistrée. Par

exemple, utilisez ir.actions.act_window pour afficher des vues et ir.actions.client pour les actions client.

• res_model: Pour les actions act_window, c'est le modèle utilisé pour les vues.

• res_id: l'ID de l'enregistrement à afficher.

• views: Pour les actions act_window, c'est une liste des vues à afficher. Cet argument doit être une

liste de tuples avec deux éléments. Le premier doit être l'identifiant de la vue (ou false si vous voulez seulement utiliser la vue par défaut définie dans le modèle). Le second doit être le type de la vue.

• target: Si la valeur est 'current', l'action sera ouverte dans la partie de contenu principale du client

web. L'action courante sera détruite avant le chargement de la nouvelle. Si la valeur est 'new', l'action apparaîtra dans un popup et l'action courante ne sera pas détruite.

• context: Le contexte à utiliser.

Exercice – Afficher un produit

Modifiez le composant PetToysList développé dans la précédente partie pour vous rendre dans une vue formulaire affichant l'item concerné lorsque nous cliquons dessus dans la liste.

Solution:

56/67

Page 57: OpenERP Formation Web

OpenERP V7.0 04/08/14

instance.oepetstore.PetToysList = instance.web.Widget.extend({ template: "PetToysList", 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); $item.click(function() { self.item_clicked(item); }); }); }); }, item_clicked: function(item) { this.do_action({ type: 'ir.actions.act_window', res_model: "product.product", res_id: item.id, views: [[false, 'form']], target: 'current', context: {}, }); },});

Actions ClientDans le module installé durant la précédente partie de ce guide, nous avons défini un widget simple qui s'affiche lorsque nous cliquons sur un élément du menu. Ceci parce que le widget a été enregistré comme action client. Les actions client sont un type d'actions entièrement définies dans le code Javascript. Voici un rappel de la manière dont a été définie l'action client :

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

instance.web.client_actions est une instance de la classe Registry. Les « Registries » ne sont pas très différents des dictionnaires sauf qu'ils attribuent des chaînes aux noms des classes. Ajouter la clé 'petstore.homepage' à ce registre dit simplement au client web « Si quelqu'un te demande d'ouvrir une action client avec la clé petstore.homepage, instancie la classe de l'instance oepetstore.HomePage et affiche la à l'utilisateur ».

Voici comment l'élément de menu permettant d'afficher cette action client a été défini. :

<record id="action_home_page" model="ir.actions.client"> <field name="tag">petstore.homepage</field></record><menuitem id="home_page_petstore_menu" name="Home Page" parent="petstore_menu"

57/67

Page 58: OpenERP Formation Web

OpenERP V7.0 04/08/14

action="action_home_page"/>

Les actions client n'ont pas besoin de beaucoup d'information sauf leur type, qui est stocké dans le champ 'tag'.

Lorsque le client web veut afficher une action client, il l'affiche simplement dans le bloc principal de contenu du client web. Ceci suffit amplement à permettre au widget d'afficher toutes sortes de choses et de créer des fonctionnalités nouvelles pour le client web.

Architecture des vuesLa majorité des complexités du client web réside dans les vues. Elles sont l'outil de base pour afficher les données de la base de données. Ce chapitre explique les vues et comment elles sont affichées dans le client Web.

Le gestionnaire de vues

Précédemment, nous avons expliqué l'objet de l'Action Manager. C'est un composant dont la classe est un ActionManager, qui gère les actions OpenERP (notamment les actions associées aux boutons de menu).

Lorsque une instance d'ActionManager reçoit une action de type ir.actions.act_window, elle sait qu'elle doit afficher une ou plusieurs vues associées à un modèle précis. Pour cela, elle crée un 'view manager qui créera une ou plusieurs vues. Regardez ce diagramme :

L'instance de ViewManager instanciera chaque classe de vue correspondant aux vues indiquées dans l'action ir.actions.act_window action. Par exemple, la classe correspondant au type de vue 'form' est FormView. Chaque vue hérite de la classe abstraite View.

Les vues

Toutes les vues standard d'OpenERP (celles que vous pouvez choisir en utilisant les petits boutons sous le champ de saisie de recherche) sont représentées par une classe dérivée de la classe abstraite View. Notez que la vue de recherche (le champ de saisie de critères de recherche à la droite de l'écran qui figure notamment dans les vues liste et kanban) est aussi considérée comme un type de vue même si elle ne

58/67

Page 59: OpenERP Formation Web

OpenERP V7.0 04/08/14

fonctionne pas comme les autres (vous ne pouvez pas « opter pour » la vue de recherche et elle n'occupe pastout l'écran).

Une vue a pour tâche de charger la description de vue XML depuis le serveur et de l'afficher. Les vues reçoivent une instance de la classe DataSet. Cette classe contient une liste d'identifiants correspondant aux enregistrements que la vue devrait afficher. Elle est remplie par la vue de recherche et la vue courante est censée afficher le résultat de la recherche après qu'elle ait été effectuée par la vue de recherche .

Les champs de la vue formulaireUn besoin classique dans le client web et d'étendre la vue formulaire pour afficher des widgets plus spécifiques. Une des possibilités de le faire est de définir un nouveau type de champ.

Un champ, dans la vue formulaire, est un type de widget conçu pour modifier le contenu d'un (et un seulement) champ dans un seul enregistrement affiché par la vue formulaire. Tous les types de données disponibles dans les modèles ont une implémentation par défaut pour les afficher et les modifier dans la vueformulaire. Par exemple, la classe FieldChar permet de saisir des données de type caractère.

D'autres classes de champs fournissent simplement un widget alternatif pour représenter un type de données existant. Un bon exemple est la classe FieldEmail. Il n'y a pas de type email dans les modèles d'OpenERP. Cette classe est conçue pour afficher un champ mail en supposant qu'il contient un email (il affichera un lien cliquable afin d'envoyer directement un mail à la personne mais aussi pour vérifier la validitéde l'adresse e-mail).

Notez également que rien n'interdit à une classe de champ de travailler avec plus d'un type de données. A titre d'exemple, la classe FieldSelection fonctionne avec les deux types de champs 'selection' et 'many2one'.

Rappel : pour indiquer un type de champ précis dans une forme vue XML, il vous suffit de spécifier l'attribut 'widget':

<field name="contact_mail" widget="email"/>

Il est également bon de noter que les classes de champ de vue formulaire sont également utilisés dans les affichages de listes modifiables. Donc, en définissant une nouvelle classe de champ, on rend ce nouveau widget disponible dans les deux vues.

Un autre type de mécanisme d'extension de la vue formulaire est le widget 'Form', qui a moins de restrictions que les champs (même s'il peut être plus compliqué à implémenter). Les widgets 'Form' seront expliqués plus loin dans ce guide.

Les champs sont instanciés par la vue formulaire après avoir lu sa description XML et construit le code HTML correspondant. Après cela, la vue formulaire va communiquer avec les objets des champs à l'aide de certaines méthodes. Ces méthodes sont définies dans l'interface FieldInterface. Presque tous les champs héritent de la classe abstraite AbstractField. Cette classe définit des mécanismes par défaut qui doivent être mis en œuvre par la plupart des champs.

Voici quelques-unes des caractéristiques d'une classe de champ:

• La classe de champ doit afficher et permettre à l'utilisateur de modifier la valeur du champ.

• Elle doit correctement implémenter les 3 attributs disponibles dans tous les champs OpenERP. La

classe AbstractField implémente un algorithme qui calcule dynamiquement la valeur de ces attributs(ils peuvent changer à tout moment car leurs valeurs changent en fonction de la valeur d'autres

59/67

Page 60: OpenERP Formation Web

OpenERP V7.0 04/08/14

champs). Leurs valeurs sont stockées dans les propriétés du widget (expliquées plus haut dans ce guide). Il est de la responsabilité de chaque classe de champ de vérifier ces propriétés du widget et de s'adapter dynamiquement en fonction de leurs valeurs. Voici une description de chacun de ces attributs:

• required: Le domaine doit avoir une valeur avant enregistrement. Si requred est vrai et que

le champ n'a pas de valeur, la méthode is_valid() du champ doit renvoyer false.

• invisible: Si vrai, le champ doit être invisible. La classe AbstractField a déjà une

implémentation de base de ce comportement qui s'adapte à la plupart des champs.

• Readonly : Si vrai, le champ ne doit pas être modifiable par l'utilisateur. La plupart des

champs dans OpenERP ont un comportement complètement différent selon la valeur de 'readonly'. A titre d'exemple, FieldChar affiche un HTML <input> lorsqu'il est modifiable et affiche le texte quand il est en lecture seule. Cela signifie beaucoup plus de code que ce qui suffirait pour implémenter un seul comportement, mais c'est nécessaire pour assurer une bonne expérience utilisateur.

• Les champs ont deux méthodes, set_value() et get_value(), qui sont appelées par la vue formulaire

pour lui donner la valeur à afficher et retenir la nouvelle valeur saisie par l'utilisateur. Ces méthodes doivent capables de traiter la valeur envoyée par le serveur OpenERP lorsque read() est exécutée surun modèle et renvoyer une valeur valide à write(). Rappelez-vous que les données JavaScript/Pythonutilisés pour représenter les valeurs données par read() et enroyées à write() ne sont pas nécessairement les même dans OpenERP. Par exemple, lorsque vous lisez un many2one, c'est toujour un tuple dont la première valeur est l'ID de l'enregistrement pointé et la seconde est le résutat de name_get (ex : 15, « Agrolait »)). Mais lorsque vous écrivez un many2one, ce doit être un entier, plus un tuple. AbstracField a une implémentation par défaut de ces méthodes qui fonctionne bien pour les types de données simples et configure un widget avec des valeurs correctement nommées.

Pour mieux comprendre comment implémenter les champs, nous vous encourageons à regarder la définition de l'interface FieldInterface et la classe AbstractField directement dans le code du client web d'OpenERP.

Créer un nouveau type de champ

Dans ce paragraphe, nous verrons comment créer un nouveau type de champ. L'exemple ici sera de ré-implémenter la classe FieldChar en expliquant chaque étape.

Champ simple en lecture seule

Voici une première implémentation qui affichera un texte. L'utilisateur ne pourra pas modifier le contenu du champ.

instance.oepetstore.FieldChar2 = instance.web.form.AbstractField.extend({ init: function() { this._super.apply(this, arguments); this.set("value", ""); }, render_value: function() { this.$el.text(this.get("value")); },

60/67

Page 61: OpenERP Formation Web

OpenERP V7.0 04/08/14

});instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');

Dans cet exemple, nous déclarons une classe nommée FieldChar2 héritant de AbstractField. Nous enregistrons aussi cette classe dans instance.web.form.widgets sous la clé char2. Cela nous permettra d'utiliser ce nouveau champ dans une vue formulaire en spécifiant le widget = "char2" dans la balise <field/> dans la déclaration XML de la vue.

Dans cet exemple, nous définissons une méthode unique: render_value(). Tout ce qu'ell fait est d'afficher la valeur de la propriété widget. Ce sont deux outils définis par la classe AbstractField. Comme expliqué précédemment, la vue formulaire va appeler la méthode set_value() du champ pour définir la valeur à afficher. Cette méthode a déjà une implémentation par défaut dans AbstractField qui fixe simplement la valeur de la propriété widget. AbstractField écoute aussi l'événement change:value sur lui-même et appelle render_value() quand ça se produit. Ainsi, render_value() est une méthode pratique à implémenter dans les classes filles pour effectuer des opérations à chaque changement de valeur dans le champ .

Dans la méthode init(), nous définissons la valeur par défaut du champ si aucune n'est spécifiée par la vue formulaire (ici nous supposons que la valeur par défaut d'un champ char doit être une chaîne vide).

Champ en lecture-écriture

Les champs qui affichent leur contenu mais ne donnent pas la possibilité à l'utilisateur de les modifier peuvent être utiles, mais la plupart des champs dans OpenERP permettent l'édition. Cela rend les classes de champs plus compliquées, surtout parce que les champs sont censés gérer à la fois le mode modifiable et non modifiable. Ces modes sont souvent complètement différent (pour la conception et l'usabilité) et les champs doivent être en mesure de passer d'un mode à l'autre à n'importe quel moment.

Pour en savoir dans quel mode le champ courrant devrait être, la classe AbstractField définit une propriété de widget nommée effective_readonly. Le champ doit observer les changements dans le propriété de ce widget et afficher le mode correct en conséquence. exemple:

instance.oepetstore.FieldChar2 = instance.web.form.AbstractField.extend({ init: function() { this._super.apply(this, arguments); this.set("value", ""); }, start: function() { this.on("change:effective_readonly", this, function() { this.display_field(); this.render_value(); }); this.display_field(); return this._super(); }, display_field: function() { var self = this; this.$el.html(QWeb.render("FieldChar2", {widget: this})); if (! this.get("effective_readonly")) { this.$("input").change(function() { self.internal_set_value(self.$("input").val()); }); } }, render_value: function() {

61/67

Page 62: OpenERP Formation Web

OpenERP V7.0 04/08/14

if (this.get("effective_readonly")) { this.$el.text(this.get("value")); } else { this.$("input").val(this.get("value")); } },});instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');

<t t-name="FieldChar2"> <div class="oe_field_char2"> <t t-if="! widget.get('effective_readonly')"> <input type="text"></input> </t> </div></t>

we bind on the event change:effective_readonly.

Dans la méthode start () (qui est appelée juste après qu'un widget a été ajouté au DOM), nous lions sur l'événement change : effective_readonly. Cela permettra de réafficher le champ à chaque changement de la propriété effective_readonly. Ce gestionnaire d'événements appellera display_field(), qui est aussi appelée directement dans start(). Ce Display_field() a été créé spécifiquement pour ce champ, ce n'est pas une méthode définie dans AbstractField ou dans une autre classe. C'est la méthode que nous utiliserons pour afficher le contenu du champ qui selon que nous sommes en lecture seule ou pas.

Ensuite, la conception de ce champ est assez standard, sauf qu'il y a un grand nombre de vérifications pour connaître l'état de la propriété effective_readonly:

• Le modèle Qweb utilisé pour afficher le contenu du widget affiche un <input type="text" /> si nous

sommes en mode lecture-écriture et rien en particulier en mode lecture seule.

• Dans la méthode display_field (), nous devons lier les modifications de <input type="text"/> à

l'événement 'change' pour savoir si l'utilisateur a modifié la valeur. Quand ceci arrive, nous appelons la méthode internal_set_value() avec la nouvelle valeur du champ. C'est une méthode utilitaire de la classe AbstractField. Cette méthode va définir une nouvelle valeur dans la propriété de la valeur, sans déclencher d'appel à render_value() (ce qui n'est pas nécessaire puisque <input type="text"/> contient déjà la valeur correcte).

• Dans render_value(), nous utilisons un code complètement différent pour afficher la valeur du

champ en fonction de si nous sommes en lecture seule ou en lecture-écriture.

Exercice – Créer un champ Couleur

Créer une classe FieldColor. La valeur de ce champ devrait être une chaîne contenant un code couleur comme ceux utilisés dans les CSS (exemple: #FF0000 pour le rouge). En mode de lecture seule, ce champ doit afficher un petit bloc dont la couleur correspond à la valeur du champ. En mode lecture-écriture, vous devez afficher <input type="color">. Ce type de <input /> est un élément HTML5 qui ne fonctionne pas dans tous les navigateurs mais, fonctionne bien dans Google Chrome. C'est suffisant pour l'utiliser dans un exercice.

Vous pouvez utiliser ce widget dans la vue formulaire du modèle message_of_the_day qui a un champ

62/67

Page 63: OpenERP Formation Web

OpenERP V7.0 04/08/14

'color'. En prime, vous pouvez modifier le widget MessageOfTheDay créé précédemment pour afficher le message du jour avec la couleur de fond indiquée dans le champ de 'color'.

Solution:

instance.oepetstore.FieldColor = instance.web.form.AbstractField.extend({ init: function() { this._super.apply(this, arguments); this.set("value", ""); }, start: function() { this.on("change:effective_readonly", this, function() { this.display_field(); this.render_value(); }); this.display_field(); return this._super(); }, display_field: function() { var self = this; this.$el.html(QWeb.render("FieldColor", {widget: this})); if (! this.get("effective_readonly")) { this.$("input").change(function() { self.internal_set_value(self.$("input").val()); }); } }, render_value: function() { if (this.get("effective_readonly")) { this.$(".oe_field_color_content").css("background-color", this.get("value") || "#FFFFFF"); } else { this.$("input").val(this.get("value") || "#FFFFFF"); } },});instance.web.form.widgets.add('color', 'instance.oepetstore.FieldColor');

<t t-name="FieldColor"> <div class="oe_field_color"> <t t-if="widget.get('effective_readonly')"> <div class="oe_field_color_content" /> </t> <t t-if="! widget.get('effective_readonly')"> <input type="color"></input> </t> </div></t>

.oe_field_color_content { height: 20px; width: 50px; border: 1px solid black;

63/67

Page 64: OpenERP Formation Web

OpenERP V7.0 04/08/14

}

Les widgets personnalisés de la vue formulaireLes champs de formulaires peuvent être utiles, mais leur but est la modification d'un seul champ. Pour agir sur la vue formulaire entière et avoir plus de liberté d'intégrer de nouveaux widgets, il est possible de créer des widgets .

Ce sont des widgets qui peuvent être ajoutés dans n'importe quelle vue formulaire en utilisant une certaine syntaxe dans la définition XML de la vue. Exemple :

<widget type="xxx" />

Ces widgets seront simplement créés par la vue formulaire pendant la création du HTML selon la définition XML. Ils ont des propriétés en commun avec les champs (comme la propriété effective_readonly) mais ne sont pas assignés à un champ particulier. Et donc, ils n'ont pas de méthodes comme get_value() ou set_value(). Ils doivent hériter de la classe abstraite FormWidget.

Les widgets de formulaire personnalisés peuvent aussi interagir avec les champs de la vue formulaire en récupérant ou en modifiant leur valeur en utilisant l'attribut field_manager de FormWidget. Voici une exemple d'utilisation :

instance.oepetstore.WidgetMultiplication = instance.web.form.FormWidget.extend({ start: function() { this._super(); this.field_manager.on("field_changed:integer_a", this, this.display_result); this.field_manager.on("field_changed:integer_b", this, this.display_result); this.display_result(); }, display_result: function() { var result = this.field_manager.get_field_value("integer_a") * this.field_manager.get_field_value("integer_b"); this.$el.text("a*b = " + result); }});instance.web.form.custom_widgets.add('multiplication', 'instance.oepetstore.WidgetMultiplication');

Cet exemple de widget personnalisé est conçu pour prendre les valeurs de deux champs existants (ceux-cis doivent exister dans la vue formulaire) et imprimer le résultat de leur multiplication. Il est rafraîchi chaque fois que l'un des deux champs est modifié.

L'attribut field_manager est en fait l'instance FormView représentant la vue formulaire. Les méthodes que les widgets peuvent appeler sont documentées de le code du client web dans l'interface FieldManagerMixin.Les fonctionnalités les plus utiles sont :

• La méthode get_field_value() qui renvoie la valeur d'un champ.

• Quand la valeur d'un champ est modifiée, quelle que soit la raison, la vue formulaire déclenchera un

événement nommé field_changed:xxx avex xxx le nom du champ.

64/67

Page 65: OpenERP Formation Web

OpenERP V7.0 04/08/14

• Il est aussi possible de changer la valeur des champs en utilisant la méthode set_values(). Cette

méthode prend un dictionnaire comme premier et seul argument dont les clés sont les noms des champs à modifier et les valeurs les nouvelles valeurs.

Exercice – Afficher des coordonnées sur Google Map

Dans cet exercice, nous voulons ajouter deux nouveaux champs sur le modèle de product.product: provider_latitude et provider_longitude. Ils représenterait les coordonnées sur une carte. Nous aimerions également créer un widget capable d'afficher une carte affichant ces coordonnées.

Pour afficher cette carte, vous pouvez simplement utiliser le service Google Map en utilisant un code HTML similaire à celui ci :

<iframe width="400" height="300" src="https://maps.google.com/?ie=UTF8&amp;ll=XXX,YYY&amp;output=embed"></iframe>

Il suffit de remplacer XXX par la latitude et YYY par la longitude.

Vous devriez afficher ces deux nouveaux champs ainsi que le widget de la carte dans une nouvelle page de votre boîte à onglets affichée dans la vue formulaire du produit.

Solution:

instance.oepetstore.WidgetCoordinates = instance.web.form.FormWidget.extend({ start: function() { this._super(); this.field_manager.on("field_changed:provider_latitude", this, this.display_map); this.field_manager.on("field_changed:provider_longitude", this, this.display_map); this.display_map(); }, display_map: function() { this.$el.html(QWeb.render("WidgetCoordinates", { "latitude": this.field_manager.get_field_value("provider_latitude") || 0, "longitude": this.field_manager.get_field_value("provider_longitude") || 0, })); }});instance.web.form.custom_widgets.add('coordinates', 'instance.oepetstore.WidgetCoordinates');

<t t-name="WidgetCoordinates"> <iframe width="400" height="300" t-att-src="'https://maps.google.com/?ie=UTF8&amp;ll=' + latitude + ','+ longitude + '&amp;output=embed'"> </iframe></t>

Exercice – Récupérer les coordonnées courantes

65/67

Page 66: OpenERP Formation Web

OpenERP V7.0 04/08/14

Maintenant, nous aimerions afficher un bouton supplémentaire pour obtenir automatiquement les coordonnées de l'emplacement de l'utilisateur courant.

Pour obtenir les coordonnées de l'utilisateur, un moyen facile est d'utiliser la géolocalisation API de géolocalisation JavaScript. Consultez la documentation en ligne pour savoir comment l'utiliser.

Notez également que ce ne serait pas très logique de permettre à l'utilisateur de cliquer sur ce bouton lorsque la vue formulaire est en mode lecture seule. Donc, ce widget personnalisé doit gérer correctement la propriété effective_readonly comme n'importe quel champ. Une façon de faire serait de faire disparaître le bouton lorsque effective_readonly est vrai.

Solution:

instance.oepetstore.WidgetCoordinates = instance.web.form.FormWidget.extend({ start: function() { this._super(); this.field_manager.on("field_changed:provider_latitude", this, this.display_map); this.field_manager.on("field_changed:provider_longitude", this, this.display_map); this.on("change:effective_readonly", this, this.display_map); this.display_map(); }, display_map: function() { var self = this; this.$el.html(QWeb.render("WidgetCoordinates", { "latitude": this.field_manager.get_field_value("provider_latitude") || 0, "longitude": this.field_manager.get_field_value("provider_longitude") || 0, })); this.$("button").toggle(! this.get("effective_readonly")); this.$("button").click(function() { navigator.geolocation.getCurrentPosition(_.bind(self.received_position, self)); }); }, received_position: function(obj) { var la = obj.coords.latitude; var lo = obj.coords.longitude; this.field_manager.set_values({ "provider_latitude": la, "provider_longitude": lo, }); },});instance.web.form.custom_widgets.add('coordinates', 'instance.oepetstore.WidgetCoordinates');

<t t-name="WidgetCoordinates"> <iframe width="400" height="300" t-att-src="'https://maps.google.com/?ie=UTF8&amp;ll=' + latitude + ','+ longitude + '&amp;output=embed'"> </iframe>

66/67

Page 67: OpenERP Formation Web

OpenERP V7.0 04/08/14

<button>Get My Current Coordinate</button></t>

67/67