Mon premier projet GWT

Moritan

Cet article en plusieurs parties est une présentation du framework « Google Web ToolKit ». ♪

1. Présentation générale de la solution GWT

2. Mon premier projet GWT (cet article)

3. Comparaison avec SWING et JSP (à venir) ♪

- Titre : Mon premier projet GWT

- Auteur : Moritan

- Parution : 29 novembre 2006

- Licence : Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Introduction

- I-A. Remerciements

- I-B. Présentation

- II. Mise en place de l'environnement

- II-A. Environnement de développement

- II-B. Environnement de production

- III. Bonjour le monde

- IV. On complique un peu

- IV-A. Un peu de nettoyage

- IV-B. Entrypoint

- IV-C. Création des éléments graphiques

- IV-C-1. Les Panels

- IV-C-2. Les éléments graphiques

- IV-D. Utilisation des styles css

- V. Activation du filtre

- VI. Dialogue avec le serveur

- VI-A. Mise en place d'un service

- VI-B. Exploitation du service

- VII. Déploiement sur Tomcat

- VIII. Conclusion

- IX. Sources

I - Introduction

I-A - Remerciements

Merci à Ricky81 pour sa relecture attentive.

I-B - Présentation

Au cours de cet article, nous verrons comment utiliser et intégrer le framework GWT avec Eclipse.
La présentation se fera par la réalisation d'une application permettant la consultation et le filtre d'une liste de contacts.
Les exemples de cet article se feront sous Windows, mais devraient s'adapter à un autre environnement sans trop de difficultés.

II - Mise en place de l'environnement

II-A - Environnement de développement

Pour commencer, il faut créer le répertoire de base du projet.

Création du répertoire

mkdir demoGWT

On se place dans ce répertoire pour travailler.

cd demoGWT

Création des fichiers projet Eclipse

projectCreator -eclipse demoGWT

Created directory D:\gwt-windows-1.1.10\demoGWT\test
Created file D:\gwt-windows-1.1.10\demoGWT\.project
Created file D:\gwt-windows-1.1.10\demoGWT\.classpath

Création de l'arborescence applicative

applicationCreator -eclipse demoGWT com.developpez.exemple.gwt.client.MonApplication

Created directory D:\gwt-windows-1.1.10\demoGWT\src
Created directory D:\gwt-windows-1.1.10\src\demoGWT\com\developpez\exemple\gwt
Created directory D:\gwt-windows-1.1.10\src\demoGWT\src\com\developpez\exemple\gwt\client
Created directory D:\gwt-windows-1.1.10\src\demoGWT\src\com\developpez\exemple\gwt\public
Created file D:\gwt-windows-1.1.10\src\demoGWT\com\developpez\exemple\gwt\MonApplication.gwt.xml
Created file D:\gwt-windows-1.1.10\src\demoGWT\com\developpez\exemple\gwt\public\MonApplication.html
Created file D:\gwt-windows-1.1.10\src\demoGWT\com\developpez\exemple\gwt\client\MonApplication.java
Created file D:\gwt-windows-1.1.10\demoGWT\MonApplication.launch
Created file D:\gwt-windows-1.1.10\demoGWT\MonApplication-shell.cmd
Created file D:\gwt-windows-1.1.10\demoGWT\MonApplication-compile.cmd

Maintenant que la structure applicative est créée, on pourrait simplement utiliser un éditeur de texte.
Mais utilisons Eclipse, pourquoi se priver de fonctionnalités avancées?
Pour cela, il suffit d'ouvrir Eclipse.
Dans le menu file, choisir import
Choisir Existing Projects into workspace
Cibler le répertoire demoGWT et importer.
On obtient

II-B - Environnement de production

En production, on aura évidemment besoin d'un serveur applicatif ou d conteneur web tel Tomcat.
Pour l'installation et le fonctionnement de Tomcat, je vous laisse consulter ce tutoriel.
Ensuite, il vous suffit de déployer votre application comme n'importe quelle autre application Java.

III - Bonjour le monde

Comme tout bon tutoriel, nous allons commencer par un « Hello World ! ».
Pour cela, lancer le script MonApplication-shell.cmd. Un navigateur va se lancer.
Il vous permettra de voir le rendu de votre application en cours de développement.
La page que vous voyez apparaître devrait ressembler à celle-ci.
Félicitation, vous venez de réussir votre premier « HelloWorld » avec le framework GWT.

IV - On complique un peu

IV-A - Un peu de nettoyage

