Traduction (très libre) de http://doc.openerp.com/trunk/developers/web/module/

Construction d’un module Web OpenERP

Il n’y a pas de distinction significative entre un module OpenERP Web et un module OpenERP,

La partie web est le plus souvent une partie de données et de code à l’intérieur d’un module « normal ».

Ceci permet d’ajouter des nouvelles fonctionnalités web directement dans le module.

Un module de base

La structure basique d’un module est la suivante :

web_example
├── __init__.py
└── __openerp__.py
# __openerp__.py
{
    'name': "Exemple Web",
    'description': "Exemple basique de module web",
    'category': 'Hidden',
    'depends': ['base'],
}

Il s’agit du minimun pour un module OpenERP valide.

Web Declaration

Pour spécifier qu’il s’agit d’un module web il suffit d’ajouter le repertoire static à la racine, donc:

web_example
├── __init__.py
├── __openerp__.py
└── static

Puis d’ajouter le dependance « web »:

--- web_example/__openerp__.py
+++ web_example/__openerp__.py
@@ -1,7 +1,7 @@
 # __openerp__.py
 {
     'name': "Web Example",
     'description': "Basic example of a (future) web module",
     'category': 'Hidden',
-    'depends': ['base'],
+    'depends': ['web'],
 }

Du fait de l’ajout de la dependance web le systeme contrôle si il y a des controleurs et du code javascript à charger. Le contenue du repertoire static est automatiquement disponible dans la browser sous la forme $module_name/static/$file_path. C’est suffisant pour par exemple fournir des images à un module,

Getting Things Done

La première étape est d’ajouter du code javascript.

Il est de coutume de le mettre dans le dossier static/src/js

// static/src/js/first_module.js
console.log("Debug statement: file loaded");

Le client ne chargera pas n’importe qu’il fichier (sauf avis contraire) , donc le nouveau fichier doit être répertorié dans le fichier manifeste ( __openerp__.py ) du module sous une nouvelle clé : js

--- web_example/__openerp__.py
+++ web_example/__openerp__.py
@@ -1,7 +1,8 @@
 # __openerp__.py
 {
     'name': "Web Example",
     'description': "Basic example of a (future) web module",
     'category': 'Hidden',
     'depends': ['web'],
+    'js': ['static/src/js/first_module.js'],
 }

A ce stade si le module est installé et le client rechargé le message  « Debug statement: file loaded » devrait apparaître dans la console du navigateur,

Note

Comme le fichier de manifeste a été modifié, vous devez redémarrer le serveur OpenERP pour que la modification puisse être prise en compte.

A ce stade, le code s’exécute, mais il fonctionne qu’une seule fois lorsque le module est initialisé, et il ne peut pas accéder à l’API du client Web (tels que des requêtes RPC vers le serveur). Ceci est réalisé en fournissant un module javascript:

--- web_example/static/src/js/first_module.js
+++ web_example/static/src/js/first_module.js
@@ -1,2 +1,4 @@
 // static/src/js/first_module.js
-console.log("Debug statement: file loaded");
+openerp.web_example = function (instance) {
+    console.log("Module loaded");
+};

Si vous rechargez le client vous obtiendrez exactement le même message dans la console , les differences invisibles sont les suivantes :

– Tous les fichiers javascript spécifiés dans le manifeste (seulement celui-ci à ce jour) ont été entièrement chargé

– Une instance du client web et un espace de noms à l’intérieur de cette instance (avec le même nom que le module) ont été créés et sont disponibles pour une utilisation

Le parametre instance fournit à la fonction est une instance du client web Openerp avec un accés à tout les modules initialisés, ce soit les points d’entrée au API du client web.

Pour le démontrer nous allons contruire une action simple: un chronomètre

premièrement déclaration de l’action:

--- web_example/__openerp__.py
+++ web_example/__openerp__.py
@@ -1,8 +1,9 @@
 # __openerp__.py
 {
     'name': "Web Example",
     'description': "Basic example of a (future) web module",
     'category': 'Hidden',
     'depends': ['web'],
+    'data': ['web_example.xml'],
     'js': ['static/src/js/first_module.js'],
 }
