lundi 3 juin 2013

Création d'un évènement personnalisé en GWT

Nous allons découvrir dans cet article comment créer un évènement personnalisé dans une application GWT. Pour cela nous allons concevoir une petite application où deux panneaux vont communiquer entre eux par l'intermédiaire d'un évènement créé par nos propres soins.

Voici à quoi va ressembler notre application :

L'application se composera donc de deux panneaux verticaux contenant chacun : une zone de saisie de texte, un bouton Ok et un espace vierge en dessous permettant d'afficher des labels.
Le fonctionnement doit être le suivant : Lorsque l'utilisateur clique sur le bouton présent dans un panneau vertical, alors un label sera ajouter sur le panneau opposé et son texte devra être celui qui a été saisi dans la zone de texte. Bien entendu, les panneaux ne sont pas liés par une quelconque variable, ainsi un panneau n'a pas connaissance de l'autre panneau placé à son opposé.

Étudions dans un premier temps le cas général de la création d'un évènement personnalisé. Trois classes interviendront dans le processus :
- La classe correspondante à l'évènement. Elle permet créer un évènement et de faire transiter un certain nombre d'informations entre le composant source (celui qui déclenche l'évènement) et le composant cible (celui qui reçoit l'évènement). Cette classe doit étendre la classe GwtEvent<Handler>.

- L'interface correspondante au traitement de l'évènement par le composant cible (le Handler). Elle va définir la méthode que devra implémenter le composant cible afin de traiter l'évènement. Elle devra elle-même implémenter la classe EventHandler.