Le ménage commence par la page HTML qui sert de support à notre application.
On supprime le surplus du HelloWorld pour ne conserver que le titre de la page et les balises propres à GWT.
On obtient alors la page suivante :

MonApplication.html

<head>
<!--                                           -->
<!-- Ici on définit le titre de notre fenêtre   -->
<!--                                           -->
<title>MonApplication GWT</title>

<!--                                           -->
<!-- La balise suivante permet de faire le     -->
<!-- lien entre la page html et le code GWT    -->
<!--                                           -->
<meta name='gwt:module' content='com.developpez.exemple.gwt.MonApplication'>
</head>

<body>
<!--                                            -->
<!-- L'inclusion de gwt.js est obligatoire.     -->
<!-- Elle placer ici pour des raisons de        -->
<!-- Performances.                              -->
<!--                                            -->
<script langage="javascript" src="gwt.js"></script>

<!-- La balise suivante est optionnelle, elle gère l'historique des pages -->
<iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
</body>
</html>

IV-B - Entrypoint

Lorsque l'on développe une application AWT/SWING, on utilise une classe avec une méthode main pour lancer notre application.
Étant donné que les classes Java que nous allons écrire seront converties en code Javascript, l'utilisation d'une méthode main est impossible.
Dans notre cas, notre classe de lancement sera la page html de notre application et notre méthode main la balise meta.

Déclaration du module

<meta name='gwt:module' content='com.developpez.exemple.gwt.MonApplication'>

L'attribut content de la balise permet de faire le lien vers le fichier de configuration de notre module GWT.

MonApplication.gwt.xml

<module>

<!-- Inherit the core Web Toolkit stuff.                  -->
<inherits name='com.google.gwt.user.User'/>

<!-- Specify the app entry point class.                   -->
<entry-point class='com.developpez.exemple.gwt.client.MonApplication'/>
</module>

La balise entry-point permet de spécifier quelle sera la classe principale de notre application.
Cette classe doit implémenter l'interface Entrypoint.

Il est intéressant aussi d'implémenter l'interface WindowResizeListener.
Cette interface permet de recevoir les informations liées à la taille de la fenêtre du navigateur et donc de redimensionner notre application proportionnellement à celle-ci.
Le code de la classe devrait ressembler à ça:

MonApplication.java

package com.developpez.exemple.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowResizeListener;

/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class MonApplication implements EntryPoint , WindowResizeListener {

/**
* This is the entry point method.
*/
public void onModuleLoad() {

// Hook the window resize event, so that we can adjust the UI.
Window.addWindowResizeListener(this);

// Get rid of scrollbars, and clear out the window's built-in margin,
// because we want to take advantage of the entire client area.
Window.enableScrolling(false);
Window.setMargin("10px");

}

public void onWindowResized(int width, int height) {
// TODO Auto-generated method stub
}
}

On obtient donc une application GWT pleinement fonctionnelle, mais qui ne fait rien.

IV-C - Création des éléments graphiques

Les classes des éléments graphiques de GWT reprennent les noms et comportements que l'on a l'habitude de manipuler quand on réalise une application AWT/SWING. Avec GWT, nous manipulerons donc des Panels et autres Label et TextBox.

IV-C-1 - Les Panels

Les panels se déclinent en plusieurs classes qui ont chacune un comportement spécifique.

Dans cette mise en application, nous utiliserons les panels suivants :

- DockPanel

- HorizontalPanel

- VerticalPanel

Le DockPanel servira de base d'accueil pour notre application. Il contiendra dans sa partie nord un VerticalPanel et dans sa partie Centre un HorizontalPanel.
Le verticalPanel contiendra les champs de recherche et le VerticalPanel présentera les résultats.

On commence par ajouter le DockPanel à l'application. Ce qui nous donne pour la classe MonApplication.java le code suivant :

MonApplication.java

package com.developpez.exemple.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowResizeListener;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.RootPanel;

/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class MonApplication implements EntryPoint , WindowResizeListener {

private DockPanel  outer = new DockPanel();

/**
* This is the entry point method.
*/
public void onModuleLoad() {

outer.setWidth("100%");
outer.setHeight("100%");
// Hook the window resize event, so that we can adjust the UI.
Window.addWindowResizeListener(this);

// Get rid of scrollbars, and clear out the window's built-in margin,
// because we want to take advantage of the entire client area.
Window.enableScrolling(false);
Window.setMargin("10px");

RootPanel.get().add(outer);
}

public void onWindowResized(int width, int height) {
// TODO Auto-generated method stub
}
}