<!-- web_example/web_example.xml -->
<openerp>
    <data>
        <record model="ir.actions.client" id="action_client_example">
            <field name="name">Example Client Action</field>
            <field name="tag">example.action</field>
        </record>
        <menuitem action="action_client_example"
                  id="menu_client_example"/>
    </data>
</openerp>

puis enregistrement de l’action:

--- web_example/static/src/js/first_module.js
+++ web_example/static/src/js/first_module.js
@@ -1,4 +1,7 @@
 // static/src/js/first_module.js
 openerp.web_example = function (instance) {
-    console.log("Module loaded");
+    instance.web.client_actions.add('example.action', 'instance.web_example.action');
+    instance.web_example.action = function (parent, action) {
+        console.log("Executed the action", action);
+    };
 };

Mettre à jour le module ( pour charger la description xml) et redémarrer le serveur,

Un nouveau menu « Example Client Action » doit apparaître dans le haut du client cliquez srur le menu fait appaitre le messahe dans la console du navigateur.

Paint it black

L’étape suivante est de prendre le contrôle de la page elle même, plutôt que simplement afficher de petits messages dans la console.

Pour le faire nous allons remplacer notre function action client par un widget.

Notre widget utilise la fonction start() pour ajouter du content au DOM.

The next step is to take control of the page itself, rather than just print little messages in the console. This we can do by replacing our client action function by a Widget. Our widget will simply use its start() to add some content to its DOM:

--- web_example/static/src/js/first_module.js
+++ web_example/static/src/js/first_module.js
@@ -1,7 +1,11 @@
 // static/src/js/first_module.js
 openerp.web_example = function (instance) {
-    instance.web.client_actions.add('example.action', 'instance.web_example.action');
-    instance.web_example.action = function (parent, action) {
-        console.log("Executed the action", action);
-    };
+    instance.web.client_actions.add('example.action', 'instance.web_example.Action');
+    instance.web_example.Action = instance.web.Widget.extend({
+        className: 'oe_web_example',
+        start: function () {
+            this.$el.text("Hello, world!");
+            return this._super();
+        }
+    });
 };

après rechargement du client (pour mettre à jour le javascript), au lieu d’afficher un message dans la console le message est affiché dans la page.

Nous pouvons maintenant voir comment ajouter une feuille de style à un module:

Pour commencer créer la feuille de style (static/src/css/web_example.css) :

.openerp .oe_web_example {
    color: white;
    background-color: black;
    height: 100%;
    font-size: 400%;
}

ajouter la feuille de style dans le manifeste (penser à redémarrer le serveur)

--- web_example/__openerp__.py
+++ web_example/__openerp__.py
@@ -1,9 +1,10 @@
 # __openerp__.py
 {
     'name': "Web Example",
     'description': "Basic example of a (future) web module",
     'category': 'Hidden',
     'depends': ['web'],
     'data': ['web_example.xml'],
     'js': ['static/src/js/first_module.js'],
+    'css': ['static/src/css/web_example.css'],
 }

le texte affiché devrait maintenant être énorme et affiché sur fond noir

 

Le language de template d’OpenERP est QWeb. Bien que n’importe quel moteur de templates puisse être utilisé (par exemple, mustache or _.template) QWEB a des caractéristiques importantes que d’autres moteurs de template ne peuvent pas fournir, et une intégration spéciale au widget OpenERP .

Ajouter d’un fichier de modèle (static/src/xml/web_example.xml )est équivalent à l’ajout d’une feuille de style :

<templates>
<div t-name="web_example.action">
    <h4>00:00:00</h4>
    <p>
        <button type="button">Start</button>
    </p>
    <p>
        <button type="button">Stop</button>
    </p>
</div>
</templates>
--- web_example/__openerp__.py
+++ web_example/__openerp__.py
@@ -1,10 +1,11 @@
 # __openerp__.py
 {
     'name': "Web Example",
     'description': "Basic example of a (future) web module",
     'category': 'Hidden',
     'depends': ['web'],
     'data': ['web_example.xml'],
     'js': ['static/src/js/first_module.js'],
     'css': ['static/src/css/web_example.css'],
+    'qweb': ['static/src/xml/web_example.xml'],
 }

Le modèle peut alors facilement être relié dans le widget:

 

