Construire un composant « glissez déposez » avec le Google Web Toolkit (1ère Partie)
Un article de ODcWiki.
| |
Le Google Web Toolkit (GWT) est la plateforme de développement d’applications Ajax proposée par Google. Elle repose sur un principe simple et révolutionnaire : développer, tester, déboguer une application que l’on écrit en Java, et la déployer ensuite, en la traduisant – pour sa partie cliente – en JavaScript. Mais ce n’est pas tout.
Le paradigme de programmation du Google Web Toolkit est proche de celui utilisé traditionnellement dans des applications clients lourds : on construit l’interface en combinant des objets d’IHM appelés « Widget ». Ces « Widgets » émettent des événements (clic, sélection, modification, etc) qui peuvent être interceptées et traités par des méthodes réflexes. GWT propose un jeu de Widgets standard, assez frustre au demeurant. Il permet surtout de construire facilement de nouveaux types de Widgets.
Autre point : le GWT ne répudie pas JavaScript. Il est possible – et même facile - d’intégrer du JavaScript dans une application GWT. Ce code n’aura pas, bien sûr, besoin d’être ensuite traduit. Inconvénient : en phase de développement, il échappe à la sagacité du compilateur et du débogueur de votre IDE préféré. Nous allons voir que cette faculté est particulièrement intéressante pour intégrer des bibliothèques JavaScript telles que Dojo, Prototype, Script.aculo.us.
Sommaire |
Intégrer une bibliothèque JavaScript
Combinons tout cela pour enrichir le GWT d’une nouvelle Widget qui apporte une fonctionnalité manquante dans la version de base : le glissez déposer (Drag and Drop). Nous allons pour cela utiliser la bibliothèque Yahoo. Pourquoi Yahoo ? Parce que cette bibliothèque fonctionne bien, qu’elle est bien documentée et qu’elle propose un panel de fonctionnalités riches et modulables.
La première chose à faire est d’associer les fichiers de scripting Yahoo à notre application GWT, afin que les scripts de Yahoo soient chargés sur le navigateur de l’utilisateur avec la partie cliente du module GWT demandé. Avant d’expliquer plus en détail la marche à suivre, rappelons succinctement quelle est structure d’un projet GWT :
- Une application GWT est divisée en « modules ». Un module correspond à une page animée. C’est l’équivalent AJAX d’une « applet ».
- Les sources et ressources associées à un module sont regroupées au sein d’une même arborescence de packages, par exemple : « com.objetdirect.examples.minitoy ». Cette arborescence est la « racine » du module.
- Cette racine doit contenir trois sous packages aux noms définis par GWT : « public », « client » et « server ».
- Le sous package « public » contient les ressources associées au module : feuille CSS, images, scripts.
- Le sous package « client » contient le code de la partie cliente du module. Ce sont les sources JAVA qui seront traduits in fine en JavaScript.
- Le sous package « server » contient le code de la partie serveur du module. Ce sont les sources JAVA qui doivent s’exécuter sur le serveur Web, en réponse aux requêtes émises par la partie cliente. Elles ne sont pas traduites en JavaScript.
- Le module est configuré par un fichier XML appelé <nom-module>.gwt.xml. Ce fichier est placé dans l’arborescence racine du module (« com.objetdirect.examples.minitoy » dans notre cas).
Pour intégrer la bibliothèques JavaScript Yahoo qui implémente le glissez déposez, il faut d’abord déposer les scripts Yahoo idoines dans le répertoire « public » de notre module. Il s’agit, des fichiers yahoo.js, dom.js, event.js, dragdrop.js et DDPlayer.js :
- yahoo.js, dom.js, event.js contiennent les routines qui forment le cœur de la bibliothèque proposée par Yahoo. Ces scripts sont utilisés par les différentes fonctionnalités de cette bibliothèque (glissez déposer, animation, composants, etc.)
- dragdrop.js contient l’implémentation de la fonctionnalité « glissez déposer »
- DDPlayer.js est un exemple proposé par Yahoo qui implémente une fonctionnalité générique : celle d’associer des zones de dépôt à des objets glissants. Elle peut être utilisée en l’état. Je me demande pourquoi Yahoo ne l’a pas mise dans le cœur de sa bibliothèque.
Il faut ensuite déclarer ces scripts dans le fichier de configuration du module, c'est-à-dire le fichier <nom-module>.gwt.xml. Pour cela, on rajoute, pour chaque fichier script, une section « script » au sein de la balise XML racine (« module »), comme suit :
<module> … <script src="yahoo.js"><![CDATA[ if ($wnd.yahooIsOk) return true; else return false; ]]> </script> … <module>
Cette section indique que le module a besoin du script « yahoo.js ». Il contient aussi le morceau de JavaScript qui s’assure que le script a été complètement chargé. Attention, ce n’est pas un gadget. Ignorer ce « détail » mène généralement a un comportement incohérent du module GWT. Il semble en effet que les scripts sont chargés de manière asynchrone par le module, de façon concurrente avec son propre démarrage.
Bien sûr, Yahoo n’a pas prévu que sa librairie puisse être intégrée dans la plateforme de son grand concurrent. Il n’a donc pas prévu de méthode pour s’assurer du bon chargement de son script. C’est à nous, pauvres développeurs de « terminer » le travail en rajoutant à l’extrême fin du fichier yahoo.js la définition suivante :
function yahooIsOk() { }
La snippet JavaScript contenue dans le fichier .gwt.xml teste seulement l’existence de la fonction « yahooIsOk ». L’implémentation de cette fonction n’a donc aucun intérêt. C’est pourquoi nous la laissons à vide.
J’admets que devoir modifier des composants fournis par un tiers n’est pas sans inconvénient. Mais c’est la seule solution que je connaisse qui soit à la fois simple et sûre. Se reposer sur la dernière définition que contient le fichier yahoo.js nous expose de toute façon à devoir inspecter ce fichier à chaque nouvelle importation de la dernière version de la bibliothèque Yahoo.
Il faut bien sûr définir une méthode différente pour chaque script : yahooIsOk pour yahoo.js, dragdropIsOk pour dragdrop.js, et ainsi de suite.
Invoquer du JavaScript au sein du code Java
Désormais, les scripts dont nous avons besoin sont disponibles au sein de notre module. Mais ils restent en JavaScript. Il n’y a pas de traduction inverse JavaScript vers Java ! Si nous voulons faire appel aux routines de ces scripts, il nous faut invoquer du JavaScript.
Heureusement, GWT nous propose un mécanisme simple pour le faire. C’est JSNI (comme JavaScript Native Interface), un dérivé GWT du mécanisme standard de JNI (Java Native Interface).
Voici un exemple illustrant JSNI :
public native static void setLocation( Element e, int leftPos, int topPos) /*-{ $wnd.YAHOO.util.Dom.setStyle(e, 'left', leftPos+'px'); $wnd.YAHOO.util.Dom.setStyle(e, 'top', topPos+'px'); }-*/;
La méthode setLocation est une méthode Java déclarée comme étant « native », c'est-à-dire que son implémentation n’est pas donnée en Java. Ici, on est encore dans JNI. Cette implémentation est fournie sous la forme d’un commentaire particulier, définie par les balises « /*-{ » et « }-*/ ». Le contenu de ce commentaire est le JavaScript qui devra être exécuté lorsque setLocation est invoquée. Ici on est dans JSNI. Ces commentaires ne sont reconnus que par GWT. N’essayez pas de les utiliser sur d’autres plateformes !
Voila. C’est tout simple. Il n’y a rien d’autre à faire : pas de compilateur à lancer, de script ant à exécuter. Notez aussi que les paramètres passés à notre méthode Java sont utilisables au sein de notre code JavaScript. Le fait que setLocation soit statique n’est pas une obligation. Nous aurions pu tout aussi bien la définir comme méthode d’instance.
Si vous inspectez le code de dom.js, vous trouverez la routine « YAHOO.util.Dom.setStyle ». Cette méthode est définie au sein de la fenêtre courante. Cette fenêtre est accessible au sein d’une routine JSNI via la variable standard « $wnd ». N’oubliez de préfixer tous vos appels aux routines Yahoo par cette référence.
La première version de notre composant
Nous allons développer un composant de type panel (une widget conteneur d’autres widgets) qui contient des composants capables de glisser sur le panel. Pour cela, il suffit de créer une classe JAVA dérivant d’une classe Panel du Google Web Toolkit. Nous allons utiliser ici la classe AbsolutePanel qui crée un panel permettant un positionnement absolu des widgets filles, c'est-à-dire que l’on doit préciser la position en x, y de chacune d’elle.
La voici dans son intégralité, le code définissant notre panel « glissez déposez » :
public class SimpleDragAndDropPanel extends AbsolutePanel { public void addDraggableWidget( Widget widget, int left, int top, String affordance) { add(widget, left, top); addDraggableSlot(widget.getElement(), affordance, counter++); } public void addTargetWidget( Widget widget, int left, int top, String affordance) { add(widget, left, top); addTargetSlot(widget.getElement(), affordance, counter++); } public static native void addDraggableSlot( Element e, String affordance, int counter) /*-{ e.id = 'drag_'+counter; new $wnd.YAHOO.example.DDPlayer(e.id, affordance); }-*/; public static native void addTargetSlot( Element e, String affordance, int counter) /*-{ e.id = 'target_'+counter; new $wnd.YAHOO.util.DDTarget(e.id, affordance); }-*/; int counter; }
Simple n’est ce pas ? Qui a dit que le GWT ne dispose pas d’un composant « glissez déposez » ? On réutilise bien sûr l’essentiel des fonctionnalités offertes par AbsolutePanel. La seule action supplémentaire de notre classe est d’associer à l’avatar JavaScript de chaque widget (une instance de la classe Element), un objet Yahoo qui en gère le déplacement. Si on associe un objet de type YAHOO.example.DDPlayer la widget pourra être glissée. Si on lui associe un objet de type YAHOO.util.DDTarget, elle pourra être la cible d’un dépôt.
L’avatar JavaScript d’une widget est obtenu par la méthode getElement de la Widget. C’est un objet qui peut être utilisé dans le code JavaScript (alors que le type widget est inconnu de JavaScript).
Notre classe panel permet aussi de préciser la compatibilité entre widgets glissantes et zone de dépôt. Il s’agit du paramètre « affordance ». Pour qu’une wiget glissante puisse être déposée sur une widget zone de dépôt (ou cible), il faut qu’elles partagent la même « affordance ». Dans le cas contraire, la widget glissante revient automatiquement sur sa position de départ.
Notez que notre composant n’oblige pas à affecter comme position initiale d’une widget glissante, la position d’une widget de dépôt. Ensuite, elle ne peut se déplacer que sur des widgets de dépôt compatibles.
Voici un exemple d’utilisation de notre nouveau composant :
SimpleDragAndDropPanel amoursCelebres = new SimpleDragAndDropPanel(); Label abelard = new Label("Abélard"); amoursCelebres.addDraggableWidget( abelard, 10, 10, "heloise_et_abelard"); Label heloise = new Label("Héloise"); amoursCelebres.addTargetWidget( heloise, 200, 10, "heloise_et_abelard"); Label tristan = new Label("Tristan"); amoursCelebres.addDraggableWidget( Tristan, 10, 50, "trisant_et_iseult"); Label iseult = new Label("Iseult"); amoursCelebres.addTargetWidget( iseult, 200, 50, "tristan_et_iseult");
Vous pouvez maintenant faire mentir l’histoire en réunissant vous-mêmes ces amants malheureux. Notez que nous ne pourrez former que les couples présentant la même affinité. Si vous forcez une rencontre, le galant reviendra tout seul à sa place initiale.
En conclusion provisoire
Cette première itération de notre composant de « Glissez déposer » est illustratif de la capacité du Google Web Toolkit à intégrer très simplement des éléments qui lui sont complètement exogènes. En moins de 70 lignes, nous avons mis en œuvre un mécanisme qui manque cruellement à la version de base de GWT !
L’exemple de la librairie Yahoo peut être reproduit pour d’autres librairies comme Prototype ou Script.aculo.us. GWT ne vient donc pas concurrencer ces travaux : il permet au contraire d’en profiter plus facilement.
On remarque aussi la facilité qu’il y a à mixer JAVA et JavaScript grâce à JSNI. Comme les parties écrites en JavaScript ne sont pas facilement déboggables, on aura intérêt à les limiter au strict minimum. L’utilisation la plus judicieuse de JSNI est donc bien de servir de « colle » entre le programme JAVA et des librairies JavaScript du marché.
Notre composant n’est pas complet. Il ne permet pas de réagir aux opérations de « Glissez déposer ». Nous verrons dans une seconde partie comment rajouter un mécanisme événementiel à notre composant panel. Cela nous permettra de voir comment nous pouvons appeler du code GWT Java à partir d’un code GWT JavaScript.