Nous allons créer deux panels, RechechePanel et ContactsPanel qui étendent respectivement HorizontalPanel et VerticalPanel.
Le fait de ne pas utiliser directement les panels génériques nous permet d'alléger la classe MonApplication et surtout nous permettra d'avoir un code lisible.
Pour la classe ContactsPanel

et pour la classe RecherchePanel

Il ne nous reste plus qu'à les insérer dans le DockPanel.

MonApplication.java

package com.developpez.exemple.gwt.client;

import com.developpez.exemple.gwt.client.panel.ContactsPanel;
import com.developpez.exemple.gwt.client.panel.RecherchePanel;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowResizeListener;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.RootPanel;

/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class MonApplication implements EntryPoint , WindowResizeListener {

private DockPanel  outer = new DockPanel();

/**
* This is the entry point method.
*/
public void onModuleLoad() {

HorizontalPanel recherchePanel =new RecherchePanel();
VerticalPanel contactsPanel =new ContactsPanel();

outer.setWidth("100%");
outer.setHeight("100%");
// Hook the window resize event, so that we can adjust the UI.
Window.addWindowResizeListener(this);

// Get rid of scrollbars, and clear out the window's built-in margin,
// because we want to take advantage of the entire client area.
Window.enableScrolling(false);
Window.setMargin("10px");

outer.add(recherchePanel, DockPanel.NORTH);
outer.add(contactsPanel, DockPanel.CENTER);

RootPanel.get().add(outer);
}

public void onWindowResized(int width, int height) {
// TODO Auto-generated method stub
}
}

IV-C-2 - Les éléments graphiques

Maintenant que la structure d'accueil de notre application est prête, nous allons pouvoir mettre un peu de contenu.
En plus des composants graphiques de base, nous utiliserons aussi la classe Composite.
La classe Composite permet d'envelopper un ou plusieurs autres éléments graphiques. Ce groupe de composant agit alors comme un seul et même élément.
Nous allons commencer par créer une classe Texte qui héritera de Composite. Cette classe nous permettra de gérer les zones de saisie utilisateur.
Elle gèrera le libellé et la zone de saisie. On utilisera la classe Label pour le libellé et la classe TextBox pour la zone de saisie.

Texte.java

package com.developpez.exemple.gwt.client.panel;

import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;

public class Texte extends Composite {

private Label label = new Label();
private TextBox textBox = new TextBox();

public Texte() {
HorizontalPanel panel =new HorizontalPanel();
panel.add(label);
panel.add(textBox);
initWidget(panel);
}

public void setLabel(String texte){
label.setText(texte+" : ");
}

public void setTexte(String texte){
textBox.setText(texte);
}

public String getTexte(){
return textBox.getText();
}
}

Ensuite, il suffit d'ajouter ce composant à notre RecherchePanel, pour avoir nos zones de filtre.

RecherchePanel.java

package com.developpez.exemple.gwt.client.panel;

import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HorizontalPanel;

public class RecherchePanel extends HorizontalPanel {

public RecherchePanel(){
this.setTitle("Recherche");
this.setWidth("100%");
Texte tb = new Texte();
tb.setLabel("Nom");
add(tb);

Texte tb2 = new Texte();
tb2.setLabel("Prénom");
add(tb2);
}
}

Avant de compléter notre application, nous allons créer une classe utilitaire Contact.
Ce sera la représentation objet des différentes personnes de notre application.
Afin d'avoir quelques éléments graphiques pour la démonstration, on rajoute une méthode getAllContacts() sur la classe.
Cette méthode va nous renvoyer un tableau de contacts à afficher, le temps que l'on implémente le service d'accès à une base de données.
À terme cette méthode disparaîtra.

Les différents contacts connus seront affichés sous forme de tableau.
Nous utiliserons pour cela la classe FlexTable. Le contenu d'un tableau se positionne par ses coordonnées.
Nous utiliserons la première ligne pour afficher les entêtes de tableau.

ContactsPanel.java

package com.developpez.exemple.gwt.client.panel;

import com.developpez.exemple.gwt.client.util.Contact;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.VerticalPanel;