--- web_example/static/src/js/first_module.js
+++ web_example/static/src/js/first_module.js
@@ -1,11 +1,7 @@
 // static/src/js/first_module.js
 openerp.web_example = function (instance) {
     instance.web.client_actions.add('example.action', 'instance.web_example.Action');
     instance.web_example.Action = instance.web.Widget.extend({
+        template: 'web_example.action'
-        className: 'oe_web_example',
-        start: function () {
-            this.$el.text("Hello, world!");
-            return this._super();
-        }
     });
 };

Et le css peut être modifié pour le nouveau (et plus complexe) DOM généré par le template:

--- web_example/static/src/css/web_example.css
+++ web_example/static/src/css/web_example.css
@@ -1,6 +1,13 @@
 .openerp .oe_web_example {
     color: white;
     background-color: black;
     height: 100%;
-    font-size: 400%;
 }
+.openerp .oe_web_example h4 {
+    margin: 0;
+    font-size: 200%;
+}
+.openerp .oe_web_example.oe_web_example_started .oe_web_example_start button,
+.openerp .oe_web_example.oe_web_example_stopped .oe_web_example_stop button {
+    display: none
+}

Note

La dernière section de la modification CSS est un exemple de «classes d’État»: une classe CSS (ou un ensemble de classes) à la racine du widget, qui est activé en fonction de l’évolution de l’état du widget et peut effectuer des modifications radicales dans le rendu ( généralement afficher / masquer différents éléments).

La dernière étape (jusqu’au prochain) est d’ajouter certains comportements et faire de notre chronomètre. D’abord accrocher des événements sur les boutons pour basculer l’état du widget:

--- web_example/static/src/js/first_module.js
+++ web_example/static/src/js/first_module.js
@@ -1,7 +1,19 @@
 // static/src/js/first_module.js
 openerp.web_example = function (instance) {
     instance.web.client_actions.add('example.action', 'instance.web_example.Action');
     instance.web_example.Action = instance.web.Widget.extend({
-        template: 'web_example.action'
+        template: 'web_example.action',
+        events: {
+            'click .oe_web_example_start button': 'watch_start',
+            'click .oe_web_example_stop button': 'watch_stop'
+        },
+        watch_start: function () {
+            this.$el.addClass('oe_web_example_started')
+                    .removeClass('oe_web_example_stopped');
+        },
+        watch_stop: function () {
+            this.$el.removeClass('oe_web_example_started')
+                    .addClass('oe_web_example_stopped');
+        },
     });
 };

Cela démontre l’utilisation des événements .

 

Puis vient une certaine logique réelle:

— web_example/static/src/js/first_module.js

+++ web_example/static/src/js/first_module.js
@@ -1,19 +1,52 @@
 // static/src/js/first_module.js
 openerp.web_example = function (instance) {
     instance.web.client_actions.add('example.action', 'instance.web_example.Action');
     instance.web_example.Action = instance.web.Widget.extend({
         template: 'web_example.action',
         events: {
             'click .oe_web_example_start button': 'watch_start',
             'click .oe_web_example_stop button': 'watch_stop'
         },
+        init: function () {
+            this._super.apply(this, arguments);
+            this._start = null;
+            this._watch = null;
+        },
+        update_counter: function () {
+            var h, m, s;
+            // Subtracting javascript dates returns the difference in milliseconds
+            var diff = new Date() - this._start;
+            s = diff / 1000;
+            m = Math.floor(s / 60);
+            s -= 60*m;
+            h = Math.floor(m / 60);
+            m -= 60*h;
+            this.$('.oe_web_example_timer').text(
+                _.str.sprintf("%02d:%02d:%02d", h, m, s));
+        },
         watch_start: function () {
             this.$el.addClass('oe_web_example_started')
                     .removeClass('oe_web_example_stopped');
+            this._start = new Date();
+            // Update the UI to the current time
+            this.update_counter();
+            // Update the counter at 30 FPS (33ms/frame)
+            this._watch = setInterval(
+                this.proxy('update_counter'),
+                33);
         },
         watch_stop: function () {
+            clearInterval(this._watch);
+            this.update_counter();
+            this._start = this._watch = null;
             this.$el.removeClass('oe_web_example_started')
                     .addClass('oe_web_example_stopped');
         },
+        destroy: function () {
+            if (this._watch) {
+                clearInterval(this._watch);
+            }
+            this._super();
+        }
     });
 };
  • Un initialiseur (la méthode init) est introduite pour mettre en place quelques variables internes: _start lance la minuterie (en tant qu’objet Date javascript), et _watch met à jour l’inteface pour afficher la durée.
  • update_counter est chargée la différence de temps entre «maintenant» et _start du formatage de la forme HH: MM: SS et d’afficher le résultat sur l’écran
  • watch_start initialise _start avec sa valeur et met en place la mise à jour du compteur toutes les 33 millisecondes
  • watch_stop désactive la mise à jour, fait une mise à jour finale de l’affichage du compteur et réinitialise tout.
  • Enfin, parce que l’intervalle javascript et les objets Timeout sont exécuté « en dehors » du widget, ils vont continuer même après que le widget aura été détruit. Donc _watch doit être effacée lorsque le widget est détruit (alors _super doit être appelé pour effectuer le nettoyage « normal » widget).Démarrage et arrêt du chronomètre fonctionne maintenant, et suit correctement le temps écoulé depuis le declenchement.

