I. Versions utilisées▲
GWT a connu pas mal d'évolution depuis sa création, et continue d'évoluer. À partir de la version 2.6, GWT utilise par défaut Java 7. Il reste possible de choisir la version 6 de Java, mais il n'y a aucune raison pour ceci dans notre tutoriel. Voici donc les versions des outils utilisées dans cette série :
- Java 1.7
- Maven 3.1.0
- Eclipse Kepler 4.3
- GWT 2.6
Je ne détaillerai pas l'installation des 3 premiers, qui font normalement partie des indispensables de tout développeur Java normalement constitué, surtout le premier... Je me concentrerai exclusivement sur GWT.
II. Installation du plugin Google pour Eclipse▲
Ceci est extrêmement simple. Dans Eclipse, ouvrez l'Eclipse Marketplace, et cherchez google :
Cliquez sur le bouton Install, et suivez les instructions. Ce plugin concerne tous les outils et frameworks de Google, pas seulement GWT. Vous pouvez vous contenter de ceux qui vous intéressent, ou les installer tous si vous préférez. Pour GWT, je vous conseille d'installer au moins :
- Google Plugin for Eclipse 4.3 : un plugin pour travailler avec l'ensemble des produits Google dans Eclipse.
- Google Web Toolkit SDK 2.6.0 : Le framework GWT proprement dit.
- GWT Designer Core
- GWT Designer Editor
- GWT Designer GPE
Les trois derniers sont des éditeurs graphiques, avec lesquels il sera possible de dessiner notre interface.
Et voilà, nous pouvons commencer à travailler.
III. Premier projet GWT▲
III-A. Création dans Eclipse▲
Avant d'utiliser Maven, nous allons créer un projet depuis Eclipse, ce qui nous permettra d'inspecter l'organisation d'un projet GWT, qui diffère sensiblement de la structure classique des projets Maven.
La création d'un projet GWT se fait avec CTRL + N, et en choisissant Web Application Project dans la fenêtre ci dessous :
L'étape suivante consiste à entrer le nom du projet et le package à utiliser.
Nous allons créer un projet de gestion de bibliothèque, nous l'appellerons donc atabib et le package sera fr.atatorus.atabib. Mais libre à vous de renommer tout cela si vous le souhaitez.
Décochez la case « Use Google App Engine », et laissez cochée la case « Generate project sample code ». Cliquez ensuite sur « Finish » pour obtenir le squelette d'une application GWT pleinement fonctionnelle. Exécutons la donc.
III-B. Exécution de l'application▲
Cliquons sur l'icône exécution dans la barre d'outils d'Eclipse, et choisissons atabib :
L'application se lance en mode développement. Dans Eclipse, la vue correspondante s'est ouverte :
Dans ce mode, l'application tourne sur un serveur Jetty compris dans le SDK, et nécessite un plugin pour fonctionner. En effet, à ce moment, notre application n'est pas compilée. Il s'agit toujours de Java qui s'exécute, ce qui permet de débogguer l'application comme n'importe quel programme Java. Pour s'exécuter dans le navigateur, nous aurons besoin d'un plugin, qui simule le fonctionnement de l'application dans le navigateur en javascript. On pourrait dire qu'il agit comme un compilateur JIT (Just In Time). Ce plugin s'installera automatiquement quand vous vous connecterez à votre application.
Comme demandé, double-cliquez sur l'URL. Pour la première utilisation du mode développement de GWT, un message vous demande donc d'installer le plugin dans votre navigateur :
Au moment où j'écris cet article, le plugin n'est plus compatible avec Firefox 27 sur Linux, mais tourne très bien dans Chromium. Une fois le plugin installé, l'application se contente de nous demander notre nom et affiche une boite de dialogue avec diverses informations sur notre système :
Voilà, nous avons notre première application GWT. Elle n'est pas très évoluée, mais ne nous a pas demandé beaucoup d'efforts non plus. Elle est cependant suffisante pour explorer la structure d'une application GWT.
III-C. Structure d'une application GWT▲
Voici la structure du projet atabib à la création :
On remarque 3 packages dans le répertoire src :
- client : c'est dans ce package qu'on place les classes Java qui seront compilées en Javascript. Ces classes sont soumises à certaines restrictions, toutes les classes du JDK ne sont pas traduisibles en Javascript. Le plugin a déjà généré pour nous une classe et deux interfaces :
- Atabib : il s'agit du point d'entrée de notre application. Elle doit implémenter l'interface com.google.gwt.core.client.EntryPoint.
- GreetingService : c'est l'interface du service de l'application.
- GreetingServiceAsync : c'est l'interface asynchrone correspondant à notre service. Elle dépend directement de l'interface du service. Nous verrons quand nous attaquerons les communications avec le serveur comment tout ceci fonctionne.
- server : ce package contient les classes s'exécutant sur le serveur, qui ne seront pas traduites en Javascript. La classe GreetingServiceImpl est l'implémentation du service coté serveur.
- shared : un autre package à traduire en Javascript. Dans notre squelette d'application, il ne contient qu'un simple validateur de champ qui s'assure que le nom n'est pas vide.
Cette organisation est celle par défaut, mais il est possible de la changer, en modifiant le fichier Atabib.gwt.xml. C'est dans ce fichier qu'on trouve, entre autre, la définition du point d'entrée de l'application, ainsi que la liste des packages à traduire en Javascript, client et shared dans notre cas.
Le répertoire war est le répertoire qui contiendra le site web lui-même. On y trouve la page de notre site et sa feuille de style. Je dis bien la page de notre site, car GWT fonctionne selon le principe de la page unique. Le fichier web.xml contient la description de notre application et des servlets comme toute application J2EE classique.
Vous pouvez jeter un coup d'œil à ces fichiers, je ne vais pas m'étendre davantage dessus pour l'instant. Mais rassurez-vous, nous y reviendrons bientôt plus en détail.
Vous avez déjà sans doute remarqué que l'organisation du projet ne correspond pas du tout au schéma type d'une application Maven, pourtant un outil des plus utiles pour le développement Java. Mais il est bien entendu possible d'utiliser Maven avec GWT grâce au plugin approprié.
IV. GWT et Maven▲
IV-A. Création du projet▲
Le plugin Maven d'Eclipse ne propose pas d'artefact pour créer un projet GWT, nous allons donc devoir passer par la ligne de commande :
mvn archetype:generate -DarchetypeGroupId=org.codehaus.mojo -DarchetypeArtifactId=gwt-maven-plugin -DarchetypeVersion=2.6.0
Maven va vous poser quelques questions, concernant le groupId, l'artifactID, … Mettez ce que vous voulez, il ne s'agit que d'identifier votre projet, pas de configurer quoi que ce soit pour l'instant. Voici mes réponses :
- groupId : fr.atatorus,
- artifactId : atabib,
- version : 1.0-SNAPSHOT,
- package : fr.atatorus.atabib,
- module : Atabib.
Notre projet créé, importons le dans Eclipse. Sélectionnez le menu « File > Import », et choisissez l'assistant « Existing Maven Project » :
Cliquez sur « Next> », puis chercher le répertoire du projet créé :
Recliquez sur « Next > » :
L'assistant nous signale une erreur. En réalité, je pense qu'il s'agit d'un bug du plugin Maven d'Eclipse, d'autant plus qu'il me semble l'avoir lu quelque part, mais c'est à confirmer. Ceci ne nous empêche en rien de continuer. Cliquez sur « Finish », et notre projet apparait dans l'espace de travail. Nous allons pouvoir l'examiner.
IV-B. Structure du projet▲
Déplions l'arborescence, et examinons là plus en détails pour voir les différences par rapport à notre projet précédent :
Nous retrouvons la structure type des projets Maven, avec src/main/java, src/main/ressources, etc. Nous remarquons également que la classe GreetingServiceAsync a migré : elle se retrouve dans target/generated-sources/gwt. Nous avons dit précédemment que son code dépend de l'interface du service. Le plugin est donc tout à fait capable de la générer à notre place.
Il y a cependant une erreur de syntaxe dans la classe Atabib. Ouvrez là, et vous verrez pourquoi Eclipse se plaint : il ne connait pas la classe Messages. Normal, elle n'existe pas encore.
Comme pour la classe GreetingServiceAsync, il s'agit d'une classe générée, dont le rôle consiste à gérer les messages de notre application. On retrouve les messages dans les fichiers Messages.properties et Messages_fr.properties. Nous y reviendrons quand on parlera de l'internationalisation de notre application. Pour l'instant, nous devons corriger cet oubli du plugin.Dans la console, tapez
mvn generate-sources
Cette commande va générer toutes les classes nécessaires à notre projet, c'est à dire GreetingServiceAsync et Messages. Rafraichissez le projet Eclipse, et tout rentrera dans l'ordre : la classe Messages apparait à coté de sa consœur. Parmi les autres différences avec le projet précédent, nous possédons maintenant une classe de test.
IV-C. Exécution du projet▲
Avant de lancer notre projet, nous devons intervenir dans le pom, pour dire à Maven d'utiliser Java 7 :
<
plugin
>
<
groupId
>
org.apache.maven.plugins<
/
groupId
>
<
artifactId
>
maven-compiler-plugin<
/
artifactId
>
<
version
>
2.3.2<
/
version
>
<
configuration
>
<
source
>
1.7<
/
source
>
<
target
>
1.7<
/
target
>
<
/
configuration
>
<
/
plugin
>
Maintenant que c'est fait, nous pouvons exécuter notre projet. Ça peur se faire à la ligne de commande avec Maven :
mvn gwt:run
Nous voyons surgir une fenêtre du mode développement, comme au bon vieux temps de GWT 1.x :
Cliquez sur « Launch Default Browser », et notre application s'exécutera à l'identique de la précédente.
Bien entendu, il nous est possible de lancer l'application directement depuis Eclipse, avec le menu contextuel « Run As > Web Application ». Cette fois, la fenêtre du mode développement est remplacée par un onglet dans Eclipse.
Note : si vous avez un message d'erreur dans la console à propos du module GWTJUnit qu'Eclipse n'arrive pas à charger, c'est sans doute une réminiscence de notre ancien projet. Aller dans « Run Configurations... », et effacer toutes les anciennes configurations. Après, tout devrait rentrer dans l'ordre.
V. Anatomie d'un projet GWT▲
Maintenant que nous savons créer et exécuter une application GWT, nous pouvons inspecter le mécanisme un peu plus en détail. Nous allons partir de notre projet Maven pour ceci.
GWT produisant des applications Web, commençons donc naturellement pas examiner à quoi ressemble notre page.
V-A. La page HTML▲
Je l'ai déjà dit, un projet GWT ne contient qu'une seule page HTML, même s'il peut être utile de découper de très gros projet en plusieurs modules, chacun ayant sa propre page. Mais en tout état de cause, une seule page nous suffit. Examinons donc le code de la page de notre application, débarrassé des commentaires superflus :
<!
doctype
html
>
<
html>
<
head>
<
meta
http-equiv=
"
content-type
"
content=
"
text/html;
charset=UTF-8
"
>
<
link
type=
"
text/css
"
rel=
"
stylesheet
"
href=
"
Atabib.css
"
>
<
title>
Web Application Starter Project<
/
title>
<script
type="text/
javascript"
language="javascript"
src="Atabib/
Atabib.nocache.js">
</script>
<
/
head>
<
body>
<!--
OPTIONAL:
include
this
if
you
want
history
support
-->
<
iframe
src=
"
javascript:''
"
id=
"
__gwt_historyFrame
"
tabIndex=
'
-1
'
style=
"
position:absolute;width:0;height:0;border:0
"
>
<
/
iframe>
<!--
RECOMMENDED
if
your
web
app
will
not
function
without
JavaScript
enabled
-->
<
noscript>
<
div
style=
"
width:
22em;
position:
absolute;
left:
50%;
margin-left:
-11em;
color:
red;
background-color:
white;
border:
1px
solid
red;
padding:
4px;
font-family:
sans-serif
"
>
Your web browser must have JavaScript enabled
in order for this application to display correctly.
<
/
div>
<
/
noscript>
<
h1>
Web Application Starter Project<
/
h1>
<
table
align=
"
center
"
>
<
tr>
<
td
colspan=
"
2
"
style=
"
font-weight:bold;
"
>
Please enter your name:<
/
td>
<
/
tr>
<
tr>
<
td
id=
"
nameFieldContainer
"
>
<
/
td>
<
td
id=
"
sendButtonContainer
"
>
<
/
td>
<
/
tr>
<
tr>
<
td
colspan=
"
2
"
style=
"
color:red;
"
id=
"
errorLabelContainer
"
>
<
/
td>
<
/
tr>
<
/
table>
<
/
body>
<
/
html>
Dans l'en-tête, on retrouve le lien vers un script Atabib.nocache.js : Il s'agit du script qui produit notre interface utilisateur. Il crée et détruit des objets, qu'il attache et détache de l'arbre DOM de la page. Nous ne voyons donc pas notre champ de texte ou notre bouton. Pour les voir, nous avons besoin d'inspecter le DOM de la page. Voici celui de notre table :
On y voit bien apparaitre notre champ texte et notre bouton, ainsi qu'un label pour les erreurs. Nous les retrouvons tous dans notre code Java :
RootPanel.get
("
nameFieldContainer
"
).add
(nameField);
RootPanel.get
("
sendButtonContainer
"
).add
(sendButton);
RootPanel.get
("
errorLabelContainer
"
).add
(errorLabel);
Nous obtenons nos éléments containers, en l'occurrence les cellules de la table, via le RootPanel, et nous ajoutons simplement nos éléments à l'intérieur. Il est tout à fait possible de se passer de tout élément dans notre page. On peut très bien créer une interface à l'intérieur d'une page vide.
Notez aussi une petite iframe pour gérer l'historique : puisqu'on est dans une application mono-page, cliquer sur le bouton retour en arrière nous la fait quitter. Il est donc primordial de mettre en place un mécanisme qui s'en charge. Ça aussi nous le verrons.
Et maintenant, au tour de notre code Java d'être examiné.
V-B. Code Java▲
Notre code Java se répartit en 2 classes et une interface :
- Atabib : il s'agit de la principale classe de l'application.
- GreetingService : interface de notre service, elle est annotée ce qui permet au compilateur GWT de mettre en place toute la plomberie nécessaire à la communication avec le serveur en Ajax.
- FieldVerifier : Pour contrôler la validité de nos données.
Nous n'examinerons que la première. L'interface du service sera l'objet d'un article prochain à elle toute seule. Quant à la dernière, son code n'appelle aucun commentaire tellement il est basique.
Examinons donc notre classe Atabib. Il s'agit du point d'entrée de notre application, ainsi que le signale l'interface EntryPoint :
public
class
Atabib implements
EntryPoint {
...
public
void
onModuleLoad
() {
...
}
}
Une fois la page chargée, le code qui s'exécute est celui de la méthode onModuleLoad(). Le code de notre méthode peut se décomposer en deux parties.
On commence par créer nos widgets (lignes 2 à 5) et les mettre en place (lignes 12 à 14). On crée aussi une boite de dialogue qui affichera la réponse du serveur :
public
void
onModuleLoad
(){
final
Button sendButton=
new
Button
(messages.sendButton
());final
TextBox nameField=
new
TextBox
();nameField.
setText
(messages.nameField
());final
Label errorLabel=
new
Label
();//
We
can
add
style
names
to
widgets
sendButton.
addStyleName
("
sendButton
"
);//
Add
the
nameField
and
sendButton
to
the
RootPanel
//
Use
RootPanel.get()
to
get
the
entire
body
element
RootPanel.
get
("
nameFieldContainer
"
).add
(nameField);RootPanel.
get
("
sendButtonContainer
"
).add
(sendButton);RootPanel.
get
("
errorLabelContainer
"
).add
(errorLabel);//
Focus
the
cursor
on
the
name
field
when
the
app
loads
nameField.
setFocus
(true
);nameField.
selectAll
();//
Create
the
popup
dialog
box
final
DialogBox dialogBox=
new
DialogBox
();dialogBox.
setText
("
Remote
Procedure
Call
"
);dialogBox.
setAnimationEnabled
(true
);final
Button closeButton=
new
Button
("
Close
"
);//
We
can
set
the
id
of
a
widget
by
accessing
its
Element
closeButton.
getElement
().setId
("
closeButton
"
);final
Label textToServerLabel=
new
Label
();final
HTML serverResponseLabel=
new
HTML
();VerticalPanel dialogVPanel
=
new
VerticalPanel
();dialogVPanel.
addStyleName
("
dialogVPanel
"
);dialogVPanel.
add
(new
HTML
("
<b>Sending
name
to
the
server:</b>
"
));dialogVPanel.
add
(textToServerLabel);dialogVPanel.
add
(new
HTML
("
<br><b>Server
replies:</b>
"
));dialogVPanel.
add
(serverResponseLabel);dialogVPanel.
setHorizontalAlignment
(VerticalPanel.ALIGN_RIGHT);dialogVPanel.
add
(closeButton);dialogBox.
setWidget
(dialogVPanel);...
}
Une fois ceci fait, la seconde partie sert à « câbler » la gestion des évènements sur nos widgets. Il s'agit simplement d'handler instanciés sous forme de classes anonymes :
public
void
onModuleLoad
(){
...
//
Add
a
handler
to
close
the
DialogBox
closeButton.
addClickHandler
(new
ClickHandler
(){
public
void
onClick
(ClickEvent event){
dialogBox.
hide
();sendButton.
setEnabled
(true
);sendButton.
setFocus
(true
);}
}
);//
Create
a
handler
for
the
sendButton
and
nameField
class
MyHandlerimplements
ClickHandler, KeyUpHandler{
/**
*
Fired
when
the
user
clicks
on
the
sendButton
.
*/
public
void
onClick
(ClickEvent event){
sendNameToServer
();}
/**
*
Fired
when
the
user
types
in
the
nameField
.
*/
public
void
onKeyUp
(KeyUpEvent event){
if
(event.getNativeKeyCode
()=
=
KeyCodes.KEY_ENTER){
sendNameToServer
();}
}
/**
*
Send
the
name
from
the
nameField
to
the
server
and
wait
for
a
*
response
.
*/
private
void
sendNameToServer
(){
//
First,
we
validate
the
input.
errorLabel.
setText
("
"
);String textToServer
=
nameField.getText
();if
(!
FieldVerifier.isValidName
(textToServer)){
errorLabel.
setText
("
Please
enter
at
least
four
characters
"
);return
;}
//
Then,
we
send
the
input
to
the
server.
sendButton.
setEnabled
(false
);textToServerLabel.
setText
(textToServer);serverResponseLabel.
setText
("
"
);greetingService.
greetServer
(textToServer,new
AsyncCallback<
String>
(){
public
void
onFailure
(Throwable caught){
//
Show
the
RPC
error
message
to
the
user
dialogBox
.
setText
("
Remote
Procedure
Call
-
Failure
"
);serverResponseLabel
.
addStyleName
("
serverResponseLabelError
"
);serverResponseLabel.
setHTML
(SERVER_ERROR);dialogBox.
center
();closeButton.
setFocus
(true
);}
public
void
onSuccess
(String result){
dialogBox.
setText
("
Remote
Procedure
Call
"
);serverResponseLabel
.
removeStyleName
("
serverResponseLabelError
"
);serverResponseLabel.
setHTML
(result);dialogBox.
center
();closeButton.
setFocus
(true
);}
}
);}
}
//
Add
a
handler
to
send
the
name
to
the
server
MyHandler handler
=
new
MyHandler
();sendButton.
addClickHandler
(handler);nameField.
addKeyUpHandler
(handler);}
L'appel à notre service se fait à la ligne 48. Dans notre code coté client, nous utiliserons toujours l'interface asynchrone. L'interface du service ne nous sert qu'à l'instantiation de l'interface asynchrone, grâce à la classe GWT :
private
final
GreetingServiceAsync greetingService =
GWT.create
(GreetingService.class
);
L'interface GreetingServiceAsync reprend les mêmes méthodes que GreetingService, en y rajoutant un paramètre AsyncCallback. C'est cet objet qui contient le code à exécuter lors de la réponse du serveur, dans ses méthodes onSuccess() et onFailure().
Restons en là pour l'instant, nous y reviendrons dans un prochain article qui expliquera plus en détail la mise en œuvre des communications asynchrones avec le serveur.
Jetons maintenant un dernier coup d'œil au fichier principal d'une application GWT.
V-C. Le fichier Atabib.gwt.xml▲
C'est lui qui va définir notre point d'entrée dans l'application, ainsi que les packages à compiler en Javascript. En voici un extrait sans les commentaires :
<?
xml
version="1.0"
encoding="UTF-8"?
>
<
module
rename-to
=
'
Atabib
'
>
<
inherits
name
=
'
com.google.gwt.user.User
'
/
>
<
inherits
name
=
'
com.google.gwt.user.theme.standard.Standard
'
/
>
<
entry-point
class
=
'
fr.atatorus.atabib.client.Atabib
'
/
>
<
source
path
=
'
client
'
/
>
<
source
path
=
'
shared
'
/
>
<
/
module
>
Comme toute application GWT, la nôtre est un module, qui hérite (utilise pourrait-on dire) deux autres modules : User, qui contient les définitions des widgets, et Standard, qui concerne le style de nos widgets. Il existe d'autres modules, pour gérer les formats JSON et XML, ou pour les tests avec Junit.
Nous définissons ensuite le point d'entrée de notre application, puis les packages sources qui doivent être compilés en Javascript. Java et Javascript sont des langages quelques peu différents, aussi tout ce qui existe en Java ne peut pas se compiler en Javascript. C'est notamment le cas de tout ce qui concerne la gestion des threads (Javascript est mono-thread), et des nombres (Javascript ne connait que les nombres flottants, avec une étendue moindre que le type long de Java). Pour la liste complète des restrictions, vous pouvez aller sur la page qui y est consacrée dans la documentation de GWT.
VI. Conclusion▲
Voilà pour ce premier article à propos de GWT. J'espère vous avoir suffisamment intéressé pour avoir envie de poursuivre. Nous n'avons en effet fait que gratter la surface d'un projet GWT, il va nous falloir aller un peu plus en profondeur pour apprécier toute la puissance de ce framework. Nous nous y attèlerons dans la seconde partie de cette série d'articles, en commençant par voir comment créer et utiliser une interface utilisateur.
VII. Liens▲
- Le site officiel de GWT : http://www.gwtproject.org/.
- Le site du plugin GWT Maven : http://mojo.codehaus.org/gwt-maven-plugin/.
- Les plus impatients et ceux qui veulent savoir ce qui les attends dans cette série peuvent aller sur mon blog : https://www.atatorus.fr/blog. Les articles sur GWT datent de la version 2.0, mais ils vous permettront de découvrir les grands principes de ce framework en attendant leur remise à niveau sur www.developpez.com.