public class ContactsPanel extends VerticalPanel {

private FlexTable t = new FlexTable();

public ContactsPanel() {

t.setTitle("Contacts");
t.setText(0, 0,"Nom");
t.setText(0, 1,"Prénom");
t.setText(0, 2,"Email");
t.setText(0, 3,"Tel");
t.setWidth("100%");
t.setCellSpacing(2);

for (int i = 0; i < Contact.getAllContacts().length; ++i)
addContact(Contact.getAllContacts()[i],i+1);

this.add(t);
this.setWidth("100%");
}

private void addContact(Contact contact,int i) {
t.setText(i, 0,contact.getNom());
t.setText(i, 1,contact.getPrenom());
t.setText(i, 2,contact.getEmail());
t.setText(i, 3,contact.getTel());
}

}

Une fois compilée, notre application donne ça.

IV-D - Utilisation des styles css

Maintenant que l'on a tous nos éléments, on va leur donner un aspect un peu plus convivial.
Pour cela, on va utiliser un fichier css.
On ajoute donc un fichier MonApplication.css au répertoire public de notre arborescence.
Afin que ces styles soient disponibles dans les classes GWT, il faut éditer le fichier MonApplication.gwt.xml, et déclarer le fichier css dans notre module.
Pour cela il suffit d'ajouter la ligne suivante dans la déclaration du module.

MonApplication.gwt.xml

<stylesheet src='MonApplication.css'/>

Les différentes classes graphiques du framework GWT héritent toutes de la classe UIObject.

Cette classe nous donne la méthode setStyleName sur tous nos composants et nous permet donc d'assigner un style précis à chacun de nos objets.
Par exemple, pour changer le style du RecherchePanel, il nous suffit d'ajouter le code suivant à son constructeur pour en modifier l'apparence.

this.setStyleName("gwt-RecherchePanel");

Bien évidemment le style gwt-RecherchePanel a été défini dans le fichier css.
Par convention, les noms de style sont composés du nom du module tiret l'élément graphique auquel il s'applique.
En appliquant nos styles à nos différents éléments, on obtient ça.
Mais bien sûr, nous n'avons encore aucun composant actif dans notre page.

V - Activation du filtre

Nous allons commencer à mettre un peu d'intelligence dans notre application. Pour cela, nous allons écouter les événements sur les zones de saisie et modifier le contenu du tableau en conséquence.
On va commencer par se donner des accesseurs à nos Panels en leur appliquant le pattern Singleton. Puis on va rajouter les méthodes getNom et getPrenom sur le RecherchePanel et la méthode filtre sur le contactsPanel.
Les méthodes getNom et getPrenom retourneront les valeurs saisies tandis que filtre modifiera le contenu du tableau de résultats. Ensuite, on va écouter les événements clavier qui surviennent dans nos zones de saisie en ajoutant le code suivant à notre classe Texte.

Texte.java

textBox.addKeyboardListener(new KeyboardListener() {
public void onKeyDown(Widget sender, char keyCode, int modifiers) {}
public void onKeyPress(Widget sender, char keyCode, int modifiers) {}
public void onKeyUp(Widget sender, char keyCode, int modifiers) {
ContactsPanel.instance.filtre();
}
});

On remplit la méthode filtre.

ContactsPanel.java

public void filtre(){
String nom = RecherchePanel.instance.getNom();
String prenom = RecherchePanel.instance.getPrenom();
for (int i = t.getRowCount()-1; i >0 ; i--) {
t.removeRow(1);
}
int next =1;
for (int i = 0; i < Contact.getAllContacts().length; ++i){
Contact contact =Contact.getAllContacts()[i];

if (contact.getNom().toUpperCase().startsWith(nom.toUpperCase())
&& contact.getPrenom().toUpperCase().startsWith(prenom.toUpperCase())) {
addContact(contact,next);
next++;
}
}
}

Une fois nos classes modifiées, on obtient ça.

VI - Dialogue avec le serveur

L'application semble fonctionnelle, mais la liste des contacts reste encore contenue dans la classe Contact, ce qui est peu satisfaisant.
Nous allons donc dans un premier temps créer un package server qui recevra les classes distantes et supprimer la méthode static getAllContact de la classe Contact.

VI-A - Mise en place d'un service

Le framework GWT dispose de sa propre solution de communication entre le client et le serveur, basée sur le principe d'appel de procédure distante (RCP).
Le serveur et le client dialogue dialoguent en Asynchrone (vous savez le premier A de AJAX ).
Il faut donc deux flux de communication, le premier direct vers le serveur pour émettre les requêtes, le second asynchrone pour recevoir les réponses du serveur.
Le flux ascendant est modélisé par une interface qui doit étendre l'interface RemoteService.
On définit dessus les différentes méthodes que l'on souhaite obtenir du serveur.
On va donc créer une interface ContactService.