__openerp__.py

{

‘name’: Exemple Web’,

‘description’: « exemplede base d’un module Web (futur) »,

‘category’: ‘Tools’,

‘depends’: [‘web’],

‘data’: [‘web_example.xml’],

js: [‘static/src/js/first_module.js’],

css: [‘static/src/css/web_example.css’],

qweb: [‘static/src/xml/web_example.xml’],

}

web_example.xml

<openerp>

<data>

<record model=« ir.actions.client » id=« action_client_example »>

<field name=« name »>Example Client Action</field>

<field name=« tag »>example.action</field>

</record>

<menuitem action=« action_client_example » id=« menu_client_example »/>

</data>

</openerp>

static/src/css/web_example.css

.openerp.oe_web_example{

color: white;

background-color: black;

height: 100%;

}

.openerp.oe_web_exampleh4 {

margin: 0;

font-size: 200%;

}

.openerp .oe_web_example.oe_web_example_started .oe_web_example_start button,

.openerp.oe_web_example.oe_web_example_stopped.oe_web_example_stopbutton {

display: none

}

static/src/xml/web_example.xml

<templates>

<div t-name=« web_example.action » class=« oe_web_example oe_web_example_stopped »>

<h4 class=« oe_web_example_timer »>00:00:00</h4>

<p class=« oe_web_example_start »>

<button type=« button »>Start</button>

</p>

<p class=« oe_web_example_stop »>

<button type=« button »>Stop</button>

</p>

</div>

</templates>

static/src/css/first_module.js

openerp.web_example=function(instance){

console.log(« Module loaded »);

instance.web.client_actions.add(‘example.action’,‘instance.web_example.Action’);

instance.web_example.Action=instance.web.Widget.extend({

template:‘web_example.action’,

events:{

‘click .oe_web_example_start button’:‘watch_start’,

‘click .oe_web_example_stop button’:‘watch_stop’

},

init:function(){

this._super.apply(this,arguments);

this._start=null;

this._watch=null;

},

update_counter:function(){

varh,m,s;

// Subtracting javascript dates returns the difference

// in milliseconds

vardiff=newDate()this._start;

s=diff/1000;

m=Math.floor(s/60);

s-=60*m;

h=Math.floor(m/60);

m-=60*h;

this.$(‘.oe_web_example_timer’).text(

_.str.sprintf(« %02d:%02d:%02d »,h,m,s));

},

watch_start:function(){

this.$el.addClass(‘oe_web_example_started’)

.removeClass(‘oe_web_example_stopped’);

this._start=newDate();

// Update the UI to the current time

this.update_counter();

// Update the counter at 30 FPS

// (33ms/frame)

this._watch=setInterval(

this.proxy(‘update_counter’),

33);

},

watch_stop:function(){

clearInterval(this._watch);

this.update_counter();

this._start=this._watch=null;

this.$el.removeClass(‘oe_web_example_started’)

.addClass(‘oe_web_example_stopped’);

},

destroy:function(){

if(this._watch){

clearInterval(this._watch);

}

this._super();

},

 });

};

Le fichier zip du code web_exemple

Cet article, publié dans OpenErp, est tagué , . Ajoutez ce permalien à vos favoris.

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l’aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s