- L'interface permettant au composant cible de s'abonner à l'évènement émis par le composant source. C'est le point de liaison entre la source, l'évènement et la cible. Cette interface devra être implémentée par le composant source (émetteur de l'évènement) avec de permettre au composant cible (récepteur de l'évènement : le handler) de pouvoir s'abonner à l'évènement.

Voici un petit schémas résumant la situation :



Nous allons voir un cas concret grâce à la mise en place de notre application. J’appellerai l'évènement LabelSend et le projet s'intitulera CustomEvent.

Dans un premier temps voici la classe correspondante à l'évènement


On déclare dans un premier temps le type, permettant ainsi d'identifier un évènement. Cela permettra lors de la réception d'un évènement, de le dispatcher à tous les handlers s'ayant abonnés à ce type d'évènement.
Ensuite on déclare les informations utiles à faire transiter entre la source et la cible. Ici il s'agit simplement d'une variable de type String qui correspondra au texte saisi dans la zone de texte. Enfin on note la présence de la méthode dispatch. Cette méthode est primordiale car elle permet d'appeler la fonction du handler pour traiter l'évènement qui est passé en paramètre (notamment pour avoir accès à la variable texte)

Dans un second temps, on a l'interface correspondante au handler :


Cette interface définit la méthode qui devra être implémentée par le/les handler(s) (les récepteurs de l'évènement).

Pour finir nous définissons l'interface permettant de s'abonner à l'évènement.

Comme on l'a dit précédemment cette méthode permet aux différents handlers de s'abonner à l'évènement. Elle devra être implémentée par le panneau source (l’émetteur de l'évènement) permettant ainsi au récepteur de s’enregistrer.

L'interface HasHandlers (contenu dans le SDK de GWT) contient une méthode fireEvent(GwtEvent<?> event). Elle devra donc être redéfinie dans la classe qui implémentera HasLabelSendHandlers.

Mise en place de l'application

Créons nos deux panneaux verticaux :



Ces deux panneaux sont à la fois récepteur (handler) et émetteur de l'évènement. Ils implémentent donc à la fois l'interface du Handler permettant de traiter la réception de l'évènement et l'interface Has*Handlers permettant de s'abonner à l'évènement. Ils déclenchent également tous les deux l'évènement LabelSendEvent avec la méthode fireEvent lorsque l'utilisateur clique sur le bouton.
A noter également qu'on a pas besoin de redéfinir la méthode fireEvent car elle est déjà contenue dans la classe Widget qui est une classe mère de notre VerticalPanel.

Dans le point d'entrée de l'application, j'affiche les panneaux verticaux et je les abonne mutuellement pour traiter la réception de l'évènement.



On compile puis on lance notre application :



jeudi 30 mai 2013

Architecture MVP (Model View Presenter) GWT avec Activity & Place

En développant avec le framework GWT on remarque qu'il n'est pas toujours évident de bien séparer les différentes parties de son application. On aimerait également que chaque page possède sa propre URL (afin que l'utilisateur puisse utiliser les boutons suivant/précédent du navigateur). L'architecture MVP permet de diviser son application en plusieurs pages et de bien séparer le code métier de l'interface graphique. Grâce au Framework Activity and Place on pourra définir une url différente pour chaque page créée et ainsi permettre au navigateur d'utiliser l'historique de navigation.

Adopter cette stratégie permet de réaliser un projet bien structuré et facile à maintenir. De plus les utilisateurs de votre application apprécieront le fait de pouvoir accéder aux différentes pages en utilisant la barre d'adresse du navigateur.

Google décrit sur cette page la mise en place de cette architecture, je présenterai ici la création d'une petite application de gestion de produits contenant simplement deux pages permettant d'ajouter un produit et de lister les produits.

 Etape 1 : Création des vues

Qu'est-ce qu'une vue ? Cela correspond tout simplement à l'interface graphique d'une page, elle ne doit pas contenir de code métier mais doit se contenter de créer et d'afficher les différents composants qui seront contenus dans la page.

Nous allons donc créer deux vues : une vue pour la page de création de produit et une vue pour la page de visualisation de produits.

Création de la première vue permettant de créer un produit

Nous allons dans un premier temps créer une interface nommée CreateProductView. Cette interface doit étendre l'interface IsWidget afin de s'assurer que les implémentations de la classe CreateProductView fourniront bien un widget. Nous définissons une méthode setPresenter qui nous servira pour communiquer avec la classe qui s'occupera de gérer le code métier. Nous verrons cela par la suite.


A quoi sert le Presenter ? Cette interface sera le point de communication entre la vue et l'activité (Une activité correspond à la classe qui sera charger de traiter le code métier correspondant à la vue). Ainsi dans l'interface Presenter, vous devez spécifier toutes les fonctions qui devront être implémentées par l'activité. Ici on aura besoin d'ajouter un produit, ce n'est pas à la vue de s'en charger (son rôle est uniquement d'afficher les composants graphiques de la page), c'est à l'activité que nous verrons par la suite de s'occuper d'ajouter un produit à l'application.

Maintenant nous allons créer la classe qui implémente cette interface afin de construire le widget.


Pas de surprise ici, nous nous contentons d'afficher un formulaire permettant de saisir les informations relatives à un produit (nom, description) ainsi qu'un bouton de validation qui appellera la fonction addProduct du Presenter. A noter que nous étendons la classe Composite permettant définir la classe comme étant un Widget.

Pourquoi avons-nous créé une interface pour définir la vue ? C'est vrai que nous aurions pu directement créer une classe (sans interface) pour définir notre vue. Cependant, le fait de créer une interface nous permet de définir plusieurs implémentations pour une vue. Ainsi nous pourrons par exemple afficher une vue différente selon que l'utilisateur soit sur un smartphone, une tablette où sur un ordinateur.

Création de la vue de visualisation de produits

Avant de créer la vue de visualisation des produits, nous allons créer deux classes permettant de faire fonctionner l'application. Premièrement nous aurons une classe Product contenant deux champs (nom et description) puis nous aurons une classe Shop permettant de sauvegarder nos produits. En temps normal la sauvegarde doit se faire en base de données, or ici pour simplifier les choses nous utiliserons la classe Shop comme étant un Singleton. Voici le descriptif de ces deux classes :



Nous pouvons désormais créer la seconde vue qui affichera la liste des produits. Elle ne contiendra pas de Presenter étant donné qu'il n'y a aucune utilité à ce qu'elle communique avec son activité (le code métier).


L'interface définie une méthode setProducts(List<Product> products) permettant de fournir les produits à afficher sur la vue.



L'implémentation affichera la liste des produits dès lors que l'on appellera la méthode setProducts.

Voila pour ce qui est des vues. Bien évidemment dans une application plus grande on pourra créer les vues en utilisant uiBinder, cela reste la même choses sauf que la partie graphique sera déclarée en xml.
Ce qu'il faut retenir c'est que l'unique rôle de la vue doit être d'afficher des composants graphiques, elle ne doit pas contenir de code métier. C'est l'activité que nous allons voir par la suite qui va s'en charger.

Etape 2 : Création de la factory

La mise en place d'une factory nous permettra de fournir le bus d'évènement, le placeController et les différentes implémentations des vues.

Grâce à cette factory que nous créerons via Deferred binding (Client Factory clientFactory = GWT.create(ClientFactory.class);), nous pourrons choisir qu'elle implémentation nous souhaitons obtenir en fonction du User Agent par exemple.


Ici nous n'avons qu'une seule implémentation de notre factory, mais nous pourrions très bien en avoir plusieurs si nous avions différentes implémentations de vues pour notre application. En ajoutant la balise <when-property-is name="user.agent" value="" /> dans le fichier de description du projet (.gwt.xml) , vous pouvez ainsi sélectionner l'implémentation de la factory à utiliser. Pour cette petite application je vais faire au plus simple et utiliser qu'une seule implémentation de la factory.

Etape 3 : Création des activités

Les activités dans GWT permettent de gérer le code métier des vues de l'application. Une activité peut être perçu comme représentant une fonctionnalité de votre application. Elle peut être démarrée et stoppée, son but est d'afficher la vue correspondante à la fonctionnalité (via la méthode start) et de gérer la logique métier en amont.





Ici la méthode, addProduct permet d'ajouter un produit à l'application puis de naviguer vers la page qui liste les produits en utilisant la méthode goTo du placeController. Pour naviguer parmi les différentes pages de l'application, on utilisera cette méthode, c'est la clientFactory qui nous permettra d'obtenir le placeController.



Ici on appelle la méthode setProducts permettant d'afficher la liste des produits présents dans l'application.

Etape 4 : Création des places

Les places sont de paires avec les activités, en fait chaque activité est associée à une place.
Une place correspond à l'endroit où l'on se trouve dans l'application, elle est la représentation d'une page, ainsi on lui associe généralement un nom spécifique ainsi qu'un token. Ces derniers seront définis dans un objet que l'on appellera le Tokenizer.

Représentation de l'url : http://addresse_serveur:port_serveur/nom_page.html#nom_place:token

Le nom_place se définit grâce à l'annotation @Prefix du Tokenizer

Le token permet d'ajouter une information supplémentaire sur la page. Ici on ne l'utilisera pas car il nous serait inutile. Imaginons que nous avons une page affichant les informations d'un produit. Le nom de la page serait showProduct et le token pourrait représenter une information utile comme par exemple l'id du produit.

L'url de la place sera : createProduct:null


Le nom de la place ici sera : showProducts:null


On peut déclarer le Tokenizer comme étant un champ statique de la Place, mais ce n'est pas une obligation ! On pourrait très bien le définir à l'extérieur de la Place.

Deux méthodes doivent être redéfinies lorsque l'on déclare le Tokenizer : getPlace et getToken.

- getPlace(String token) permettra de retourner la place correspondante à l'url saisi par l'utilisateur.

- getToken(Place place) permettra d'afficher le token (correspondant à la place) dans l'url. Ceci intervient lorsque l'on navigue dans l'application par l’intermédiaire de la méthode goTo du placeController

Etape 5 : Création des mappers

Le premier Mapper permet de déclarer les différents Tokenizer que l'on a dans notre application, pour faire le lien entre URL et Place.


Ce second Mapper permet de faire le lien entre les Places et les Activités notamment avec la méthode getActivity qui retourne une Activité en fonction de la Place passée en paramètre.



Etape 6 : La mise en place du système


L'initialisation se passera dans le point d'entrée de l'application.
Dans un premier temps, on définie notre clientFactory. Rappelez vous dans le fichier de configuration du projet (le fichier .gwt.xml), on avait spécifier le remplacement de la classe ClientFactory par ClientFactoryImpl lors de son instanciation. La clientFactory nous permet d'initialiser le bus d'évènement, le placeController et les différentes vues de l'application.

Ensuite je vous propose de détailler ce petit schémas pour mieux comprendre le fonctionnement du framework.

L'avitityMapper permet de déclencher l'activité en fonction de la place qui a été demandée par l'utilisateur. Entre alors en jeu l'activityManager qui va pouvoir récupérer les places qui ont été diffusées sur l'eventBus afin de les redistribuer à l'activityMapper pour instancier l'activité correspondante.

La déclaration des différents tokenizer de l'application se fait en instanciant notre classe PlaceHistoryMapper qui va définir les liens à effectuer entre URL et Place.

C'est à ce moment là que le PlaceHistoryHandler fait son apparition ! Il va permettre lier de façon bidirectionnelle l'Url et la Place. La navigation va pouvoir alors se faire de deux manières. 
Soit en saisissant l'url dans la barre d'adresse, alors le PlaceHistoryHandler fournira la Place correspondante au PlaceController, soit en appelant la méthode goTo(Place place) du placeController. Le PlaceHistoryHandler se chargera de mettre à jour l'url dans la barre d'adresse. 

C'est ensuite le PlaceController qui prendra la main afin de diffuser la Place sur le bus d'évènement et d'avertir l'utilisateur au préalable dans le cas où il y aurait un autre traitement encore en cours. 

La dernière instruction historyHandler.handleCurrentHistory();  permet d'aller à la place correspondante à l'url saisi par l'utilisateur au démarrage de l'application, si le nom de la place ne correspond à aucune place alors l'application affichera la place par défaut (defaultPlace qui correspond ici à la place de création de produit).

Etape 7 : Test de l'application

Compilons maintenant notre projet afin de passer au test de notre application. Nous allons naviguer entre les deux pages que nous avons créées. La page html de mon projet se nomme ProductMvp.html.

Lancement de la page par défaut :


L'url saisi (127.0.0.1:8888/ProductMvp.html) ne correspond à aucune place, c'est donc la defaultPlace qui est chargée. Comme nous avons défini la place par défaut comme étant de type CreateProductPlace, c'est donc la page de création de produit qui s'affiche.

Complétons le formulaire afin d'ajouter un produit :

Lors du clic sur le bouton ajouter, nous tombons sur la page d'affichage des produits (dont l'url correspond à #showProducts:null)


Maintenant pour revenir à la page de création de produit, on peut soit appuyer sur le bouton précédent du navigateur, soit saisir le nom de l'url (#createProduct:null) dans la barre d'adresse.

 

 







jeudi 18 avril 2013

Introduction au Super Dev Mode avec GWT 2.5

L'article ci-présent correspond à l'élaboration d'un tutoriel sur le super dev mode de GWT.

Le Super Dev Mode a été introduit dans la version 2.5 de GWT. Il est une alternative au bien connu Dev Mode qui permettait de voir les modifications apportées à une application GWT sans avoir besoin de la recompiler. Le point faible étant que le Dev Mode classique est relativement lent, le temps de chargement des pages est considérablement alourdi.  Il nécessite également l'installation d'un plug-in additionnel sur son navigateur, le Super Dev Mode lui n'en a aucunement besoin.

Le Super Dev Mode a donc été créé pour palier ces lacunes, il est aujourd'hui sous le statut experimental et ne doit pas être utilisé en production. Cependant il peut nous être bien utile lors de notre phase de développement. Je vais expliquer par la suite comment l'utiliser avec l'IDE eclipse. Je ne présenterai pas le fonctionnement en détail du Super Dev Mode, si vous souhaitez en connaître d'avantage, je vous invite à lire cette page écrite par google.

Pour la suite, je pars du principe que vous avez un eclipse qui fonctionne avec le plugin gwt et qui utilise la version 2.5 du SDK.

Etape 1 : Création d'un projet lambda

La première étape consiste bien sûr à créer son projet sous eclipse. Pour cela, on va procéder de façon classique : File -> New -> Web Application Project, j’appellerai mon projet TestSuperDevMode. J'obtiens l'arborescence suivante
Pour le moment j'ajoute juste un label à ma page principale au niveau de mon point d'entrée :
A présent il suffit de compiler et de lancer l'application sur le serveur Jetty (où sur un autre serveur si en utilisez un autre).
Petit rappel : clic droit sur votre projet -> Run As -> Web Application ensuite tapez l'url suivante http://127.0.0.1:8888/TestSuperDevMode.html dans votre navigateur préféré.

Le label créé dans le point d'entrée devrait apparaître :
 Etape 2 : Modification du descripteur de module

Ajoutez les deux lignes suivante dans votre fichier .gwt.xml (pour ma part il s'agit du fichier TestSuperDevMode.gwt.xml) et recompilez votre projet.
  <add-linker name="xsiframe"/>
  <set-configuration-property name="devModeRedirectEnabled" value="true"/>


Etape 3 : Création de la configuration d’exécution et lancement du serveur

Pour lancer le super dev mode, nous allons devoir créer un nouvelle configuration d’exécution de notre application. Pour cela, clic droit sur votre projet -> Run as -> Run Configurations...


Sélectionnez maintenant Java Application, faites un clic droit -> New

Dans l'onglet Main :
        - Choisissez un nom qui vous convient, pour ma part je l'appelle TestSDM
        - Pour le projet, sélectionnez celui que vous avez créé, de mon côté il s'agit de TestSuperDevMode
        - Pour la Main class il faut saisir celle-la : com.google.gwt.dev.codeserver.CodeServer

Dans l'onglet Argument :
Il faut saisir le répertoire de vos sources dans le projet, ainsi que le chemin complet de votre descripteur de module (le fichier .gwt.xml). De mon côté j'ai les arguments suivants -src src/ com.sdm.TestSuperDevMode :



Dans l'onglet Classpath :
Il faut rajouter le jar gwt-codeserver.jar au classpath. Il se trouve dans le répertoire de votre SDK. Si vous avez téléchargé le sdk de gwt à partir d'eclipse (lors de l'installation du plugin) alors le jar se trouve dans le répertoire contenant votre eclipse (une petite recherche windows dans le répertoire de'eclipse vous permettra de retrouver facilement le jar)

Sélectionnez User Entries -> Add External JARS... et ajoutez gwt-codeserver.jar



Il vous suffit maintenant de cliquer sur Apply puis Run.
La console devrait afficher le message suivant à la fin


Etape 4 : Lancement au sein du navigateur

Dans un premier temps, tapez l'url suivant dans votre navigateur http://localhost:9876/
Mettez les liens Dev Mode On et Dev Mode Off en marque page pour pouvoir y accéder rapidement

Maintenant c'est presque terminé, il vous suffit de vous rendre sur la page de votre application http://127.0.0.1:8888/TestSuperDevMode.html puis de cliquer sur votre marque page Dev Mode On.

Cliquez sur Compile et votre projet est réactualisé.

Ajoutez maintenant un bouton sur votre application après votre label :

Retournez sur votre navigateur, cliquez sur dev mode on puis compile et les modifications apparaissent :


samedi 19 janvier 2013

Chargement de plusieurs modules au sein d'un même projet GWT

Par défaut lorsqu'on crée un projet GWT, il y a un module (point d'entrée) qui est automatiquement créé et qui porte le même nom que le projet. Comme nous allons le voir par la suite, il est tout à fait possible de définir d'autres modules et de les charger au sein du même projet.

1) Création d'un projet par défaut

Créons dans un premier temps un projet gwt nommé MultiModules.




2) Modification du module créé

Le module qui a été créé par défaut se nomme multimodules, nous allons le renommer en premiermodule

Pour cela il faut ouvrir le fichier MultiModules.gwt.xml, remplacez la balise <module rename-to="multimodules"> par <module rename-to="premiermodule">, nous allons également renommer ce fichier en PremierModule.gwt.xml. On voit dans la balise <entry-point class='com.multi.modules.client.MultiModules'/> que le point d'entrée de l'application correspond à la classe java MultiModules. Renommez cette classe en PremierModule.java et changez la balise entry-point par <entry-point class='com.multi.modules.client.PremierModule/>


Dans le point d'entrée PremierModule.java, effacez tous le code qui a été créé, construisez un widget à votre guise comme dans l'exemple suivant, c'est juste pour voir quelque chose apparaître sur notre navigateur !



Dans le dossier war, on remarque la présence de ces deux fichiers MultiModules.html et MultiModules.css. Renommez-les en PremierModule.html et PremierModule.css.



Sur la page PremierModule.html on va charger le module gwt PremierModule, pour cela il faut modifier le code suivant :



3) Ajout d'un second module

