OpenERP Formation Web

5 049 vues

Publié le

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

Publié dans : Logiciels
  • Soyez le premier à commenter

OpenERP Formation Web

  1. 1. 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
  2. 2. OpenERP V7.0 04/08/14 La méthode $.ajax().............................................................................................................................................23 Promesses et différés..........................................................................................................................................24 Combiner des différés.........................................................................................................................................25 Différé multiplexé................................................................................................................................................25 Différés chaînés.................................................................................................................................................... 26 Meilleures 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............................................................................................................................................... 32 Votre premier widget.......................................................................................................................................... 32 Affichage du contenu..........................................................................................................................................32 Widgets parents et enfants................................................................................................................................34 Supprimer des widgets........................................................................................................................................35 Le moteur de template QWeb.................................................................................................................................35 Utiliser Qweb dans un widget............................................................................................................................36 Contexte QWeb.................................................................................................................................................... 37 Déclaration de template.....................................................................................................................................37 Escaping........................................................................................................................................................... 38 Afficher du HTML............................................................................................................................................38 If........................................................................................................................................................................ 38 Foreach............................................................................................................................................................. 38 Définir de la valeur d'un attribut XML.........................................................................................................39 Pour en savoir plus QWeb...................................................................................................................................39 Exercice.................................................................................................................................................................. 39 Solution............................................................................................................................................................ 39 Événements et propriétés des Widgets.................................................................................................................41 Événements.......................................................................................................................................................... 41 Propriétés.............................................................................................................................................................. 43 Exercice.................................................................................................................................................................. 43 Assistants de Widgets...............................................................................................................................................45 Connexion plus facile avec les événements du DOM.....................................................................................45 Recommandations pour le développement....................................................................................................46 Modifier des Widgets et des classes existants......................................................................................................47 Traductions................................................................................................................................................................. 48 Communication avec le serveur OpenERP.............................................................................................................48 Contacter les modèles......................................................................................................................................... 48 CompoundContext.............................................................................................................................................. 50 Requêtes................................................................................................................................................................ 51 Exercices...................................................................................................................................................................... 52 Les composants web OpenERP...............................................................................................................................55 Action Manager.......................................................................................................................................................... 55 Utiliser l'ActionManager......................................................................................................................................55 Actions Client.............................................................................................................................................................. 57 Architecture des vues................................................................................................................................................ 58 Le gestionnaire de vues.......................................................................................................................................58 Les vues................................................................................................................................................................. 58 Les champs de la vue formulaire.............................................................................................................................58 Créer un nouveau type de champ......................................................................................................................60 Champ simple en lecture seule....................................................................................................................60 Champ en lecture-écriture............................................................................................................................61 Exercice – Créer un champ Couleur...................................................................................................................62 2/67
  3. 3. OpenERP V7.0 04/08/14 Les widgets personnalisés de la vue formulaire....................................................................................................63 Exercice – Afficher des coordonnées sur Google Map...................................................................................64 3/67
  4. 4. OpenERP V7.0 04/08/14 Introduction Rappel concernant la structure d'OpenERP OpenERP 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 guide Ce 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 a une bonne connaissance du développement des modules Python. Ce guide suppose également que le 4/67
  5. 5. 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
  6. 6. 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 commande Pour 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
  7. 7. 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
  8. 8. 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 of size 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 types JavaScript 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
  9. 9. 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ôle JavaScript fournit les mêmes structures de contrôle que C. (Pour tester avec la console Chrome, vous pouvez utiliser 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++; } 1 2 3 > for (var i = 5; i < 8; i++) { console.log(i); } 5 6 7 JavaScript fournit également une structure de boucle sur les objets (for (... in ...).) Il convient de noter que, en raison 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
  10. 10. OpenERP V7.0 04/08/14 Fonctions Les fonctions peuvent être déclarées comme suit: > function talk () { console.log("Hello World"); } > talk(); Hello World En 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"); hello undefined > print("nice", "to", "meet", "you"); nice to Variables et portée Une 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
  11. 11. 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(); hello world 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
  12. 12. 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"] Objets La 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
  13. 13. 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 lookup hello > console.log(obj.key2); // object-like lookup world 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
  14. 14. 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
  15. 15. 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
  16. 16. 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 moment si 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
  17. 17. 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
  18. 18. OpenERP V7.0 04/08/14 Underscore.js Underscore.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 fonctions Underscore.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
  19. 19. OpenERP V7.0 04/08/14 }; app.main = main; })(); Manipulations HTML avec jQuery jQuery 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 navigateur modifiera 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
  20. 20. 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
  21. 21. 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 sur les é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
  22. 22. 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 jQuery Dans 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
  23. 23. 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 du JSON et effectuent des opérations mathématiques simples (addition et multiplication). Nous allons écrire du code 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
  24. 24. 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êcher l'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 en y 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
  25. 25. 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
  26. 26. 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
  27. 27. 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, by transitivity, a function // that performs an asynchronous call. So it should return a deferred too. return def2; 27/67
  28. 28. OpenERP V7.0 04/08/14 Framework Web OpenERP Dans 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 framework Il 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 vous avez déjà écrit un module OpenERP. Un nouvelle table, quelques vues, des menus, etc. 28/67
  29. 29. 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
  30. 30. 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 OpenERP Dans le chapitre précédent, nous avons expliqué que Javascript n'a pas de mécanisme correct pour gérer les espaces 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 dictionnaire est 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 classes JavaScript 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
  31. 31. 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
  32. 32. 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 widgets Dans 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. Dans OpenERP, 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 le widget 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
  33. 33. 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 sera expliqué 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 modifierons un 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
  34. 34. 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
  35. 35. 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 QWeb Les 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
  36. 36. 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
  37. 37. 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
  38. 38. 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
  39. 39. 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
  40. 40. 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
  41. 41. OpenERP V7.0 04/08/14 Événements et propriétés des Widgets Il 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
  42. 42. 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 cette fonction 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
  43. 43. 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 peut ré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 fond que 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
  44. 44. 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
  45. 45. OpenERP V7.0 04/08/14 Note La méthode JQuery css() permet de d'attribuer une propriété css. Assistants de Widgets Vous 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 de vé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 la plupart 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
  46. 46. 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 syntaxe plus 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

×