Afin que le client puisse recevoir la réponse du serveur, on va créer une seconde interface ContactServiceAsync.
Cette interface doit répondre à plusieurs critères :

- le nom de l'interface doit être le même que celui pour le flux ascendant avec à la fin « Async » ;

- toutes ses méthodes sont de type void ;

- le nombre et le nom de ses méthodes sont identiques à ceux pour le flux ascendant ;

- chacune de ses méthodes doit prendre les mêmes paramètres que pour le flux montant plus un paramètre supplémentaire de la classe AsyncCallback.

C'est au travers de l'objet AsyncCallback que le serveur retournera sa réponse au client.

Maintenant que nous avons nos flux de communication avec le serveur, il nous faut ensuite créer une servlet sur le serveur capable de recevoir notre demande et d'y répondre.
Pour cela dans le package server, on va créer une classe ContactServiceImpl qui étendra RemoteServiceServlet et implémentera notre interface ContactService.
Hériter de la classe RemoteServiceServlet permet de faire abstraction de l'élaboration de l'objet AsyncCallback.
Le mécanisme RCP fonctionne comme un miroir, les méthodes appelées côté client seront exécutées côté serveur sur notre servlet.
C'est donc dans le corps des méthodes issues de l'implémentation de notre interface que l'on placera le code que l'on souhaite exécuter sur le serveur.
Notre classe servlet elle aussi doit répondre à plusieurs critères :

- le nom de la servlet doit être le même que celui pour le flux ascendant avec à la fin « Impl » ;

- la servlet doit implémenter RemoteServiceServlet ;

- la servlet doit implémenter l'interface du service ascendant.

ContactServiceImpl.java

package com.developpez.exemple.gwt.server;

import com.developpez.exemple.gwt.client.ContactService;
import com.developpez.exemple.gwt.client.util.Contact;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class ContactServiceImpl extends RemoteServiceServlet implements
ContactService {

public Contact[] getAllContact() {
return ContactData.getAllContacts();
}
}

La classe ContactData est une classe utilitaire chargée de fournir la liste des contacts.
La méthode d'obtention des contacts importe peu, jdbc, Hibernate, fichier XML, LDAP, libre à vous d'utiliser celle qu'il vous plait.

Dernière action pour finaliser la création de notre service, la déclaration dans le fichier MonApplication.gwt.xml.
Pour cela, il suffit d'ajouter la ligne suivante à la déclaration du module.

MonApplication.gwt.xml

<servlet path="/contactService" class="com.developpez.exemple.gwt.server.ContactServiceImpl"/>

VI-B - Exploitation du service

Maintenant que le service est créé, nous allons pouvoir l'utiliser. On va l'appeler directement dans notre classe ContactsPanel.
On rajoute une méthode getAllContacts sur notre classe, qui après quelques modifications ressemble à ça :

ContactsPanel.java

package com.developpez.exemple.gwt.client.panel;

import com.developpez.exemple.gwt.client.ContactService;
import com.developpez.exemple.gwt.client.ContactServiceAsync;
import com.developpez.exemple.gwt.client.util.Contact;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.VerticalPanel;

public class ContactsPanel extends VerticalPanel {

private FlexTable t = new FlexTable();
private Contact[] contacts;

public static ContactsPanel instance;
public ContactsPanel() {
getAllContacts();
t.setTitle("Contacts");
t.setText(0, 0,"Nom");
t.setText(0, 1,"Prénom");
t.setText(0, 2,"Email");
t.setText(0, 3,"Tel");
t.setWidth("100%");
t.setCellSpacing(2);

for (int i = 0; i < 4; i++) {
t.getCellFormatter().addStyleName(0, i, "contact-ContactPanel");
}

this.add(t);
this.setWidth("100%");
instance=this;
}

private void addContact(Contact contact,int i) {
t.setText(i, 0,contact.getNom());
t.setText(i, 1,contact.getPrenom());
t.setText(i, 2,contact.getEmail());
t.setText(i, 3,contact.getTel());

for (int j = 0; j < 4; j++) {
if(i%2==0)
t.getCellFormatter().addStyleName(i, j, "contact-ContactPanel-line1");
else
t.getCellFormatter().addStyleName(i, j, "contact-ContactPanel-line2");
}
}

public void filtre(){
String nom = RecherchePanel.instance.getNom();
String prenom = RecherchePanel.instance.getPrenom();
for (int i = t.getRowCount()-1; i >0 ; i--) {
t.removeRow(1);
}
int next =1;
for (int i = 0; i < contacts.length; ++i){
Contact contact =contacts[i];

if (contact.getNom().toUpperCase().startsWith(nom.toUpperCase())
&& contact.getPrenom().toUpperCase().startsWith(prenom.toUpperCase())) {
addContact(contact,next);
next++;
}
}
}

private void getAllContacts(){
contacts = new Contact[0];
//         define the service you want to call
ContactServiceAsync svc =
(ContactServiceAsync) GWT.create(ContactService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) svc;
String moduleRelativeURL = GWT.getModuleBaseURL() + "contactService";
endpoint.setServiceEntryPoint(moduleRelativeURL);

AsyncCallback callback = new AsyncCallback() {
public void onSuccess (Object result)
{
contacts = (Contact[]) result;
filtre();
}

public void onFailure (Throwable ex)
{
contacts = new Contact[0];
filtre();
}
};

// execute the service
svc.getAllContact(callback);

}
}