On va maintenant créer le descripteur du module, à savoir le fichier SecondModule.gwt.xml au même endroit que celui du premier module.
Le nom du module sera secondmodule et le point d'entrée sera la classe com.multi.modules.client.SecondModule comme indiqué sur l'image suivante :


Il faut maintenant créer le fichier SecondModule.java dans le package com.multi.modules.client qui se contentera d'afficher un simple label.

Il ne nous reste plus qu'à créer un fichier SecondModule.html dans le dossier war afin de charger le module que l'on vient de créer.


Vous pouvez également modifier la balise <welcome-file> du fichier web.xml afin d'y mettre la page PremierModule.html par exemple. 

4) Compilation et lancement du projet

 Pour compiler votre projet, rien de plus simple, il vous suffit de faire comme à votre habitude un GWT Compile Project... et de sélectionner le module que vous souhaitez compiler.


Après compilation des deux modules, on observe bien leur présence au sein du dossier war. Il ne vous reste plus qu'à démarer votre serveur et afin de déployer le projet dessus.


Rendez vous aux deux adresses suivantes (http://localhost:8080/MultiModules/PremierModule.html et http://localhost:8080/MultiModules/SecondModule.html) pour bien vous rendre compte que vos deux modules ont été chargés.


Le fait d'avoir plusieurs modules peut vous permettre de développer séparément deux fonctionnalités bien différentes au sein d'un même projet.