Si on détaille un peu la méthode getAllContacts() :

Déclaration du service que l'on souhaite appeler

ContactServiceAsync svc = (ContactServiceAsync) GWT.create(ContactService.class);

On définit ou trouver le service, son url

ServiceDefTarget endpoint = (ServiceDefTarget) svc;
String moduleRelativeURL = GWT.getModuleBaseURL() + "contactService";
endpoint.setServiceEntryPoint(moduleRelativeURL);

On définit le comportement de retour

AsyncCallback callback = new AsyncCallback() {
public void onSuccess (Object result)
{
contacts = (Contact[]) result;
filtre();
}

public void onFailure (Throwable ex)
{
contacts = new Contact[0];
filtre();
}
};

J'ai utilisé une classe interne, car le comportement voulu était très simple, mais dans un cadre plus complexe, il serait souhaitable d'utiliser une classe externe.

on appelle le service

svc.getAllContact(callback);

Dans notre exemple la mise place et l'appel du service sont fait dans la même méthode, car l'appel du service n'est fait qu'une seule fois au chargement de la page.
Néanmoins, on peut très bien placer notre service sur un slot et y faire ensuite appel en différente occasion.
Chaque appel à un service génère un aller-retour serveur. Il faut donc prévoir les temps de latence possibles.
De plus il est intéressant comme dans notre application, de stocker en cache côté client un certain nombre d'informations, afin d'alléger le nombre de requêtes au serveur.
Typiquement dans notre exemple, il était inutile de faire un aller/retour serveur pour le filtre (à la volumétrie près).
Notre application est maintenant terminée et nous obtenons la liste des contacts d'un serveur distant.

VII - Déploiement sur Tomcat

La structure standard d'une application sous Tomcat est la suivante :

La structure de notre projet sous Eclipse est la suivante :

Pour déployer dans une arborescence tomcat il faut :

- créer une webapp avec le nom de notre service ;

- copier le contenu du répertoire bin dans le répertoire classes de notre webapps ;

- placer le jar gwt-servlet.jar dans le répertoire lib de notre application ou dans le répertoire common/lib à la racine de tomcat ;

- copier le contenu du répertoire www à la racine de notre webapp.

Dans notre exemple le répertoire www contient un sous répertoire com.developpez.exemple.gwt.MonApplication.

Lors d'un déploiement, il est possible de ne pas conserver ce sous-répertoire en ne copiant que son contenu à la racine de la webapps.

Cela permet de simplifier l'adresse de notre application.

Enfin, il faut déclarer notre servlet. Il faut donc placer un fichier web.xml dans le répertoire WEB-INF.

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Copyright 2004 The Apache Software Foundation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">

<display-name>GWT</display-name>
<description>
GWT example
</description>

<servlet>
<servlet-name>ContactService</servlet-name>
<servlet-class>com.developpez.exemple.gwt.server.ContactServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ContactService</servlet-name>
<url-pattern>/contactService</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>MonApplication.html</welcome-file>
</welcome-file-list>

</web-app>

VIII - Conclusion

Au cours de cet article, nous avons vu tout l'intérêt du framework GWT.
Nous avons réalisé une application web interactive, dialoguant avec un serveur, en faisant abstraction de tout autre langage à part Java.
De plus le code généré est compatible avec la majorité des navigateurs.
Le framework GWT apporte au développement WEB tous les avantages du développement d'un client lourd, en permettant notamment la modélisation des classes gérant l'interface graphique.

IX - Sources

Fichiers sources de la première partie du tutoriel ici ou ici.
Fichiers sources du chapitre VI ici ou ici.