Créer des applications web interactives avec Google Web Toolkit

publicité
GWT
Créer des applications web interactives
avec Google Web Toolkit
(versions 1.7 et 2.0)
Olivier Gérardin
Directeur technique de Sfeir Benelux (groupe Sfeir)
Préface de
Didier Girard
Directeur des opérations et de l'innovation de Sfeir Paris (groupe Sfeir)
978-2-10-054628-2
Préface
JavaScript : je t’aime, moi non plus
Depuis que je développe des sites ou des applications web, j’ai toujours recherché
la technologie qui me permettrait de développer des sites web avec la meilleure
expérience utilisateur. La raison est simple, meilleure est l’expérience utilisateur et
plus il y a de chances pour que l’internaute revienne. C’est tellement simple, c’est
tellement vrai. Cette quête permanente de la meilleure technologie fait certainement
de moi un technophile.
Je n’en suis pas moins un technophobe : la technologie ne m’intéresse pas pour ce
qu’elle est, mais pour ce qu’elle me permet de créer. Ainsi, chaque mois je teste un
grand nombre de « nouveautés qui vont révolutionner le monde du développement
logiciel » et j’en rejette autant avec tout le dégoût provoqué par la perte du temps
consacré. Pour tester une technologie, mon approche est simple, quinze minutes pour
comprendre, une heure pour faire un Hello World et huit heures pour résoudre un
problème qui me tient à cœur. C’est ainsi qu’au fil des années, j’ai développé des sites
web en Shell, Perl, SSJS, ASP, .NET, Flex et Java. Année après année, une technologie
revenait sur mon banc de test : sa puissance, son universalité, sa simplicité me plaisait.
Pourtant, après quelques heures d’utilisation, je n’avais qu’une envie : la jeter par la
fenêtre. Cette technologie était JavaScript.
Pendant des années mes sites ont donc contenu le minimum syndical de JavaScript
et je voyais arriver la vague AJAX comme un raz de marée qui allait m’engloutir faute
de pouvoir aimer le cœur de cette approche.
GWT : Bon pour l’utilisateur, bon pour le développeur
GWT va me sauver. Sur le papier, cette technologie pensée par Google correspond
à ce que j’attendais : bonne pour l’utilisateur, bonne pour le développeur. En cinq
minutes, j’ai compris son fonctionnement et sa puissance « coder en java, compiler
en JavaScript » ; en quinze minutes, j’ai fait mon premier Hello World ; en huit heures
j’ai développé une application qui me semblait impossible à réaliser quelques heures
auparavant. J’étais conquis. L’accueil par les développeurs Web 2.0 était pourtant
mitigé, l’approche adoptée par GWT blessait les puristes ou les influenceurs du Web :
JavaScript n’était plus considéré comme un langage pour programmer le Web, mais
VI
GWT
comme un simple assembleur permettant d’exécuter le Web. L’intensité du rejet par
ces puristes était sans doute à l’échelle du potentiel de GWT qui :
• propose une bonne expérience utilisateur ;
• est facile d’utilisation ;
• est compatible avec les meilleurs environnements de développement ;
• aide à résoudre des problèmes difficiles de manière simple et efficace ;
• est soutenu par une communauté enthousiaste et en expansion rapide ;
• facilite la maintenance ;
• garantit une bonne performance ;
• est fun ;
• est gratuit et libre d’utilisation.
Et nous : Invention et Innovation
En créant GWT, Google ouvre une nouvelle ère du développement et rend accessible
à tous les développeurs la réalisation de sites web de nouvelle génération. Cette
invention permet d’améliorer la satisfaction client tout en innovant, c’est pour cela
que je l’ai adoptée et que je la conseille à mes clients.
Ce livre
Il existait actuellement beaucoup d’ouvrages en anglais sur GWT, il manquait un
ouvrage en français qui permette aux nombreux développeurs francophones de
découvrir cette technologie et ces concepts dans leur langue. C’est ce que propose
Olivier avec talent. Présenter une technologie n’est jamais aisée, les livres sont souvent
trop techniques ou pas assez, c’est selon. Ce livre a le bon dosage, il vous permettra
à la fois de bien débuter avec la technologie mais aussi d’approfondir les concepts
importants afin de développer des applications ayant la qualité requise pour une
utilisation en entreprise. Je le conseille donc à toute personne qui veut bien démarrer
avec GWT, que ce soit dans le cadre d’une activité de veille technologique, dans le
cadre du démarrage d’un projet ou dans le cadre de monitorat.
Bonne aventure technologique,
Didier Girard
http://www.google.com/search?q=Didier+Girard
Table des matières
Préface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
V
Avant-propos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
XIII
Première partie – Développer avec GWT
Chapitre 1 – De HTML à GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.1 Au commencement était HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.1.1
La notion d’URL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.1.2
La technologie derrière les pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.2 L’apparition des pages dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.2.1
CGI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ie
6
1.2.2
Server-side Scripting : PHP & C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.2.3
Java côté client : les applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.2.4
Les servlets et JSP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
1.2.5
Client-side Scripting et JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
1.2.6
Du vrai client-serveur en JavaScript : XmlHttpRequest et AJAX . . . . . . . . . . . .
9
1.3 L’étape suivante : Google Web Toolkit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
1.3.1
Les challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
1.3.2
Les réponses de GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
1.3.3
L’historique de la plate-forme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
VIII
GWT
Chapitre 2 – Hello, GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.1 L’environnement de développement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.1.1
Les plugins GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
2.2 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
2.2.1
Le choix de la plate-forme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
2.2.2
Installation d’Eclipse. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
2.2.3
Installation de GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
2.2.4
Google Plugin pour Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
2.3 Anatomie d’un projet GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
2.3.1
Nommage des packages et structure des dossiers . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
2.3.2
La page HTML hôte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
2.3.3
Le point d’entrée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
2.3.4
Le fichier module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
2.4 Hello, GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
2.4.1
Création du projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
2.4.2
Création du module GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
2.4.3
Création du point d’entrée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
2.4.4
Création de la page HTML hôte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
2.4.5
Lancement de l’application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
Chapitre 3 – Développer avec GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
3.1 Hosted mode vs web mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
3.2 Contraintes sur le code Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32
3.2.1
Support du langage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32
3.2.2
Émulation des classes du JRE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
3.3 Lancer en mode hôte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
3.3.1
Le serveur d’applications intégré . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
3.4 Compiler et lancer en mode web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
3.4.1
Les fichiers générés par le compilateur GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
3.5 Développement avec l’IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
3.5.1
Debug et cycle de développement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
Table des matières
IX
Chapitre 4 – Widgets, panels, etc. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.1 Une interface graphique dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.1.1
Les widgets GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42
4.1.2
GWT et les aspects graphiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42
4.2 Widgets et panels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
4.2.1
Événements et Handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
4.2.2
Widgets simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
4.2.3
Panels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
Chapitre 5 – Communiquer avec le serveur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
5.1 Code client vs code serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
5.2 Les principes de GWT RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
5.2.1
Classes et interfaces mises en jeu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
5.3 La création d’un service pas à pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
5.3.1
Écriture des interfaces et de l’implémentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
5.3.2
Déploiement sur le serveur embarqué . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
5.3.3
La réalisation d’un appel RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
77
5.4 Exemple complet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78
5.5 Utilisation d’un serveur externe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
5.6 Contraintes de sérialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
82
5.6.1
Types sérialisables déclarés par l’utilisateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83
5.7 Les exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83
Deuxième partie – Aller plus loin avec GWT
Chapitre 6 – Internationalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
89
6.1 Les possibilités d’internationalisation de GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
89
6.1.1
Importer le module I18N . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
90
6.2 Internationalisation « statique » . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
90
6.2.1
L’interface Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
91
6.2.2
Gestion des locales multiples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
96
6.2.3
Annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
102
6.2.4
L’interface ConstantsWithLookup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
103
X
6.2.5
GWT
L’interface Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
104
6.3 Le formatage des dates et nombres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
105
6.3.1
NumberFormat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
105
6.3.2
DateTimeFormat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
106
Chapitre 7 – Mécanismes avancés du compilateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
107
7.1 JSNI (JavaScript Native Interface) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
108
7.1.1
Le principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
108
7.1.2
Écrire une méthode JSNI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
108
7.1.3
Accéder à des objets Java depuis JSNI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
108
7.1.4
Règles de passage de paramètres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
110
7.1.5
Traitement des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
110
7.2 Deferred Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
110
7.2.1
Principes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
110
7.2.2
Mise en oeuvre du Deferred Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
112
Chapitre 8 – Le mécanisme d’historique de GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
115
8.1 Le problème . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
115
8.2 L’approche de GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
116
8.2.1
URL et fragment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
116
8.2.2
Encoder l’état de l’application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
116
8.2.3
Mise en oeuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
117
8.3 Un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
117
8.3.1
Créer une entrée dans l’historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
117
8.3.2
Réagir à un événement historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
118
8.4 Le widget Hyperlink . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
119
Chapitre 9 – Envoyer des requêtes HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
121
9.1 Au-delà de GWT RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
121
9.2 Requêtes HTTP en GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
122
9.2.1
Réalisation d’un appel HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
122
9.3 Manipulation de XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
124
9.3.1
Le DOM XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
124
9.3.2
Parsing d’un document XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
124
Table des matières
9.3.3
XI
Création d’un document XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
125
9.4 Manipulation de JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
126
9.5 Accès à des web services JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
128
9.6 Proxy serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
131
Chapitre 10 – Créer ses propres widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
133
10.1 Sous-classer un widget existant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
134
10.2 Utiliser la classe Composite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
134
10.3 Implémenter complètement un widget en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
135
10.4 Implémenter tout ou partie d’un widget en JavaScript. . . . . . . . . . . . . . . . . . . . . . .
136
Chapitre 11 – Bibliothèques tierces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
139
11.1 Bibliothèques de composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
139
11.1.1 GWT-ext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
139
11.1.2 Ext-GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
145
11.1.3 SmartGWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
149
11.1.4 Autres composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
149
11.2 Bibliothèques utilitaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
152
11.2.1 Gwittir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
152
11.2.2 GWT Server Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
152
11.2.3 Google API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
152
Chapitre 12 – GWT 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
159
12.1 Obtenir la dernière version de GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
160
12.1.1 Prérequis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
160
12.1.2 Génération de GWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
161
12.2 OOPHM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
161
12.2.1 Utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
164
12.3 Code splitting & SOYC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
166
12.3.1 Insérer un split point . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
166
12.3.2 Story Of Your Compile (SOYC) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
168
12.3.3 Optimiser avec le code splitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
172
XII
GWT
12.4 UiBinder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
174
12.4.1 Utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
174
12.4.2 Un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
176
12.5 ClientBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
178
Annexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
179
Liste des classes de la bibliothèque d’émulation JRE (chapitre 3) . . . . . . . . . . . . . . . . . . . . .
181
Exemple de FlexTable avec cellule baladeuse (chapitre 4) . . . . . . . . . . . . . . . . . . . . . . . . . .
184
Exemple d’appel à un service RPC (chapitre 5) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
186
Exemple de mise en œuvre du mécanisme d’historique (chapitre 8) . . . . . . . . . . . . . . . . . .
188
Appel à un service web JSON (chapitre 9) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
191
Exemple d’utilisation de l’API Google Gears (chapitre 11) . . . . . . . . . . . . . . . . . . . . . . . . .
194
Exemple d’utilisation d’UiBinder pour créer un widget composite (chapitre 12) . . . . . . . .
197
Webographie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
199
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
201
Avant-propos
Le Google Web Toolkit, ou GWT, est apparu en 2006 un peu comme un OVNI
sur la scène du développement d’applications dites RIA (Rich Internet Applications) ou
« Web 2.0 ». En effet, il ne rentrait dans aucune des catégories d’outils existant alors
pour faciliter le développement de ce genre d’applications. Était-ce un framework
web de plus ? Non... Une énième bibliothèque de composants JavaScript ? Non...
Une nouvelle plate-forme nécessitant encore un plugin pour fonctionner dans le
navigateur ? Non plus...
GWT est fondé sur un concept tellement original qu’il n’a pas convaincu grand
monde à l’époque : développer et mettre au point en pur Java, et traduire en JavaScript
au moment de déployer l’application sur le Web. Les avantages : on développe dans
un langage familier et avec un typage fort (Java), dans un environnement familier
(son IDE préféré – Eclipse, NetBeans, peu importe), avec des concepts familiers
(boutons, panels, listeners, MVC, etc.) ; par conséquent la courbe d’apprentissage pour
des développeurs Java est très rapide. D’autre part, toute la complexité de l’adaptation
aux différents navigateurs est entièrement assumée par le traducteur Java-JavaScript,
et le cauchemar de la prise en compte des différentes variantes de JavaScript entre
Firefox, IE, Safari, n’est plus qu’un souvenir.
GWT est la traduction technique de la vision qu’a Google des technologies du
Web 2.0 : « The browser is the platform » (le navigateur est la plate-forme). GWT
n’impose pas de nouveau runtime (pas de plugin nécessaire), il profite de l’infrastructure
et des outils en place, et s’intègre parfaitement dans les architectures et avec les
technologies existantes. Il offre une transition idéale entre le développement classique
et le développement d’applications RIA, avec un investissement humain et technique
minimal.
La promesse semble trop belle pour être vraie, et pourtant... difficile de lui trouver
des défauts : si les premières versions souffraient de quelques problèmes, GWT n’a
cessé de progresser, et depuis la version 1.4, on peut considérer qu’il est totalement
fiable et efficace, et l’a prouvé sur de nombreux projets. La licence initiale, jugée
restrictive par certains, a été remplacée par la licence Apache 2.0, considérée comme
une des plus libérales de l’Open Source, ce qui garantit la pérennité du produit. Le
XIV
GWT
groupe de discussion consacré à GWT compte plus de 20 000 membres, preuve de la
vitalité de la plate-forme.
J’ai personnellement découvert GWT en 2006, et l’approche m’a immédiatement
séduite car elle me permettait enfin de réconcilier la création d’applications AJAX
avec l’univers Java qui est le mien depuis de nombreuses années. Cela signifiait surtout
qu’il serait désormais possible de mener à bien un projet de ce type en se passant
(enfin) d’un « expert JavaScript », ce magicien qui connaît par cœur les subtilités
(et les bugs) des différentes implémentations dans les navigateurs, et les astuces qui
permettent de les contourner...
Si l’approche de GWT m’a séduite, j’avoue avoir eu des doutes sur son applicabilité : comment émuler fidèlement la bibliothèque Java du Java Runtime Environment
(JRE) ? Comment gérer la généricité ? Comment tester le code ? Les différences
entre le langage interprété qu’est JavaScript et le langage compilé qu’est Java ne
seront-elles pas rédhibitoires ? Et qu’en sera-t-il des performances ? Cependant, comme
à son habitude, Google a fait taire mes doutes en apportant des réponses techniques
adéquates, efficaces et innovantes, et c’est sans doute ce qui a fait de GWT un produit
réellement utilisable, et pas seulement un concept original.
À ce jour, cela fait deux ans que je participe à des projets mettant en œuvre
GWT, et sur chacun d’eux il a fait la preuve de sa maturité de la meilleure manière :
en se faisant oublier... une fois la configuration en place, il devient un rouage de la
mécanique, et permet de se concentrer sur les problématiques propres à l’application.
L’intérêt des développeurs pour GWT est fort, mais malheureusement la littérature
francophone sur le sujet est rare. J’espère donc au travers de ce livre vous donner
toutes les clés qui vous permettront de vous mettre à GWT.
À qui s’adresse ce livre ?
Ce livre s’adresse principalement aux développeurs Java ayant un minimum d’expérience et qui veulent découvrir et mettre en œuvre Google Web Toolkit. Une
connaissance de base du langage Java est requise, ainsi que des principales technologies
du Web : HTTP, HTML et CSS, JavaScript.
Pour les aspects client-serveur, une connaissance des principes de JEE (Java Enterprise Edition) est souhaitable, même si nous essayerons d’en rappeler les principales
notions lorsque nous y aurons affaire.
XV
Avant-propos
Comment est structuré ce livre ?
Ce livre est découpé en deux parties :
• La première partie expose les bases qui permettent de comprendre GWT et de
réaliser une application complète :
– en situant GWT dans son contexte et en expliquant ses ambitions et le
cheminement qui a conduit à son apparition (chap. 1) ;
– en montrant comment mettre en place l’environnement logiciel nécessaire
et créer un premier projet GWT (chap. 2). Vous y apprendrez également la
façon de développer et déboguer avec GWT (chap. 3) ;
– en présentant les éléments de construction d’une application GWT, aussi
bien graphiques (widgets, chap. 4) qu’architecturaux (communication avec
le serveur, chap. 5).
• La deuxième partie explorera des concepts avancés qui ne sont pas forcément
nécessaires dans toutes les applications GWT, mais qui peuvent répondre à une
problématique spécifique :
– les possibilités d’internationalisation de GWT (chap. 6) ;
– les possibilités avancées du compilateur : JSNI pour incorporer du code
JavaScript dans des méthodes Java, et le deferred binding pour générer des
versions de code optimisées pour chaque environnement (chap. 7) ;
– la gestion de l’historique du navigateur (chap. 8) ;
– les possibilités d’envoi direct de requêtes HTTP (chap. 9). On y verra aussi
comment manipuler XML et JSON avec GWT ;
– les différentes manières de créer ses propres composants graphiques (chap.
10) ;
– l’utilisation de bibliothèques tierces avec GWT, que ce soit pour enrichir
la palette de composants d’interface utilisateur ou pour fournir d’autres
fonctionnalités (chap. 11) ;
– enfin, le chapitre 12 qui fera un tour d’horizon des nouveautés de GWT 2.0.
Si la première partie est plutôt linéaire, la seconde peut être consultée indépendamment et servir de référence.
Remerciements
Ce livre n’aurait pas été possible sans la patience de mon épouse, qui a supporté les
nombreuses heures durant lesquelles j’étais plongé dans la rédaction de cet ouvrage.
Merci également à Didier Girard pour avoir eu l’amabilité de rédiger la préface,
pour m’avoir mis en contact avec l’éditeur et pour ses conseils toujours précieux.
PREMIÈRE PARTIE
Développer avec GWT
1
De HTML à GWT
Objectif
Dans ce chapitre, nous verrons comment le Web « original » a été conçu, quelles
technologies sont apparues pour le rendre plus dynamique et permettre l’émergence
d’applications web. Nous verrons quels inconvénients ces technologies possèdent,
et comment GWT propose une solution élégante à la création d’applications web
dynamiques.
1.1 AU COMMENCEMENT ÉTAIT HTML
Le Web est un concept tellement familier que nous le manipulons quotidiennement
pour la plupart d’entre nous. Mais le Web d’aujourd’hui est passablement différent du
Web tel qu’il a été imaginé par ses concepteurs, et tel qu’il a vu le jour.
Le Web, ou World Wide Web comme on l’appelait encore il n’y a pas si longtemps,
est un concept inséparable de celui d’hypertexte. Pour schématiser, l’hypertexte, c’est
du contenu (au sens large : texte, images, médias), augmenté de liens qui permettent
de passer d’une ancre (l’origine du lien) à la cible du lien (un autre document).
L’hypertexte a été implémenté de plusieurs manières, indépendamment du WWW,
notamment dans des systèmes propriétaires et fermés. Tous les documents liés se
trouvaient alors dans une base unique, permettant de créer des systèmes documentaires
plus riches qu’une collection de simples documents, mais limités à leur propre domaine.
La véritable révolution est née de la combinaison d’un langage de description de page
(HTML pour Hypertext Markup Language), d’un protocole de transfert approprié aux
contenus hypertextes (HTTP pour Hypertext Transfer Protocol), et d’un réseau de
données à l’échelle mondiale, l’Internet. Il devenait alors possible de créer des pages
4
Chapitre 1. De HTML à GWT
avec un contenu « riche » (texte et images), incluant des liens vers n’importe quelle
autre page, du même site ou d’un autre site quelque part sur la planète.
L’invention du Web
L’histoire retiendra que le Web a été conceptualisé au début des années 1990 au CERN
à Genève, par Tim Berners-Lee, un physicien anglais, et Robert Cailliau, un informaticien
belge. Dès 1990, s’appuyant sur les concepts hypertextes existants, ils envisagent de
construire une « toile » reliant des nœuds constitués de pages hypertextes, accessibles
au travers de « browsers » sur un réseau. Le concept se concrétise en 1992 avec
l’apparition des premiers sites web, et se popularise dès 1993 avec la disponibilité
du premier browser graphique, Mosaic, qui remplace avantageusement Lynx, son
prédécesseur, un browser uniquement textuel.
Dès lors, le nombre de sites et de pages web ne cessera d’augmenter.
http://news.netcraft.com/archives/web_server_survey.html
1.1.1 La notion d’URL
Un concept essentiel à la réussite du Web est la notion d’URL, Uniform Resource
Locator. Dans le WWW, une URL est une chaîne de caractères formalisée qui est la
façon normalisée de désigner une ressource accessible via le Web (en général une page
HTML, mais ce n’est pas obligatoire).
Une URL est constituée de plusieurs parties, dont certaines sont optionnelles et
d’autres non. Dans sa forme la plus commune, une URL se présente de la manière
suivante :
http://code.google.com:80/webtoolkit/overview.html
On reconnaît les parties suivantes :
• http est le schema qui désigne le protocole à utiliser. À noter que sa valeur
conditionne aussi le format du reste de l’URL ; on se limitera ici à la description
des URL de type « http » ;
• code.google.com est le hostname, c’est-à-dire la désignation de la machine qui
héberge la ressource ;
• 80 est le port IP à utiliser pour la connexion ;
• le reste de l’URL constitue le chemin d’accès à la ressource sur le serveur.
Additionnellement, on peut encore trouver à la suite du chemin :
• une query string commençant par un point d’interrogation ? et spécifiant la
valeur de certains paramètres de la ressource, par exemple des critères de
recherche : http://www.google.com/search?q=gwt ;
• un indicateur de fragment, commençant par un symbole dièse #, qui désigne
une sous-partie de la ressource. Dans les pages HTML, cet indicateur est utilisé
pour désigner un signet, c’est-à-dire un marqueur dans la page (début de section
par exemple).
1.2 L’apparition des pages dynamiques
5
L’URL joue un rôle essentiel dans le WWW car :
• elle permet la création de liens hypertextes : la cible de tout lien est désignée
par son URL ;
• elle permet l’inclusion de ressources (par exemple des images dans une page
HTML) en les désignant par leur URL.
Contrairement à un système fermé où la cible d’un lien appartient forcément au
système au même titre que la source, le WWW permet de désigner n’importe quelle
ressource, quel qu’en soit le propriétaire, au travers de cette chaîne de caractères qu’est
l’URL.
1.1.2 La technologie derrière les pages
Grâce au WWW et à la notion d’URL, il est donc aisé de saisir une « adresse web »
sous forme d’une URL dans un navigateur, et d’accéder à la page correspondante.
Que se passe-t-il exactement quand nous tapons une adresse dans un navigateur
pour accéder à un site web ? L’URL est alors « déréférencée », c’est-à-dire interprétée
et utilisée pour obtenir son contenu.
La séquence des événements est la suivante :
1. L’URL est parsée et ses composants isolés.
2. Si le hostname est indiqué par son nom, une requête DNS est faite pour obtenir
l’adresse IP correspondante.
3. Une connexion TCP est établie vers cette machine, sur le port spécifié (ou un
port par défaut qui dépend du protocole, 80 pour HTTP).
4. Une requête HTTP est construite et envoyée au serveur via la connexion
ouverte. Cette requête peut contenir un certain nombre d’informations, mais
elle contient en particulier le chemin relatif du document accédé (dans notre
exemple, /webtoolkit/overview.html).
5. Le serveur répond en renvoyant une réponse HTTP. Si tout est correct, la
réponse contient le document demandé, ainsi que d’autres informations, en
particulier un type MIME qui indique le type du document.
6. En fonction du type MIME de la réponse, le navigateur interprétera le
contenu différemment : par exemple s’il s’agit de HTML, il va parser et afficher
le document ; s’il s’agit d’un fichier PDF, il proposera de l’ouvrir ou de le
sauvegarder, etc.
1.2 L’APPARITION DES PAGES DYNAMIQUES
Si ce système fonctionne de façon satisfaisante, il s’agit alors de pages statiques,
c’est-à-dire de pages HTML stockées sous forme de fichiers, et servies telles quelles au
client. Le besoin apparaît de générer la réponse à une requête HTTP dynamiquement,
par l’exécution de code côté serveur, plutôt que par le simple contenu d’un fichier
HTML immuable.
6
Chapitre 1. De HTML à GWT
1.2.1 CGI
La norme CGI (Common Gateway Interface) résulte d’une idée simple : le résultat
de l’appel à une URL est fourni par l’exécution d’une commande du système
d’exploitation. La norme spécifie que les paramètres de la requête (la query string) sont
passés à la commande sous forme d’une variable d’environnement ; la sortie standard
de la commande est capturée et constitue le résultat qui sera renvoyé au client.
C’est la configuration du serveur web qui détermine quelles sont les commandes
qui sont ainsi exécutables ; en général un sous-répertoire nommé cgi-bin est désigné
et toutes les URL qui pointent vers ce chemin sont considérées comme désignant une
commande CGI à exécuter plutôt que comme un fichier dont le contenu doit être
renvoyé.
Si CGI permet effectivement de générer des pages dynamiques et a été pendant
des années le fondement du Web dynamique, il souffre de plusieurs problèmes dont
le principal est une inefficacité liée à son principe même : chaque requête déclenche
l’exécution d’une commande au niveau de l’OS, et donc le lancement d’un processus.
Or la création d’un processus est une opération assez coûteuse en termes de ressources
et de temps, et CGI s’est avéré peu adapté lorsqu’il s’agit de monter en charge.
1.2.2 Server-side Scripting : PHP & Cie
Dans le sillage de CGI, d’autres technologies pour rendre dynamique tout ou partie de
la page HTML sont apparues, faisant appel à des mécanismes de scripting côté serveur.
On retiendra celui qui a le mieux réussi : PHP.
PHP propose d’inclure à l’intérieur même de la page HTML des balises spéciales
qui sont destinées à être décodées par le serveur. Entre ces balises, du code PHP, un
langage interprété et qui est devenu au fil des ans très riche en termes de bibliothèques
intégrées. Le serveur web, en général au travers d’un module dédié, reconnaît la
présence de code PHP dans une page et l’exécute. Le code PHP peut à son tour
produire du HTML dynamique qui sera inclus dans la page retournée au client.
Cette technologie a beaucoup d’avantages, notamment la versatilité et la relative
simplicité du langage PHP, et la possibilité pour un utilisateur de mettre en ligne
lui-même des pages PHP sans compromettre la sécurité. C’est ce qui a fait son
succès jusqu’à aujourd’hui. Cependant, PHP reste interprété et donc relativement
peu efficace.
1.2.3 Java côté client : les applets
Lorsque le langage Java a été introduit par Sun, sa première application a été la
possibilité d’insérer des mini-applications à l’intérieur même d’une page HTML : les
applets. Ces mini-applications pouvaient disposer de toute la puissance et la richesse
de Java, fournir une interface graphique évoluée avec les composants AWT (et plus
tard Swing), ou bien dessiner directement en mode bitmap.
1.2 L’apparition des pages dynamiques
7
Est-ce que l’interactivité, le dynamisme et la richesse des interfaces web seraient
finalement apportés par les applets Java ? Malheureusement, les applets ont rapidement
et injustement été rangées dans la catégorie « gadgets animés qu’on peut mettre dans
un coin d’une page web ». Les raisons de ce semi-échec sont multiples :
• le support de Java dans les navigateurs n’était pas universel à l’époque, et était
souvent associé à un temps de démarrage important ;
• l’implémentation de Java fournie par Microsoft présentait des différences
importantes avec celle de Sun qui rendaient difficile la création d’une applet
fonctionnant à la fois avec la JVM (Java Virtual Machine) de Sun et celle de
Microsoft ;
• l’interaction entre l’applet et le reste de la page est la plupart du temps
inexistante, car très complexe à mettre en place.
1.2.4 Les servlets et JSP
Pour répondre au problème de scalabilité des pages dynamiques, que CGI et PHP
ne résolvent pas de façon satisfaisante, Sun a imaginé le concept de servlet. Une
servlet est un composant logiciel (une classe Java) qui est écrit spécifiquement pour
répondre à une requête HTTP. La norme spécifie très précisément comment s’effectue
l’invocation des servlets, la transmission des paramètres à la requête et en retour du
résultat renvoyé par la servlet.
L’énorme avantage en comparaison de CGI est que chaque requête est traitée
dans un fil d’exécution (thread), ce qui épargne le coût de la création systématique
d’un processus. Le serveur contrôle strictement le nombre de threads présents et la
distribution des requêtes aux threads au travers d’un dispatcher.
Les pages JSP (Java Server Pages) sont intimement reliées aux servlets — bien
qu’elles se présentent sous une apparence semblable à une page PHP, c’est-à-dire
du HTML dans lequel des balises spéciales introduisent des parties dynamiques —,
puisqu’elles sont en fait compilées sous forme de servlet à leur première utilisation.
On pourrait dire que servlets et JSP sont donc deux visages de la même technologie,
ou plus exactement que JSP est une autre façon d’écrire des servlets.
Cependant, dans la mesure où les documents renvoyés par les servlets/pages JSP
restent des pages HTML complètes, l’interactivité n’est que peu améliorée en regard
des pages servies par CGI ou PHP, puisque la moindre action de l’utilisateur qui
nécessite une requête au serveur doit passer par une action de submit HTML et le
rechargement complet de la page. Tout au plus a-t-on optimisé le temps de réponse du
serveur.
1.2.5 Client-side Scripting et JavaScript
Pour franchir un pas dans l’interactivité, il fallait une technologie capable d’exécuter
du code côté client, pour pouvoir réagir aux événements qui se produisent dans le
navigateur (frappe d’une touche, changement du focus, clic sur un bouton, etc.). Ainsi
8
Chapitre 1. De HTML à GWT
est apparue la balise HTML <script>, qui permet d’embarquer à l’intérieur d’une
page HTML du code exécutable sous forme de script, c’est-à-dire de code source
d’un langage interprété. En principe, les scripts inclus dans une page HTML peuvent
être écrits dans différents langages ; cependant, JavaScript est le seul langage dont le
support natif soit commun à tous les navigateurs « grand public », et c’est à ce titre
qu’il a connu un tel succès.
JavaScript a été créé par Netscape en 1995 et a connu différents noms (Mocha,
LiveScript) avant que JavaScript soit adopté, comme résultat d’un accord avec Sun
qui prévoyait que le navigateur dominant de l’époque embarque une JVM. JavaScript
s’inspire de la syntaxe de Java, mais n’a pas de relation directe avec ce dernier.
Tableau 1.1 — Comparaison entre Java et JavaScript
Type de langage
Typage
Mode d’exécution
Syntaxe
Conventions de
nommage
Java
Orienté objet
Statique
Semi-compilé (bytecode) +
machine virtuelle
JavaScript
Orienté objet/fonctionnel
Dynamique
Interprété
Type « C »
Similaires
Pour interagir avec la page HTML qui le contient, un script JavaScript s’appuie
sur le DOM (Document Object Model), qui est une abstraction sous forme d’arbre des
éléments contenus dans la page HTML. L’arbre DOM initial est construit à partir des
éléments HTML présents dans la page hôte ; la grande puissance du scripting web,
c’est la possibilité de modifier dynamiquement le DOM, c’est-à-dire de changer le
contenu de la page HTML après son chargement. Au travers du DOM, un script peut
à loisir ajouter des éléments dans la page, mais aussi retirer ou modifier des éléments
existants ! La conjonction du scripting et du DOM offre les conditions pour créer des
interfaces sur une base HTML, mais capables de réagir en temps réel aux actions de
l’utilisateur, et de se modifier en conséquence.
Les premières applications concrètes ont été la validation de saisie côté client : en
effectuant des vérifications sur la longueur des champs, le format des données saisies,
il était possible d’afficher un message à l’utilisateur et d’inhiber l’envoi de la requête
au serveur ; on évitait ainsi un aller-retour voué à l’échec. Le scripting a ensuite été
utilisé pour ajouter de la richesse aux interfaces, en particulier la création de menus
déroulants et l’implémentation de composants se rapprochant de plus en plus de leurs
équivalents des clients lourds.
Cependant, il restait un obstacle à la réalisation d’interfaces fluides : toute requête
au serveur passait par un submit HTML et le rechargement complet de la page.
1.3 L’étape suivante : Google Web Toolkit
9
1.2.6 Du vrai client-serveur en JavaScript : XmlHttpRequest et AJAX
En 2000, Microsoft introduit avec Exchange Server 2000 un client web nommé
« Outlook Web Access » permettant d’accéder à toutes ses données (e-mail, calendrier,
contacts, etc.) depuis un navigateur web. La nouveauté de ce client web, c’est qu’il
reproduit de façon quasi identique le client lourd Outlook, et surtout met à jour les
données affichées sans jamais recharger la page courante en entier. Pour y parvenir,
le client JavaScript utilise la nouvelle classe XmlHttpRequest qui permet d’envoyer
directement une requête au serveur, sans passer par un submit. La réponse à la requête
est traitée de façon asynchrone, c’est-à-dire que le flux d’exécution continue jusqu’à
ce que la réponse soit disponible.
Cette technique permet enfin de créer des applications client entièrement en
JavaScript qui communiquent avec un serveur sans quitter la page courante, et donc
en conservant la fluidité de l’interface. Fini le clignotement qui accompagnait le
rechargement de la page HTML avec les technologies utilisant un submit HTML,
on peut désormais construire des clients web qui autorisent un look-and-feel quasi
identique à celui d’un client lourd.
D’abord réservé à Internet Explorer, le support de XmlHttpRequest a rapidement été
incorporé dans les autres navigateurs, créant ainsi les conditions (avec DOM et CSS)
pour la généralisation de ce nouveau type d’applications web riches et interactives
baptisé AJAX (Asynchronous JavaScript and XML).
1.3 L’ÉTAPE SUIVANTE : GOOGLE WEB TOOLKIT
1.3.1 Les challenges
On l’a vu, au cours du temps les technologies mises en œuvre dans les applications
web ont évolué, se sont complexifiées et enrichies. Aujourd’hui, la réalisation d’une
application web dynamique de type AJAX suppose la maîtrise de JavaScript, de HTML,
du DOM, et d’une ou plusieurs bibliothèques additionnelles.
JavaScript a longtemps été et est encore souvent considéré comme un langage de
scripting parmi d’autres. Cependant, JavaScript est un langage bien plus complexe et
puissant qu’on l’imagine souvent avant de l’étudier sérieusement. S’il est très facile
d’ajouter un petit peu de dynamisme à une page web avec quelques lignes de JavaScript,
son utilisation à plus grande échelle s’avère souvent problématique, et le passage au
stade industriel du développement est délicat...
Un des plus sérieux handicaps du développement AJAX est la nécessité de prendre
en compte les différences entre les plates-formes pour obtenir un comportement
identique de l’application pour chacune d’elles. En effet, il existe entre les browsers
majeurs des variations considérables dans l’interprétation de JavaScript, le support
HTML, et la gestion du DOM. Ces différences ajoutent une charge de travail
non négligeable puisqu’il faut les connaître, savoir les contourner, implémenter les
solutions alternatives, tester pour les différentes plates-formes, etc. Ce type de développement exige un niveau de compétences très pointu dans les domaines en question.
10
Chapitre 1. De HTML à GWT
En outre, même si d’énormes progrès ont été faits dernièrement, les outils de
développement JavaScript souffrent toujours de la comparaison avec leurs homologues
des langages compilés comme Java ou C++, et sont souvent dédiés à une seule plateforme cible. Les outils d’introspection HTML sont différents de Firefox à Internet
Explorer et d’IE à Safari.
Enfin, le langage JavaScript, quelle que soit sa puissance, reste un langage interprété
et avec un typage faible ; de nombreuses erreurs n’apparaissent qu’à l’exécution, et
leur diagnostic n’est pas toujours évident. Les possibilités de refactoring automatisé qui
sont devenues communes dans les environnements de développement intégré (IDE)
Java ne sont encore qu’un rêve en JavaScript.
Pour toutes ces raisons, le développement AJAX « direct » en JavaScript, même
s’il est attirant grâce aux résultats qu’il permet d’obtenir, présente des contraintes
importantes et des risques élevés.
1.3.2 Les réponses de GWT
Les problèmes posés par le développement AJAX n’ont bien sûr pas échappé à
Google, dont de plus en plus d’applications ont mis en œuvre au fil du temps cette
technologie. Google se devait donc de trouver des solutions à ses propres problèmes
de développement JavaScript, et c’est de cette motivation qu’est né le Google Web
Toolkit.
Google s’est posé la question suivante : Quel langage de développement est
aujourd’hui à la fois extrêmement répandu, mature, stable, disposant d’outils et
d’environnements de développement complets et agréables à utiliser, et d’un potentiel
de compétences quasi inépuisable ? Si vous avez répondu Java, vous avez gagné bien
sûr.
Le postulat de base de GWT, qui peut sembler assez incongru de prime abord, est
que le meilleur moyen de produire du JavaScript destiné à animer une application web
n’est pas d’écrire directement le code JavaScript, mais d’utiliser un autre langage (Java),
avec ses outils de développement et son environnement. L’écriture de l’application se
fera entièrement en Java, avec tous les avantages : IDE complets et puissants, typage
fort et vérifications statiques, aides au refactoring, débogage évolué, etc. Le code
JavaScript sera généré au besoin, pour déployer l’application ou pour la tester, à partir
du code Java.
Évidemment, les différences entre Java et JavaScript imposent des contraintes au
code qu’il est possible de transformer en JavaScript. Seule une partie des classes de
Java SE est supportée, mais cela inclut la majorité des classes des packages java.lang
et java.lang.annotations, et une bonne partie de java.util. En pratique, il s’avère
que ces contraintes sont peu pesantes pour le développement courant.
De fait, le rôle joué par JavaScript dans une application GWT est similaire à celui
de l’assembleur pour un langage de haut niveau, ou du bytecode pour une application
Java standard : ce n’est ni plus ni moins qu’un langage intermédiaire destiné à être
interprété par une plate-forme cible.
1.3 L’étape suivante : Google Web Toolkit
11
1.3.3 L’historique de la plate-forme
La première version distribuée publiquement de GWT, la version 1.0 RC1, a été
publiée en mai 2006. Elle a été annoncée au salon JavaOne la même année.
Chaque version majeure (1.1, 1.2) est en général précédée d’une « release candidate » (RC), et parfois suivie de version corrective.
Les versions 1.1 et 1.2, publiées au cours de l’année 2006, ont introduit la plupart
des fonctionnalités qui existent dans la version actuelle. À noter que le support pour
Mac OS est présent depuis la version 1.2.
Un changement crucial est apparu avec la version 1.3 (février 2007), qui est la
première version totalement Open Source de GWT. À partir de cette version, GWT
est distribué sous la licence dite « Apache », qui est une des plus permissives du monde
Open Source. Il s’agit là d’un gros argument de moins pour ceux qui doutaient de la
pérennité de GWT comme produit purement propriétaire de Google.
La version 1.4 a introduit la plupart des composants graphiques existant dans la
version actuelle.
Un autre changement majeur a été introduit avec la version 1.5 (aout 2008) : il
s’agit du support de la syntaxe Java 5, et principalement des types génériques. Jusque
là, GWT ne pouvait compiler que du code Java écrit avec la syntaxe Java 1.4 ; l’arrivée
des génériques a apporté un nouveau degré de vérifications statiques.
Puis la version 1.6 (avril 2009) a introduit deux changements principaux :
• une nouvelle structure de projet plus proche des standards du développement
web ;
• l’abandon de la notion de listener pour celle de handler.
La version 1.7, malgré son apparence de release majeure, n’apporte en fait essentiellement que la compatibilité avec Internet Explorer 8 et la version 10.6 de Mac OS X
(« Snow Leopard »).
Enfin, la dernière version en date, qui porte le numéro 2.0, marque une étape
importante grâce à de nombreuses nouveautés, axées principalement sur l’amélioration
des performances et l’optimisation. On y trouve en particulier :
• une nouvelle option permettant de déboguer avec tout type de navigateur ;
• la possibilité de diviser le code en plusieurs blocs pour accélérer le chargement
initial ;
• un langage de description permettant de construire des interfaces sans écrire de
code ;
• le groupement de ressources hétérogènes, etc.
Un chapitre entier de cet ouvrage (chap. 12) est consacré aux nouveautés de cette
version.
Vous pouvez consulter l’historique détaillé de GWT à cette adresse :
http://code.google.com/webtoolkit/releases/release-notes-1.7.1.html.
2
Hello, GWT
Objectif
Dans ce chapitre, nous installerons l’environnement de développement nécessaire à
GWT ainsi que GWT lui-même, puis nous créerons notre premier projet GWT.
2.1 L’ENVIRONNEMENT DE DÉVELOPPEMENT
Une des caractéristiques de GWT est de ne pas imposer au développeur d’outils
spécifiques. Puisque le développement se fait en Java, pourquoi ne pas laisser le
développeur utiliser son environnement de développement préféré ?
Et c’est en effet la réalité avec GWT : que vous soyez fan d’Eclipse, de NetBeans
ou d’IntelliJ, ou de tout autre IDE, le développement avec GWT ne se différencie que
très peu du développement d’une application Java classique. On écrit du code Java,
on le compile (ou l’IDE s’en charge automatiquement), on l’exécute et on le débogue
directement comme n’importe quel code Java.
La différence la plus notable est sans doute que le code Java nécessite un environnement particulier pour s’exécuter : le hosted mode (ou mode hôte). Comme vous le
savez, le but ultime de GWT est de générer une application purement JavaScript, et
qui s’exécute dans un navigateur web. Cependant, pour permettre le développement
et surtout le débogage sous forme de code Java standard, le hosted mode permet
d’exécuter l’application GWT dans sa version Java pur. La conversion en JavaScript
n’est nécessaire que pour le déploiement de l’application.
1.4, 1.5, 1.6, 1.7
1.3 (IntelliJ 6)
1.4 (IntelliJ 7)
1.5 (IntelliJ 8)
1.6, 1.7 (IntelliJ 9)
1.5, 1.6, 1.7
Toutes (le
téléchargement du
plugin inclut la version
la plus récente de
GWT)
GWT Studio
GWT4NB
Google Plugin for
Eclipse
Versions de GWT
supportées
1.4, 1.5
Instantiations GWT
Designer 7.2
Cypal Studio for
GWT 1.0
Plugin
Eclipse 3.3+
NetBeans
IDE 6.7.1
IntelliJ IDEA
Eclipse 3.2+
Eclipse 3.3+
IDE cible
Gratuit
Gratuit
Inclus avec IntelliJ
(version 6+)
Commercial avec évaluation
de 14 jours (licence complète
à partir de 59 $ pour un an)
Gratuit
Licence
Tableau 2.1 — Les plugins de GWT
Création de projet/module
Création de service RPC
Lancement en mode hôte
Compilation JavaScript
Déploiement WAR
Editeur visuel d’interface
Synchronisation bidirectionnelle avec le code Java
Création de composants réutilisables
Création de projet/module
Compilation JavaScript
Contrôles statiques (inspectors)
Lancement en mode hôte
Navigation/refactoring/complétion de code
Déploiement WAR
Création de projet
Création de service RPC
Compilation JavaScript
Déploiement WAR
Création de projet/module/entry point/page hôte
Lancement en mode hôte
Compilation JavaScript
Déploiement sur le Google App Engine
Vérification syntaxique et refactoring dans les
méthodes JSNI
Fonctionnalités
14
Chapitre 2. Hello, GWT
15
2.2 Installation
2.1.1 Les plugins GWT
Bien entendu, il existe des plugins qui facilitent l’intégration de GWT dans les différents IDE. Le tableau 2.1 récapitule les caractéristiques des plugins GWT disponibles à
ce jour.
Deux plugins de cette liste nécessitent un approfondissement :
• GWT Designer est le seul qui offre la possibilité d’éditer visuellement une
interface GWT. Néanmoins, c’est aussi le seul qui soit payant ;
• le dernier venu, le Google Plugin, est intéressant en premier lieu car il vient
de Google lui-même, en deuxième lieu car il offre aussi la possibilité de tester
et déployer des applications sur le Google App Engine dont la version Java est
apparue simultanément.
Le plugin Cypal était jusqu’à l’apparition de GWT 1.6 le favori des utilisateurs
d’Eclipse, mais sa non-compatibilité avec GWT 1.6 et surtout l’apparition du Google
Plugin l’ont rendu obsolète.
2.2 INSTALLATION
2.2.1 Le choix de la plate-forme
Comme vous le savez, Java est par nature multi-plate-forme, et les outils associés
existent en règle générale pour la plupart des combinaisons architecture matérielle/système d’exploitation.
GWT lui-même, bien qu’étant quasi exclusivement écrit en Java, est publié par
Google dans trois variantes :
• Windows (versions Vista, XP ou 2000) ;
• Mac OS (versions 10.4 ou ultérieure) ;
• Linux (x86, nécessite GTK+ version 2.2.1 ou ultérieure).
Bien entendu, il fallait faire le choix d’une plate-forme de référence pour illustrer
les exemples de ce livre ; nous avons opté pour Eclipse 3.4 (« Ganymède ») sur Mac
OS X avec Google Plugin. À quelques différences mineures près, l’installation et le
développement avec Eclipse et GWT sont tout à fait similaires sur les trois platesformes ; nous signalerons toutefois les différences lorsque cela sera utile.
Nous détaillerons ici l’installation et l’utilisation de GWT uniquement en combinaison avec Eclipse ; cependant si vous êtes familier d’un autre IDE, vous devriez
facilement réussir à transposer ces étapes pour votre IDE de prédilection.
16
Chapitre 2. Hello, GWT
2.2.2 Installation d’Eclipse
Eclipse existe en de nombreuses variantes selon le type de développement qu’on
souhaite faire. La distribution qui nous intéresse doit au minimum intégrer le
développement web, mais pour être paré nous utiliserons la version « JEE » qui inclut
entre autres tous les outils web.
Cette version peut se télécharger depuis la page :
http://www.eclipse.org/downloads/packages/release/ganymede/sr2
Il n’existe pas d’installeur pour Eclipse (dans les versions standard) ; le téléchargement consiste simplement en un fichier ZIP qui contient un unique dossier eclipse
qu’il suffit d’extraire à l’endroit le plus approprié (en principe C:\Program Files sous
Windows, /Applications sous Mac OS). Simple et efficace.
Attention
Si vous faites une mise à jour d’une version existante, des précautions sont à prendre
car écraser simplement le dossier eclipse existant peut conduire à des mauvaises
surprises... Il est conseillé dans ce cas de suivre la procédure recommandée, qui est
d’installer la nouvelle version à côté de l’existante, et de faire pointer la nouvelle version
vers le workspace existant.
Le dossier eclipse que vous venez d’extraire contient l’application elle-même, qui
s’appelle sans surprise eclipse.exe sous Windows ou Eclipse sous Mac OS. Libre à
vous de créer alors un raccourci/alias où bon vous semble.
2.2.3 Installation de GWT
À noter
L’installation manuelle de GWT comme expliquée dans cette section n’est pas
obligatoire si vous utilisez le Plugin Google ; en effet ce dernier inclut la version
la plus récente de GWT. Néanmoins, vous devrez procéder comme décrit ici si vous
souhaitez travailler avec une version spécifique, ou installer simultanément plusieurs
versions de GWT.
GWT se télécharge depuis la page suivante :
http://code.google.com/webtoolkit/versions.html
Prenez soin de choisir la version correspondant à votre plate-forme de développement.
Comme pour Eclipse, en téléchargeant GWT vous récupérez un fichier ZIP, que
vous pouvez extraire à l’endroit qui vous semble le plus approprié. Sous Windows, vous
pouvez créer un dossier C:\Java dans lequel vous placerez également toutes les autres
bibliothèques Java. Sous Mac OS, l’emplacement le plus indiqué est /Library/Java
pour une installation partagée, ou ~/Library/Java pour une installation personnelle
(où ~ désigne votre home directory).
2.2 Installation
17
Aucune installation additionnelle n’est à effectuer. Si vous examinez le contenu
du dossier que vous venez d’extraire, vous y trouverez :
• des fichiers JAR qui contiennent les différentes parties de la bibliothèque GWT
elle-même ;
• des scripts (fichiers .cmd dans la versions Windows, shell scripts dans la version
Mac ou Linux) qui encapsulent différentes commandes utilitaires de GWT ;
• un sous-dossier doc contenant une partie de la documentation, à savoir la
référence de l’API GWT (la javadoc). À noter que le reste de la documentation
est disponible uniquement en ligne ;
• des exemples prêts à être exécutés.
Un point de départ qui peut s’avérer utile est le fichier index.html dans le dossier
principal de GWT qui contient des liens vers différentes ressources GWT sur le Web,
la javadoc, les exemples, etc.
2.2.4 Google Plugin pour Eclipse
Le plugin GWT pour Eclipse de Google se trouve à l’adresse suivante :
http://code.google.com/eclipse/
Si votre machine de développement est connectée à l’Internet, il est préférable
d’utiliser le gestionnaire de téléchargements intégré à Eclipse pour installer ce plugin.
1. Démarrez Eclipse
2. Sélectionnez le menu Help/Software Updates...
3. Cliquez sur le bouton Manage Sites...
4. Cliquez sur le bouton Add...
5. Entrez l’URL du plugin : http://dl.google.com/eclipse/plugin/3.4 et validez avec
OK.
6. Le site apparaît dans la liste comme « Google Update Site for Eclipse 3.4 ».
Désélectionnez tous les sites à l’exception de celui-ci (ceci évite à Eclipse d’aller
vérifier des mises à jour inutiles, ce qui lui prend beaucoup de temps). Validez
avec OK.
7. Acceptez la licence qui vous est proposée.
8. Laissez le téléchargement et l’installation se dérouler. S’il vous est proposé de
redémarrer Eclipse pour prendre en compte les nouvelles fonctionnalités, il est
préférable d’accepter.
18
Chapitre 2. Hello, GWT
2.3 ANATOMIE D’UN PROJET GWT
GWT inclut des utilitaires qui permettent de créer un squelette d’application GWT
pouvant servir de base à un développement. Depuis GWT 1.6, le squelette ainsi créé
contient même un exemple d’implémentation et d’utilisation d’un service distant
(RPC).
Cependant, ce squelette est déjà relativement complexe pour aborder les concepts
GWT de base ; nous allons donc plutôt introduire les éléments de GWT plus
progressivement au travers d’un classique « Hello World » minimaliste.
2.3.1 Nommage des packages et structure des dossiers
Les applications GWT sont des projets particuliers car ce sont avant tout des
applications « web » au sens classique du terme, mais une fois chargées dans le
navigateur, elles se comportent comme des clients lourds.
La structure d’un projet GWT reflète cette dualité ; en effet il contient une partie
qui doit être déployée de façon classique sur le serveur, à savoir les pages et fichiers
statiques ainsi que le code Java compilé (servlets par exemple), mais aussi le code
GWT qui lui doit être transformé en JavaScript par le compilateur GWT.
Pour matérialiser cette distinction, le code GWT doit se trouver dans un package
particulier, par convention un sous-package nommé client. Par exemple, si le
package principal de votre application est info.gerardin.monappli, le code
GWT dit « traduisible » (translatable code) doit se trouver dans le package
info.gerardin.monappli.client.
Pour le reste, fort heureusement le projet se structure comme un projet web
classique :
• le code source Java ainsi que les ressources associées se trouvent sous un dossier
source désigné (en principe src) ;
• les fichiers et pages statiques (HTML, JSP, ...) se placent dans un dossier war,
qui servira de base à la création du fichier .WAR déployable ;
• dans ce dossier war, on trouve également un sous-dossier WEB-INF qui contient
un certain nombre de ressources nécessaires au bon déploiement :
– le fichier web.xml permettant de configurer l’application et les éventuels
servlets,
– le sous-dossier lib contenant les bibliothèques Java utilisées (fichiers .JAR),
– le sous-dossier classes contenant les classes Java compilées et les ressources.
Le compilateur GWT placera également les fichiers générés dans un sous-dossier
de war, de sorte qu’une fois le code Java classique compilé et le code GWT traduit en
JavaScript, le dossier war soit prêt pour générer un fichier .WAR déployable.
2.3 Anatomie d’un projet GWT
19
2.3.2 La page HTML hôte
Au final, le code GWT que vous allez écrire est destiné à tourner à l’intérieur d’une
page web sous forme de JavaScript. Par conséquent, même si votre application va
occuper tout l’écran, il lui faut une page web pour l’accueillir : c’est ce qu’on appelle
la page HTML hôte (HTML host page).
Cette page hôte est une page HTML des plus classiques, à ceci près qu’elle doit
faire référence à un des fichiers JavaScript générés par le compilateur GWT au moyen
du tag <SCRIPT>. Ce fichier sert de point d’entrée et va charger le reste du code GWT
compilé, ou plus précisément la version appropriée au navigateur sur lequel il va
s’exécuter. Nous reviendrons sur ce mécanisme appelé « Deferred Binding », dans le
chapitre 7.
Voici un exemple de page HTML hôte minimale :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Mon application GWT</title>
<script type="text/javascript" language="javascript"
src="monappli/monappli.nocache.js"></script>
</head>
<body>
</body>
</html>
Dans l’exemple ci-dessus, la ligne importante est la ligne en gras ; elle charge le
script JavaScript de bootstrap qui va initialiser toute la mécanique GWT. monappli
désigne le nom du module GWT qui contient la description de l’application (nous y
reviendrons plus loin).
Comme vous le voyez, le contenu de la page peut rester totalement vierge ; dans
ce cas, c’est le code JavaScript qui créera tous les éléments visuels d’interaction via le
DOM.
Cependant, GWT peut aussi s’intégrer facilement dans une page HTML existante :
il suffit pour cela d’y ajouter l’élément <SCRIPT> obligatoire ; le code GWT peut alors
faire référence à des éléments existants de la page au moyen de leur identifiant HTML
(attribut id) pour y attacher des éléments additionnels, comme dans l’exemple page
suivante.
20
Chapitre 2. Hello, GWT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Mon application GWT</title>
<script type="text/javascript" language="javascript"
src="monappli/monappli.nocache.js"></script>
</head>
<body>
<h1>Titre statique</h1>
<table align="center">
<tr>
<td colspan="2" style="font-weight:bold;">URL:</td>
</tr>
<tr>
<td id="nameFieldContainer"></td>
<td id="sendButtonContainer"></td>
</tr>
<tr>
<td colspan="2" style="font-weight:bold;">Résultat :</td>
</tr>
<tr>
<td colspan="2" id="result"></td>
</tr>
</table>
</body>
</html>
Ici une partie de la page est construite par le HTML présent dans le fichier, le reste
sera généré par le code GWT.
Le cas échéant, si aucun des éléments n’est identifié, on peut toujours accéder à la
« racine » de la page HTML et s’y « greffer » grâce à la méthode RootPanel.get().
2.3.3 Le point d’entrée
Tout comme une application Java classique, une application GWT possède un « point
d’entrée ».
Dans le cas d’une application Java classique, il s’agit d’une méthode public static
main(String args[]) dans une classe quelconque, que vous passez en paramètre à la
commande java pour démarrer l’application.
Dans le cas de GWT, il s’agit d’une méthode avec le profil public void onModuleLoad().
Contrairement à la méthode main décrite ci-dessus, onModuleLoad() n’est pas
static. En fait, la classe qui contient le point d’entrée doit implémenter l’interface
com.google.gwt.core.client.EntryPoint, qui contient la seule méthode onModuleLoad(). Cela signifie qu’avant d’appeler onModuleLoad(), la classe doit être instanciée
par GWT ; à cet effet elle doit exposer un constructeur public sans arguments.
2.3 Anatomie d’un projet GWT
21
Une autre différence importante avec une application classique : il est possible avec
GWT de définir plusieurs points d’entrée. Ceux-ci seront déclarés dans la configuration
du module GWT, et lors de l’initialisation de GWT, chacune des classes déclarées
comme point d’entrée sera instanciée et sa méthode onModuleLoad() appelée.
2.3.4 Le fichier module
Jusqu’à présent, nous avons considéré une application GWT comme monolithique ;
mais que se passe-t-il si on veut composer une application de plusieurs parties
indépendantes et réutilisables ? C’est pour cette raison qu’existe la notion de module.
Selon les propres termes de Google, un module est « l’unité de configuration »
GWT. Cela signifie qu’un module comprend tout ce qui est nécessaire pour compiler,
exécuter, déployer, réutiliser votre projet GWT.
Le module est défini au travers d’un fichier XML, dans lequel on trouvera en
particulier :
• la liste des classes contenant un point d’entrée à instancier ;
• les modules « hérités » selon la terminologie GWT ; il s’agit en vérité des
modules utilisés par votre module (par exemple des bibliothèques de composants
externes). À noter que les différents « thèmes » graphiques, c’est-à-dire l’habillage de base des composants, sont considérés comme des modules externes ;
• le source path, c’est-à-dire la liste des packages qui doivent être convertis
en JavaScript lors de la compilation GWT. Les sous-packages d’un
package du source path sont automatiquement inclus dans le source path ;
par exemple si on désigne com.sfeir.gwt.client, les éventuels packages
com.sfeir.gwt.client.xxxxx seront aussi inclus dans le source path. Par défaut,
le source path comprend le sous-package nommé client immédiatement en
dessous de l’endroit où se trouve le fichier XML du module.
Remarques
À propos du point d’entrée : Si le module GWT est destiné à être utilisé par d’autres
modules, il est parfaitement licite de ne définir aucun point d’entrée. En revanche, tout
module référencé directement dans une page HTML (au travers de la balise SCRIPT)
doit contenir au moins un point d’entrée.
À propos du source path : Grâce à cette technique, il est parfaitement possible de
« partager » certaines classes entre la partie « client » d’une application GWT (convertie
en JavaScript) et le reste du code, notamment la partie serveur. Néanmoins, il faut
garder à l’esprit les contraintes qui pèsent sur le code convertible (voir § 3.2 Contraintes
sur le code Java).
Le fichier de module a un suffixe .gwt.xml. Il peut se trouver n’importe où dans le
classpath du projet, mais il est conseillé de le placer dans le package racine de votre
projet.
22
Chapitre 2. Hello, GWT
Voici un exemple de fichier module tel que généré par GWT :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN"
"http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distrosource/core/src/gwt-module.dtd">
<module rename-to=’gwtskel’>
<!-- Inherit the core Web Toolkit stuff.
-->
<inherits name=’com.google.gwt.user.User’/>
<!-- Inherit the default GWT style sheet. You can change
<!-- the theme of your GWT application by uncommenting
<!-- any one of the following lines.
<inherits name=’com.google.gwt.user.theme.standard.Standard’/>
<!-- <inherits name=’com.google.gwt.user.theme.chrome.Chrome’/>
<!-- <inherits name=’com.google.gwt.user.theme.dark.Dark’/>
-->
-->
<!-- Other module inherits
-->
<!-- Specify the app entry point class.
<entry-point class=’oge.gwt.skel.client.GwtApp’/>
</module>
-->
-->
-->
-->
L’attribut rename-to de l’élément module est important, car il permet de définir
un « alias » au nom logique du module, qui est utilisé en particulier comme dossier
de base pour tous les fichiers générés par la compilation JavaScript. En l’absence de
rename-to, le nom du module est le nom du package dans lequel il se trouve suivi
de son nom (sans .gwt.xml) ; par exemple, si le module se trouve dans le fichier
GwtSkeleton.gwt.xml, situé dans le package oge.gwt.skel, le nom logique du module
est oge.gwt.skel.GwtSkeleton. Avec l’attribut rename-to de notre exemple, le nom
logique du module devient gwtskel.
2.4 HELLO, GWT
Nous allons maintenant assembler les éléments vus ci-avant (page hôte, module, point
d’entrée) et construire un premier projet GWT minimaliste, au moyen d’Eclipse et de
Google Plugin.
2.4.1 Création du projet
Dans Eclipse, créez un nouveau projet : Menu File > New... > Projet.
Une liste de types de projets vous est proposée ; choisissez Dynamic Web Project
puis cliquer sur Next (figure 2.1).
Donnez un nom au projet (« GwtChap24Hello » par exemple), et laissez les autres
paramètres à leur valeur par défaut. Cliquez sur Next (figure 2.2).
23
2.4 Hello, GWT
Figure 2.1 — Choix du type de projet
Figure 2.2 — Options de création
24
Chapitre 2. Hello, GWT
Dans l’écran suivant, spécifiez « war » comme valeur pour Content Directory. Ceci
est important pour que les réglages d’Eclipse correspondent à ceux de GWT. Cliquez
sur Finish (figure 2.3).
Figure 2.3 — Options du module web
2.4.2 Création du module GWT
Le projet est maintenant créé, il reste à activer le Google Plugin pour ce projet. Pour
cela, faites un clic droit sur le projet puis sélectionnez Google > Web Toolkit Settings.
Dans la fenêtre qui s’ouvre, cochez la case Use Google Web Toolkit, et sélectionnez la
version à utiliser. Cliquez sur OK (figure 2.4).
Créez un package racine pour le projet : clic droit sur le projet puis sélectionnez
New > Package. Nommez-le par exemple oge.gwt.chap24.hello, puis cliquez sur Finish
(figure 2.5).
On va maintenant créer un module GWT : clic droit sur le projet, puis sélectionnez
New > Other...
25
2.4 Hello, GWT
Figure 2.4 — Propriétés GWT du projet
Figure 2.5 — Création d’un nouveau package
26
Chapitre 2. Hello, GWT
Dans la fenêtre qui s’ouvre, choisissez Google Web Toolkit > Module puis cliquez sur
Next (figure 2.6).
Figure 2.6 — Création d’un nouveau module GWT
Dans l’écran suivant (New GWT Module) :
• cliquez sur Browse en face de Package et sélectionnez la package que vous avez
créé ci-avant ;
• dans Module Name, donnez un nom à votre module, par exemple HelloGwt
(figure 2.7) ;
• cliquez sur Finish ; le fichier module HelloGwt.gwt.xml est créé et ouvert. Vous
pouvez noter que le sous-package client a été créé automatiquement.
Figure 2.7 — Options de création du nouveau module
27
2.4 Hello, GWT
2.4.3 Création du point d’entrée
On doit créer au minimum une classe avec un point d’entrée pour initialiser l’application. Pour cela, faites un clic droit sur le projet et New > Other...
Dans la fenêtre qui s’ouvre, choisissez Google Web Toolkit > Entry Point class puis
cliquez sur Next (figure 2.8).
Figure 2.8 — Création d’un point d’entrée
Dans l’écran suivant, donnez un nom à la classe : HelloGwt et laissez les autres
paramètres par défaut. Cliquez sur Finish (figure 2.9).
Figure 2.9 — Options de création du point d’entrée
28
Chapitre 2. Hello, GWT
Vous pouvez noter qu’une ligne référençant le point d’entrée a été ajoutée dans le
fichier module. Pour vérifier que le point d’entrée est bien invoqué, ajoutez la ligne
suivante dans la méthode onModuleLoad() de la classe qui vient d’être créée :
RootPanel.get().add(new Label("Ca marche !"));
2.4.4 Création de la page HTML hôte
Reste à créer la page HTML hôte : clic droit et New > Google Web Toolkit > HTML
Page puis Next (figure 2.10).
Figure 2.10 — Création d’une page HTML hôte pour GWT
Donnez un nom à la page : Hello.html. Laissez les autres paramètres par défaut et
cliquez sur Finish (figure 2.11).
Figure 2.11 — Options de création de la page
29
2.4 Hello, GWT
2.4.5 Lancement de l’application
Voilà, tout est en place... Pour lancer l’application, il suffit de faire un clic droit puis
de sélectionner Run As > Web Application ; si tout est correct, l’application se lance
en hosted mode, ce mode spécial qui permet d’exécuter le code Java GWT sans le
transformer en JavaScript (nous y reviendrons dans le chapitre suivant).
Vous devriez voir la fenêtre « shell » du mode hôte présentée en figure 2.12, et la
fenêtre « navigateur » qui affichera la page hôte et au final notre label, en figure 2.13.
Figure 2.12 — Fenêtre shell du hosted mode
Figure 2.13 — Fenêtre navigateur
Certes cette première application GWT ne fait pas grand chose d’utile, mais
l’infrastructure est en place pour commencer à coder...
3
Développer avec GWT
Objectif
Le développement avec GWT est sensiblement différent du développement Java
standard, de par la nature de l’environnement ciblé (navigateur web). Dans ce
chapitre, nous allons voir quelles sont les différences avec le développement Java
standard et comment GWT permet au développeur de travailler au quotidien.
3.1 HOSTED MODE VS WEB MODE
À la fin du chapitre précédent, nous avons lancé notre application minimaliste
au travers d’une configuration de lancement (launch configuration) créée pour nous
automatiquement par le Google Plugin. Nous avons vu que cette application s’est
exécutée dans une fenêtre intitulée « Google Web Toolkit hosted mode ».
En réalité, le « mode hébergé » (hosted mode) est une des deux façons d’exécuter
une application GWT : dans ce mode, c’est le code Java compilé (bytecode) qui
s’exécute directement dans une JVM (Java Virtual Machine, machine virtuelle Java)
comme pour une application classique. Cependant, il ne s’agit pas d’une application
classique puisqu’elle est destinée au final à être utilisée dans un navigateur web ; le
principe même est que toute son interface utilisateur repose sur la création et la
manipulation de la page HTML au travers de l’API DOM.
Comment alors cette application peut-elle s’exécuter directement en Java ? Le
hosted mode embarque en fait un moteur de rendu HTML comme celui qu’on trouve
dans les navigateurs web, à ceci près qu’il est spécialement modifié pour pouvoir
exécuter nativement le code Java compilé, là où un navigateur classique ne comprend
32
Chapitre 3. Développer avec GWT
que le JavaScript. Dans la version Windows de GWT, ce moteur est celui d’Internet
Explorer ; dans la version Mac, il s’agit de WebKit, le moteur de base de Safari, et
dans la version Linux, une version modifiée de Mozilla.
L’exécution d’une application GWT en hosted mode a un énorme avantage : comme
il s’agit de pur Java, il est possible de positionner des points d’arrêt (breakpoints) là où
on le désire, de démarrer l’application en mode debug, de faire de l’exécution en pas à
pas, d’examiner le contenu des variables, etc. Toutes choses que quiconque s’est essayé
à la programmation JavaScript directe appréciera... À noter que si votre application
comprend une partie serveur (ce qui sera très probablement le cas), ceci s’applique
aussi au code serveur, pour peu que le serveur soit aussi du code Java qui puisse tourner
en mode debug. La plupart des IDE Java fournissent une bonne intégration avec les
serveurs d’application et facilitent cet usage.
Autre intérêt non négligeable : après une modification du code, un simple clic sur le
bouton Refresh du navigateur hosted mode recharge l’application avec les modifications.
Ceci autorise des cycles codage/test courts, car la compilation en JavaScript n’est pas,
elle, des plus rapides.
Le postulat central de GWT, c’est que si votre application fonctionne comme
attendu en mode hôte, alors après sa compilation en JavaScript elle se comportera
exactement de la même manière dans les navigateurs supportés (et à quelques exceptions près, c’est généralement le cas). Par conséquent, en tant que programmeur GWT
vous passerez probablement la plupart de votre temps à exécuter votre application en
mode hôte, et ne la compilerez en JavaScript que de temps en temps pour vérifier que
tout fonctionne comme prévu sur la plate-forme finale.
3.2 CONTRAINTES SUR LE CODE JAVA
Le principe même de GWT, c’est-à-dire le fait que le code que vous écrivez ne sera
pas le code final qui sera exécuté dans le navigateur, mais qu’il devra passer par
une étape de transformation en JavaScript et ne tournera pas dans une JVM mais
dans un interpréteur JavaScript, impose certaines contraintes et limitations lors du
développement. Ces contraintes portent sur le langage lui-même, ainsi que sur les
classes du Java Runtime Environment (JRE, environnement d’exécution Java) qui sont
utilisables.
3.2.1 Support du langage
Lorsque vous développez le code client d’une application GWT, l’ensemble des
possibilités du langage Java est disponible, y compris l’utilisation des génériques, des
annotations et plus généralement des nouveautés apportées par Java 5.
Cependant, il faut garder à l’esprit que la cible est JavaScript, et certaines caractéristiques de Java ne disposent pas d’équivalent en JavaScript. Parmi les principales
différences qui sont les plus susceptibles de nécessiter une attention particulière, on
peut citer les suivantes.
3.2 Contraintes sur le code Java
33
• Mono-thread : Le modèle d’exécution de JavaScript est mono-thread (un seul fil
d’exécution), et donc votre application le sera aussi. Le mot-clé synchronized
•
•
•
•
est accepté par le compilateur GWT, mais n’a aucun effet. De même, il est
impossible de démarrer un nouveau thread via la classe Thread. Cette limitation
aura des conséquences importantes sur la façon d’accéder à des services distants
(RPC).
Pas de réflexion : Java permet de charger une classe dynamiquement à
l’exécution, par exemple grâce à Class.forName(), et l’instancier grâce à
Class.newInstance() ; cette possibilité n’existe pas en JavaScript, car la
totalité des classes utilisées doit être connue par le compilateur GWT au
moment de la compilation. Attention, car cela peut sembler fonctionner très
bien en mode hôte, et ce n’est que lors de l’exécution en mode web que cela
produira une erreur. De même, il est impossible dans le code client d’énumérer
les propriétés ou méthodes d’une classe.
Gestion des exceptions : Le support des exceptions est similaire à celui de
Java, avec toutefois quelques différences notables. En particulier, certaines
exceptions non contrôlées (unchecked) comme NullPointerException, StackOverflowException ou OutOfMemoryException ne sont jamais levées dans le code
JavaScript ; à la place une exception générique JavaScriptException est levée.
Les valeurs qui pourraient être nulles doivent donc être testées explicitement
(comme ça devrait être toujours le cas en bon Java...). D’autre part, l’exception
levée en JavaScript est relativement pauvre en informations ; getStackTrace()
par exemple ne retourne malheureusement rien de très exploitable, ce qui peut
s’avérer gênant dans le cas où une exception se produit dans la version JavaScript
de votre code mais pas dans sa version Java... heureusement ce cas est rarissime.
Support partiel de long : le type long n’est pas supporté en JavaScript ;
cependant cela n’est pas un problème dans le code qui est compilé en JavaScript,
car le compilateur GWT gère cela de façon totalement transparente (grâce à une
paire d’int) ; il faut toutefois en être conscient lorsqu’il s’agit de communiquer
avec du JavaScript natif via JSNI.
Pas de support de finalize : JavaScript ne supporte pas la finalisation durant
le passage du ramasse-miettes (garbage collector) ; par conséquent, la méthode
finalize n’est jamais exécutée.
3.2.2 Émulation des classes du JRE
Le code Java qu’on peut écrire utilise en permanence, qu’on en soit conscient ou non,
des classes et des méthodes qui font partie de la bibliothèque de base du JRE. Ces
classes sont celles qui permettent de manipuler les objets natifs tels que String ou
Integer, mais aussi les exceptions, les collections, etc.
Pour pouvoir exécuter en JavaScript l’équivalent de ce que votre code Java
effectue avec ces classes, le code JavaScript doit pouvoir accéder à un équivalent de
la bibliothèque de base du JRE ; c’est le rôle que remplit la bibliothèque d’émulation
JRE de GWT. Cette bibliothèque consiste en un ensemble de classes JavaScript,
qui reproduit (« émule ») de façon aussi précise que possible le comportement
de leurs équivalents du JRE, dans les limites que permet JavaScript.
34
Chapitre 3. Développer avec GWT
Sont compris dans cette bibliothèque :
• la quasi totalité du package java.lang, qui comprend l’essentiel des classes de
base de Java, ainsi que java.lang.annotation ;
• une grande partie du package java.util, y compris les collections ;
• quelques classes des packages java.io et java.sql. Il n’est toutefois pas question
d’écrire des fichiers ou d’accéder à une base SQL, mais simplement de faciliter
la compatibilité.
Classes émulées
La table accessible en annexe 1 contient la liste des classes présentes dans la bibliothèque d’émulation JRE de GWT. Attention, dans certains cas, seul un sous-ensemble
des méthodes de la classe est implémenté. La liste complète des méthodes peut être
consultée sur la page :
http://code.google.com/webtoolkit/doc/1.6/RefJreEmulation.html
Autres différences
Il existe d’autres domaines pour lesquels l’émulation de GWT diffère du Java standard :
• le support des expressions régulières (regexp) est légèrement différent entre Java
et JavaScript ; GWT ne cherche pas à reproduire pas en JavaScript le comportement des expressions régulières Java, mais s’appuie sur l’implémentation native
de JavaScript. Ceci s’applique notamment aux méthodes String.replaceAll()
et String.split(). Il convient donc lorsqu’on appelle ces méthodes de s’assurer
qu’on utilise des expressions régulières qui ont la même sémantique en Java et
en JavaScript ;
• la notion de sérialisation en Java s’appuie fortement sur la réflexion qui, on
l’a vu, n’est pas disponible en JavaScript. La sérialisation Java n’est donc pas
utilisable dans le code client ; cependant, un mécanisme similaire existe dans le
but d’invoquer des méthodes distantes via RPC (voir le chapitre 5 à ce sujet).
Classes utilitaires
Pour certaines classes du JRE dont l’émulation aurait été trop coûteuse, GWT fournit
un équivalent « light » qui offre une fonctionnalité similaire mais simplifiée :
• com.google.gwt.i18n.client.DateTimeFormat supporte un sous-ensemble de
la fonctionnalité de java.util.DateTimeFormat ;
• com.google.gwt.i18n.client.NumberFormat supporte un sous-ensemble de la
fonctionnalité de java.util.NumberFormat ;
• com.google.gwt.user.client.Timer fournit une fonctionnalité similaire à
java.util.Timer, adaptée à l’environnement mono-thread.
L’utilisation des classes DateTimeFormat et NumberFormat est détaillée au § 6.3 Le
formatage des dates et nombres.
35
3.3 Lancer en mode hôte
3.3 LANCER EN MODE HÔTE
Le hosted mode est implémenté dans la classe com.google.gwt.dev.HostedMode, qui
se trouve dans le fichier gwt-dev-mac.jar (ou gwt-dev-windows.jar ou gwt-devlinux.jar, selon votre plate-forme de développement). Pour démarrer une application
GWT en mode hôte, il suffit d’exécuter une commande de la forme :
java -XstartOnFirstThread com.google.gwt.dev.HostedMode -startupUrl
HelloGwt.html HelloGwt.gwt.xml
HelloGwt.gwt.xml désigne le module principal de votre application, qui contient
toutes les informations nécessaires à l’exécution. Le paramètre startupUrl quant à
lui désigne une page web à ouvrir au démarrage, normalement la page hôte de votre
application. Bien sûr, pour que cette commande fonctionne, le classpath doit inclure le
JAR gwt-dev-xxx.jar, ainsi que tout le code client, en particulier le fichier de module.
Heureusement, il existe des façons plus simples de lancer une application en mode
hôte. Comme nous l’avons vu dans le chapitre précédent, le Google Plugin ajoute à
Eclipse un type de run configuration (configuration d’exécution) qui permet de démarrer
une application GWT très facilement depuis Eclipse.
Figure 3.1 — Configurations d’exécution GWT dans Eclipse
En outre, lorsque vous générez un squelette d’application au travers du même
plugin, la configuration d’exécution est créée automatiquement pour vous. De même
au travers de l’outil en ligne de commande webAppCreator inclus dans GWT, qui
génère en prime un fichier build.xml destiné à Ant, ce fichier comprend une cible
(target) nommée « hosted » qui démarre l’application en hosted mode.
36
Chapitre 3. Développer avec GWT
3.3.1 Le serveur d’applications intégré
Même s’il est parfaitement possible de créer une application GWT « stand-alone », la
plupart des applications GWT auront besoin tôt ou tard de communiquer avec un
serveur pour réaliser certaines opérations. Pour cette raison, le mode hôte de GWT
embarque un serveur d’applications qui permet de tester facilement à la fois le code
client (qui tourne dans le navigateur) et le code serveur (qui tourne en principe dans
un serveur web) en un seul environnement, ce qui est très commode.
Depuis la version 1.6, le serveur embarqué est Jetty, qui succède à Tomcat dans
les versions précédentes. Jetty est un serveur web et un moteur de servlets adapté
aux configurations embarquées grâce à son faible besoin en mémoire, et offrant des
performances comparables. Dans la pratique, peu de différences sont perceptibles, car
les deux implémentent la spécification « Java Servlets 2.5 ». Nous reviendrons sur le
développement de code serveur dans le chapitre 5.
Notez toutefois que sur des projets conséquents, il arrive fréquemment que la partie
serveur d’un projet ne puisse pas fonctionner dans le serveur embarqué avec le hosted
mode ; ceci arrive en particulier dès qu’on a besoin de faire appel à des fonctionnalités
avancés spécifiques à un serveur d’applications, typiquement lorsqu’on met en jeu
des mécanismes transactionnels ou des Entity JavaBeans (EJB). Dans ce cas, il suffit
de décocher l’option Run built-in server dans l’onglet principal de la configuration
d’exécution GWT, ou bien de passer l’option -noserver sur la ligne de commande qui
démarre le mode hôte ; ainsi le serveur intégré ne démarrera pas.
3.4 COMPILER ET LANCER EN MODE WEB
Lorsque vous voulez tester une application GWT comme si elle était déployée, c’est-àdire sous forme de JavaScript dans un navigateur classique, il faut passer par l’étape de
« compilation GWT », dont le but est de générer le code JavaScript correspondant au
code Java que vous avez écrit.
Cette opération est parfois nécessaire lorsque vous voulez contrôler les performances de votre application dans un navigateur natif ; en effet, en hosted mode votre
application est exécutée sous forme d’un mélange de code Java natif (votre propre code,
les bibliothèques GWT et les éventuelles bibliothèques tierces) et de JavaScript (par
exemple, les bibliothèques JavaScript intégrées directement dans la page HTML). Cet
environnement étant très différent de l’environnement cible (JavaScript seulement),
il peut exister des différences de performance importantes entre les deux, dans un
sens comme dans l’autre. En général, on constate que le code orienté « interface
utilisateur » est sensiblement plus performant en web mode qu’en hosted mode, mais
cela peut varier dans un sens comme dans l’autre.
Une option simple pour tester l’application en mode web est de cliquer sur le
bouton Compile/Browse du navigateur du mode hôte ; ceci a pour effet de déclencher
la compilation JavaScript, et lorsque celle-ci est terminée, ouvre la page hôte dans le
navigateur par défaut de votre système. Cette possibilité est intéressante car elle ne
nécessite aucun paramétrage, mais elle a comme inconvénient de ne pas pouvoir être
3.4 Compiler et lancer en mode web
37
intégrée dans une chaîne de construction automatisée, et de bloquer le mode hôte
durant tout le temps de la compilation. Heureusement, d’autres options sont offertes.
Figure 3.2 — Bouton Compile/Browse du navigateur du mode hôte
Le compilateur GWT est implémenté dans la classe com.google.gwt.dev.Compiler,
qu’on peut appeler depuis une ligne de commande de la façon suivante :
java com.google.gwt.dev.Compiler Hello.gwt.xml
Si le projet a été généré par l’outil webAppCreator, une cible ant spécifique
nommée gwtc (qui est également la cible par défaut) est incluse dans le fichier
build.xml généré, de sorte que l’appel à la commande :
ant gwtc
ou tout simplement :
ant
va déclencher la compilation GWT.
3.4.1 Les fichiers générés par le compilateur GWT
Pour exprimer les choses très simplement, le but du compilateur GWT est de générer
une application JavaScript équivalente à votre application Java. Il va donc examiner
votre code source et générer un ensemble de fichiers aux noms cryptiques qui
contiennent le code JavaScript et d’autres ressources nécessaires au fonctionnement
de l’application en mode web.
La compilation GWT n’est pas une opération simple ; en effet le compilateur
doit prendre en compte les différentes plates-formes cibles pour générer du code
qui fonctionne à l’identique sur toutes celles-ci. Plutôt que de générer un seul code
38
Chapitre 3. Développer avec GWT
JavaScript et d’y inclure des tests pour s’adapter au navigateur, les développeurs de
GWT ont pris le parti de générer une variante de code JavaScript par plate-forme
cible. Le fichier qui est référencé dans la page HTML hôte ne sert qu’à détecter
la plate-forme courante, et passer la main au code JavaScript spécifique à celle-ci.
Cette approche est nettement plus performante car elle réduit le volume du code
à télécharger, et rend inutile tout test de plate-forme ultérieur. En contrepartie, la
phase de compilation est plus longue puisqu’il faut générer autant de variantes que de
plates-formes supportées (cinq pour GWT 1.6/1.7).
Le compilateur GWT crée (normalement sous le dossier war) un sous-dossier par
module, portant le nom du module. Dans chacun de ces dossiers, on retrouvera un
certain nombre de fichiers aux noms peu évocateurs, par exemple :
testapp/14A43CD7E24B0A0136C2B8B20D6DF3C0.cache.png
testapp/29F4EA1240F157649C12466F01F46F60.gwt.rpc
testapp/346766FA9D2CAEC39CC4D21F46562F34.cache.html
...
testapp/FCF795F77D04980E723A4DFA05A7926B.cache.html
testapp/FDFBC219465FCAB905EA55951EE425FA.cache.html
testapp/clear.cache.gif
testapp/hosted.html
testapp/testapp.nocache.js
Ces fichiers contiennent les différentes variantes de code généré pour chacune
des plates-formes cibles. Le seul fichier qui nous intéresse véritablement ici est le
fichier testapp.nocache.js (où testapp est le nom de votre module), qui contient le
bootstrap, c’est-à-dire le minimum de code qui va déterminer la plate-forme courante
et charger les fichiers adéquats. C’est ce fichier que vous devrez référencer dans la page
HTML hôte de votre application (voir § 2.3.2 La page HTML hôte).
3.5 DÉVELOPPEMENT AVEC L’IDE
Lorsque vous développez avec GWT, il y a fort à parier (nous l’espérons !) que vous
travaillerez depuis un IDE tel Eclipse. Dans ce cas, comment se passe l’intégration de
votre projet GWT dans l’IDE ?
Tout d’abord, pour l’IDE, un projet GWT est un projet Java comme un autre : il est
constitué de code source Java qui sera compilé en bytecode, et de ressources qui seront
copiées telles quelles. Dans les IDE modernes (y compris Eclipse), la compilation est
incrémentale et se produit au fur et à mesure que vous sauvegardez le code source ; il
n’est pas utile de déclencher manuellement une compilation. Par conséquent, votre
projet est toujours prêt à être lancé en hosted mode, pour peu que vous ayez défini une
configuration d’exécution appropriée.
3.5 Développement avec l’IDE
39
3.5.1 Debug et cycle de développement
Comme toute application Java, une application GWT peut également être démarrée
en mode debug. C’est ici que GWT montre tout son intérêt, puisque vous pouvez
utiliser votre IDE favori (Eclipse, IntelliJ, Netbeans ou autres) pour positionner des
points d’arrêt dans le code Java, et utiliser toutes les fonctionnalités du débogueur
intégré que vous utiliseriez pour une application Java : inspecter le contenu des
variables, avancer en pas à pas, etc. Il est loin d’être aussi aisé de faire la même chose
en JavaScript.
Une fois votre application démarrée en hosted mode, vous serez naturellement
amené à modifier (et donc recompiler) votre code. En principe, si l’application a été
lancée en mode debug par l’IDE, ce dernier tentera de recharger la nouvelle version
des classes dans la JVM en cours d’exécution (hotswap). Dans une application Java
classique, cela fonctionne dans un certain nombre de cas, avec certaines limitations
(pas de modification du profil de la classe par exemple) ; en pratique dans une
application GWT cela ne fonctionne pas à cause des spécificités du hosted mode. Faut-il
en conclure qu’après chaque modification du code, il faut relancer complètement le
hosted mode ? Heureusement non, les concepteurs de GWT ont prévu ce cas : la fenêtre
principale du hosted mode est pourvue d’un bouton Reload qui a pour effet de recharger
l’application, sans pour autant redémarrer le hosted mode.
Figure 3.3 — Bouton Reload du navigateur du mode hôte
La plupart du temps, il est donc commode de laisser les fenêtres du hosted mode
ouvertes en parallèle à l’IDE ; lorsque des modifications sont à tester, on bascule vers
le hosted mode et on recharge l’application via le bouton Reload.
40
Chapitre 3. Développer avec GWT
Figure 3.4 — Résumé du cycle de développement
Attention toutefois à certains points :
• Si votre application possède une partie serveur déployée sur un serveur web hors
du hosted mode et que vous intervenez sur le code de cette partie serveur, il faudra
redéployer vos modifications sur le serveur web. Exception : si le serveur est
démarré en mode debug depuis l’IDE et que vos modifications sont compatibles
avec le hotswap de la JVM, elles seront prises en compte sans redéploiement.
• Si votre application accède à des ressources générées lors de la compilation
GWT, il pourra s’avérer nécessaire de relancer la compilation GWT.
• Après un certain temps d’utilisation, on constate que le hosted mode devient de
plus en plus lent. Fuite mémoire ? En tout cas, un simple redémarrage remet les
choses en ordre.
4
Widgets, panels, etc.
Objectif
Un des aspects les plus attractifs de GWT est la possibilité de construire une interface
graphique dynamique à base de composants de type « widgets » qu’on trouve dans
des toolkits comparables comme Swing. Nous allons voir dans ce chapitre quels
composants offre GWT et comment les combiner dans des panels.
4.1 UNE INTERFACE GRAPHIQUE DYNAMIQUE
Même si on ne peut pas le résumer à cela, la caractéristique le plus souvent mise en
avant de GWT est la possibilité de construire une interface homme-machine (IHM,
ou Graphical User Interface GUI) dynamique. Qu’est-ce qu’une interface dynamique ?
Pour simplifier, on peut dire que c’est une interface :
• polymorphe, c’est-à-dire qui présente à l’utilisateur des aspects variables (par
exemple, un nœud d’un arbre peut être développé ou réduit) ;
• réactive, c’est-à-dire qui produit un feedback instantané aux actions de l’utilisa-
teur (par exemple, montrer par un indicateur que le champ en cours de saisie
n’est pas valide).
42
Chapitre 4. Widgets, panels, etc.
Comme nous l’avons évoqué au début de ce livre, les interfaces web « traditionnelles » sont loin d’être dynamiques, pour deux raisons principales :
• la pauvreté des composants de base : même s’ils se sont enrichis au cours du
temps, la base des applications web a longtemps été le formulaire avec ses
champs texte et un bouton submit. Même avec des composants additionnels
comme la case à cocher ou la liste déroulante, les possibilités restent limitées ;
• l’impossibilité de fournir un feedback tant qu’une requête n’a pas été soumise
au serveur. Outre le délai que cela induit, la page doit être rechargée en entier.
La combinaison de JavaScript (pour l’aspect réactif) avec DOM/DHTML et
CSS (pour l’aspect polymorphe) a finalement offert les mécanismes nécessaires à
la création de véritables interfaces dynamiques au sein d’une page web ; GWT offre
une abstraction de ces mécanismes derrière une architecture de composants similaires
à ce qu’offrent les toolkits d’IHM classiques tel Swing ou SWT.
4.1.1 Les widgets GWT
Comme pour les toolkits classiques, une IHM construite avec GWT consiste en
un assemblage de composants, appelés « widgets » dans la terminologie officielle.
Cependant, à la différence d’un toolkit classique, les widgets GWT sont rendus à
l’écran au travers de HTML généré dynamiquement, au lieu de graphismes dessinés.
Un des partis pris de GWT est d’utiliser les composants HTML natifs à chaque
fois que c’est possible ; par exemple le widget TextBox sera rendu par un élément
HTML <input>, Button par un <button>. Cela a plusieurs avantages : rapidité, légèreté,
accessibilité, et un rendu familier qui ne déroute pas les utilisateurs. En revanche,
d’autres composants seront rendus synthétiquement au travers de bitmaps et de <div>.
La façon précise dont un widget est rendu peut dépendre de la plate-forme cible,
mais là encore c’est totalement transparent pour le programmeur et en pratique un
widget se comportera de la même façon sur les différents navigateurs.
4.1.2 GWT et les aspects graphiques
Alors que les toolkits classiques s’appuient sur des API permettant de définir les
attributs des composants (couleur, trait, police, etc.), il aurait été très complexe de
mettre en œuvre une telle stratégie dans un toolkit comme GWT qui, au final, produit
du HTML. Au lieu de cela, GWT a adopté une approche plus simple et plus naturelle,
puisque tous les aspects de stylisation de GWT reposent sur l’utilisation de styles CSS.
L’association de styles aux widgets se fait simplement dans le code GWT au travers de
méthodes comme addStyleName() ou removeStyleName().
Cette approche a l’avantage de séparer clairement la logique applicative des aspects
de design, ce qui facilite le travail collaboratif puisqu’il est plus aisé de travailler
indépendamment sur l’un et sur l’autre. En outre, cela offre de meilleures performances
et permet de faire des modifications de style sans recompiler l’application.
4.2 Widgets et panels
43
4.2 WIDGETS ET PANELS
Les widgets GWT peuvent être rangés en deux catégories : les widgets simples, et les
panels, ces derniers ayant la caractéristique de pouvoir contenir d’autres widgets (on
reconnaît là le fameux design pattern « composite »). La construction d’une interface
utilisateur se fait habituellement – comme pour les bibliothèques d’IHM classiques –
par composition, en instanciant des widgets et en les ajoutant à des panels.
Le rôle d’un widget est de permettre l’interaction avec l’utilisateur, que ce soit
simplement en affichant un texte (Html), en réagissant à un clic (Button), en autorisant
la saisie de texte (TextBox), etc. Par contraste, le rôle d’un panel est de gérer la
disposition (layout) de ses enfants, c’est-à-dire des widgets qu’il contient.
Widget gallery
Le « catalogue » des widgets et panels GWT est visible sur la page
http://code.google.com/webtoolkit/doc/1.6/RefWidgetGallery.html
avec
des
exemples pour chacun d’entre eux. Il peut être utile de garder ce lien dans ses favoris
comme référence en ligne.
4.2.1 Événements et Handlers
Le concept d’événement est central dans le fonctionnement des widgets GWT,
puisqu’il permet la réactivité de l’interface. Un événement est généré lorsqu’une
situation particulière se produit, par exemple un clic de l’utilisateur sur un bouton.
Pour qu’un événement puisse déclencher une action, il doit être associé à un handler.
Concrètement, un handler est une interface qui définit une ou plusieurs méthodes que
le widget appellera lorsque l’événement correspondant se produira. Par exemple, pour
déclencher une action sur un clic, il faut implémenter l’interface ClickHandler qui
contient une unique méthode onClick(ClickEvent). Le widget Button génère des
événements de type Click (il implémente HasClickHandlers), on peut donc lui fournir
une instance de ClickHandler via la méthode addClickHandler() ; chaque fois que
l’événement se produira, le widget appellera la méthode onClick() du ClickHandler
qu’on lui aura fourni.
À titre d’exemple, voici comment associer un ClickHandler à un Button :
class MyHandler implements ClickHandler {
/**
* appelé lorsque l’utilisateur clique sur le bouton.
*/
public void onClick(ClickEvent event) {
// Faire quelque chose...
}
}
// Associer une instance du handler au bouton
MyHandler handler = new MyHandler();
boutton.addClickHandler(handler);
44
Chapitre 4. Widgets, panels, etc.
Toutes les méthodes des interfaces handlers reçoivent en paramètre une référence
vers l’événement à l’origine de leur invocation. Cet objet (GwtEvent) contient en
particulier la source de l’événement, c’est-à-dire le widget d’origine (dans notre cas le
bouton) ; ceci permet de partager un handler entre plusieurs widgets, le handler pouvant
adopter un comportement différent en fonction de la source.
La documentation (Javadoc) de chaque widget spécifie précisément quels événements sont susceptibles d’être générés par celui-ci.
4.2.2 Widgets simples
GWT propose une gamme de widgets suffisante pour construire une application
complète et riche. Cependant, si vos besoins vous orientent vers des composants
plus complets ou spécialisés, GWT laisse la possibilité aux développeurs de créer des
widgets personnalisés de façon très simple (voir le chapitre 10) ; vous pourrez alors
vous tourner vers une des bibliothèques de composants disponibles, ou vous atteler
vous-même à la création de widgets.
Nous allons présenter dans un premier temps la gamme de widgets simples (c’est-àdire non composites) de GWT.
Button
Un Button est un bouton simple qui déclenche une action lorsque l’utilisateur le
presse. Son label est défini par un fragment de HTML, ce qui permet d’y mettre autre
chose qu’un simple texte.
Figure 4.1 — Widget Button (actif, inactif)
L’action à effectuer lors du clic est fournie au bouton sous forme d’une instance de
ClickHandler, de la façon suivante :
final Button button = new Button("Clic !");
// Handler pour le bouton
class MyHandler implements ClickHandler {
public void onClick(ClickEvent event) {
Window.alert("onClick()");
}
}
// Associer le handler au bouton
MyHandler handler = new MyHandler();
button.addClickHandler(handler);
45
4.2 Widgets et panels
De façon plus compacte, si le handler n’est pas utilisé par d’autres widgets, on peut
utiliser une classe imbriquée (inner class) anonyme, comme ceci :
final Button button = new Button("Clic !");
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
Window.alert("onClick()");
}
});
Voire même passer le handler directement au constructeur du Button :
final Button button = new Button("Clic !", new ClickHandler() {
public void onClick(ClickEvent event) {
Window.alert("onClick()");
}
});
PushButton
Un PushButton est un bouton graphique qui donne le contrôle total sur son apparence
au travers de différents styles CSS, selon son état.
Tableau 4.1 — Styles standards référencés par le PushButton
Style CSS
.gwt-PushButton-up
.gwt-PushButton-down
.gwt-PushButton-up-hovering
.gwt-PushButton-up-disabled
.gwt-PushButton-down-hovering
.gwt-PushButton-down-disabled
État du PushButton
Non enfoncé, pointeur en dehors
Enfoncé, pointeur en dehors
Non enfoncé, pointeur au-dessus
Non enfoncé, désactivé (disabled)
Enfoncé, pointeur au-dessus
Enfoncé, désactivé (disabled)
Différents constructeurs permettent de créer un PushButton avec des images
personnalisées différentes associées aux états enfoncé (up) et non enfoncé (down).
De même que pour Button, on peut associer à un PushButton un ClickHandler qui
sera invoqué lorsque l’utilisateur clique sur le bouton.
RadioButton
Un RadioButton est un genre de case à cocher (CheckBox) dont la particularité est
qu’un au plus parmi un groupe peut être coché à un instant t. La sélection d’un
RadioButton provoque la désélection de tous les autres du groupe.
Le groupe auquel appartient un RadioButton est désigné par une chaîne de
caractères passée à son constructeur. Attention, si le label contient du code HTML, il
faudra utiliser le constructeur prenant un booléen pour l’indiquer.
46
Chapitre 4. Widgets, panels, etc.
Par exemple :
// Créer des RadioButtons dans le groupe "rbGroup0"
RadioButton rb0 = new RadioButton("rbGroup0", "premier");
RadioButton rb1 = new RadioButton("rbGroup0", "deuxième", true);
RadioButton rb2 = new RadioButton("rbGroup0", "troisième", true);
// sélectionner le troisième
rb2.setValue(true);
// Les ajouter dans un Panel
FlowPanel panel = new FlowPanel();
panel.add(rb0);
panel.add(rb1);
panel.add(rb2);
RootPanel.get("contenu").add(panel);
Figure 4.2 — Un groupe de trois widgets RadioButton
Caractères accentués
Si vous n’utilisez pas l’encodage HTML, vous pouvez toutefois inclure des caractères
accentués dans les labels à condition que vos fichiers source soient encodés en UTF-8.
Ce réglage se fait en principe dans les préférences de l’IDE, par exemple dans Eclipse
on le trouve dans General > Workspace sous l’intitulé « Text file encoding ».
CheckBox
Une CheckBox est une simple case à cocher, pouvant prendre deux états : coché
ou non coché. Elle possède également un label, et peut déclencher un événement
ValueChanged lorsque sa valeur (sélectionnée ou pas) change.
//Créer la checkbox
final CheckBox checkBox = new CheckBox("cochez-moi !");
// Attacher un handler sur l’événement "ValueChanged"
checkBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
public void onValueChange(ValueChangeEvent<Boolean> event) {
checkBox.setText(event.getValue() ? "décochez-moi..." :
"cochez-moi !");
}
});
RootPanel.get("contenu").add(checkBox);
Figure 4.3 — Un widget CheckBox
47
4.2 Widgets et panels
DatePicker
DatePicker est un widget qui permet de sélectionner une date de façon interactive
grâce à un calendrier. Le calendrier présente une vue d’un mois complet, et on peut
se déplacer d’un mois en avant ou en arrière. Malheureusement, l’implémentation de
base ne permet pas de sauter facilement à un mois ou une année donnée.
Bien évidemment, une valeur de type Date est associée à ce widget. Lorsque le
DatePicker est instancié, il affiche la date correspondant à cette valeur ; un clic sur la
case d’un jour positionne la valeur à ce jour.
// créer un label (sans texte encore)
final Label dateLabel = new Label();
final DateTimeFormat dateFormat = DateTimeFormat.getFormat("dd/MM/yyyy");
// créer le DatePicker
DatePicker datePicker = new DatePicker();
datePicker.addValueChangeHandler(new ValueChangeHandler<Date>() {
public void onValueChange(ValueChangeEvent<Date> event) {
// positionne le texte du label avec la date sélectionnée
Date selection = event.getValue();
dateLabel.setText(dateFormat.format(selection));
};
});
// positionner la date actuelle
Date now = new Date();
datePicker.setValue(now, true); // true pour déclencher ValueChangedHandler
RootPanel.get("contenu").add(datePicker);
RootPanel.get("contenu").add(dateLabel);
Figure 4.4 — Widget DatePicker
Dans la figure 4.4 illustrant le widget DatePicker, on remarque que la valeur initiale
de la date reste visible (case encadrée).
48
Chapitre 4. Widgets, panels, etc.
ToggleButton
Un ToggleButton est un bouton à deux états (non enfoncé, enfoncé). Contrairement
à un PushButton, sa raison d’être n’est pas de déclencher une action, mais de
basculer entre deux états ; de ce point de vue, il est fonctionnellement équivalent
à une CheckBox.
En revanche, il est similaire au PushButton en ce sens qu’il est destiné à être stylé
au travers de styles CSS. Les deux widgets héritent de la classe abstraite CustomButton
dans laquelle sont définies les variantes de styles CSS appliqués selon l’état du bouton.
Tableau 4.2 — Styles référencés par le ToggleButton
Style CSS
.gwt-ToggleButton-up
.gwt-ToggleButton-down
.gwt-ToggleButton-up-hovering
.gwt-ToggleButton-up-disabled
.gwt-ToggleButton-down-hovering
.gwt-ToggleButton-down-disabled
État du ToggleButton
Non enfoncé, pointeur en dehors
Enfoncé, pointeur en dehors
Non enfoncé, pointeur au-dessus
Non enfoncé, désactivé (disabled)
Enfoncé, pointeur au-dessus
Enfoncé, désactivé (disabled)
Exemple :
// créer un label
final Label toggleLabel = new Label();
// créer le ToggleButton
final ToggleButton toggleButton = new ToggleButton("NON", "OUI");
toggleButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// en cas de clic, positionner le texte du label
// selon l’état du ToggleButton
if (toggleButton.isDown()) {
toggleLabel.setText("Je suis enfoncé !");
}
else {
toggleLabel.setText("Je ne suis pas enfoncé !");
}
}
});
// ajouter au Panel
RootPanel.get("contenu").add(toggleButton);
RootPanel.get("contenu").add(toggleLabel);
Figure 4.5 — Widget ToggleButton (état non enfoncé, état enfoncé)
49
4.2 Widgets et panels
TextBox
TextBox est un champ de saisie textuelle classique, qui permet d’entrer du texte sur
une ligne (voir TextArea pour une saisie multilignes).
TextBox génère de nombreux événements, en particulier ceux liées à la saisie de
texte (KeyUpEvent, KeyDownEvent, KeyPressEvent). Il est possible de lui associer une
longueur maximale (de texte autorisé) et un nombre de caractères visibles.
Voici un petit exemple qui associe à une TextBox un compteur qui affiche en
permanence le nombre de caractères tapés dans la TextBox :
// Créer la TextBox
final TextBox textBox = new TextBox();
textBox.setMaxLength(20); // pas plus de 20 caractères saisis
textBox.setVisibleLength(10); // pas plus de 10 caractères affichés à la fois
// Label qui servira de compteur
final Label compteurLabel = new Label();
textBox.addKeyUpHandler(new KeyUpHandler() {
public void onKeyUp(KeyUpEvent event) {
// positionne le label en fonction de la longueur du texte entré
String text = textBox.getText();
compteurLabel.setText("" + text.length()+ " caractère(s)");
}
});
// ajouter au Panel
RootPanel.get("contenu").add(textBox);
RootPanel.get("contenu").add(compteurLabel);
Figure 4.6 — Widget TextBox (avec un compteur de caractères)
PasswordTextBox
Un PasswordTextBox est simplement une TextBox dont les caractères tapés par
l’utilisateur sont masqués à l’écran. Pour le reste, tout est identique.
Figure 4.7 — Widget PasswordTextBox (avec un compteur de caractères)
50
Chapitre 4. Widgets, panels, etc.
TextArea
TextArea est une zone de saisie de texte similaire à TextBox, à cette différence près
qu’elle peut s’étendre sur plusieurs lignes. On peut en définir la largeur en termes de
nombre de caractères (sur base d’une valeur moyenne), et un nombre de lignes visibles.
// Créer la TextArea
final TextArea textArea = new TextArea();
textArea.setCharacterWidth(40); // largeur approx 40 caractères
textArea.setVisibleLines(5); // 5 lignes visibles
// Label qui servira de compteur de voyelles/consonnes
final Label compteur2Label = new Label();
textArea.addKeyUpHandler(new KeyUpHandler() {
public void onKeyUp(KeyUpEvent event) {
// positionne le label en fonction du nombre de consonnes et voyelles
String text = textArea.getText();
int cons = 0;
int voy = 0;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if ("aeiou".indexOf(c) >= 0) {
voy++;
} else if ("bcdfghjklmnpqrstvwxyz".indexOf(c) >= 0) {
cons++;
}
}
compteur2Label.setText("" + voy + " voyelles, " + cons + " consonnes");
}
});
// ajouter au Panel
RootPanel.get("contenu").add(textArea);
RootPanel.get("contenu").add(compteur2Label);
Figure 4.8 — Widget TextArea (avec un compteur de consonnes/voyelles)
51
4.2 Widgets et panels
Hyperlink
Hyperlink est un hyperlien qui fonctionne en conjonction avec le système de gestion
d’historique de GWT (lire à ce sujet le chapitre 8).
Attention, il ne s’agit pas d’un hyperlien générique ! Si on souhaite créer un
hyperlien générique, on utilisera simplement le widget HTML comme ceci :
HTML hyperlink = new HTML(
"<a href=’http://code.google.com/webtoolkit’>" +
"Google WebToolkit" +
"</a>");
ListBox
ListBox présente une liste de choix à l’utilisateur, qui peut se présenter soit comme
un widget intégré à son conteneur, soit dans un popup affiché à la demande.
À chaque élément de la liste est associé, en plus du label qui est affiché, une valeur
qui est cachée. La valeur peut par exemple contenir un code ou un identifiant qui
n’est pas destiné à être affiché.
ListBox autorise la sélection simple (un seul élément sélectionné à la fois)
ou multiple (plusieurs éléments sélectionnables). En mode de sélection multiple,
l’utilisateur peut sélectionner ou désélectionner un élément individuel en cliquant
sur celui-ci en combinaison avec la touche habituelle de sa plate-forme (Ctrl pour
Windows et Linux, Cmd pour Mac OS).
Tableau 4.3 — Différentes utilisations du widget ListBox
Sélection simple
Instanciation
new ListBox();
Sélection multiple
new ListBox(true);
ou
new ListBox(false);
getSelectedIndex()
Récupération
de la sélection
courante
Positionnement setSelectedIndex(i)
de la sélection
courante
isItemSelected(i)
setItemSelected(i,selected)
52
Chapitre 4. Widgets, panels, etc.
Exemple :
// Création de la ListBox
final ListBox listBox = new ListBox(true);
// ajout des éléments et codes associés
listBox.addItem("lundi", "lun");
listBox.addItem("mardi", "mar");
listBox.addItem("mercredi", "mer");
listBox.addItem("jeudi", "jeu");
listBox.addItem("vendredi", "ven");
listBox.addItem("samedi", "sam");
listBox.addItem("dimanche", "dim");
// Label qui servira de résumé
final Label resumeLabel = new Label();
// handler sur le changement de sélection de la ListBox
listBox.addChangeHandler(new ChangeHandler() {
public void onChange(ChangeEvent event) {
// concatène simplement les codes des éléments sélectionnés
StringBuffer buf = new StringBuffer();
for (int i = 0; i < listBox.getItemCount(); i++) {
if (listBox.isItemSelected(i)) {
buf.append(listBox.getValue(i) + " ");
}
}
resumeLabel.setText(buf.toString());
}
});
// ajouter au Panel
RootPanel.get("contenu").add(listBox);
RootPanel.get("contenu").add(resumeLabel);
Figure 4.9 — Une ListBox avec les jours de la semaine
53
4.2 Widgets et panels
MenuBar
MenuBar est le widget de base qui sert à la construction des menus. Un MenuBar :
• peut être horizontal ou vertical ;
• contient une liste d’éléments, chaque élément étant associé :
– soit à un objet de type Command (exécution d’une action),
– soit à un autre MenuBar (ouverture d’un sous-menu en popup).
De nombreux styles CSS sont associés au MenuBar et permettent de contrôler son
apparence.
Tableau 4.4 — Styles référencés par le MenuBar
Style CSS
.gwt-MenuBar
.gwt-MenuBar-horizontal
.gwt-MenuBar-vertical
.gwt-MenuBar .gwt-MenuItem
.gwt-MenuBar .gwt-MenuItem-selected
.gwt-MenuBar .gwt-MenuItemSeparator
.gwt-MenuBar .gwt-MenuItemSeparator
.menuSeparatorInner
.gwt-MenuBarPopup .menuPopup
{Top|Middle|Bottom}
{Left|LeftInner|Center|CenterInner|
Right|RightInner}
Élément concerné
La barre de menu proprement dite
Style appliqué aux barres de menu
horizontales
Style appliqué aux barres de menu
verticales
Élément de menu
Élément de menu sélectionné
Séparateurs entre les éléments de menu
Composant intérieur du séparateur
Composants des cellules des menus
popups ; respectivement :
– haut, milieu, bas
– gauche, gauche (intérieur), centre, centre
(intérieur), droite, droite (intérieur)
Tree
Tree représente une arborescence de widgets que l’on peut développer, réduire et
sélectionner.
Chaque nœud de l’arbre est constitué par une instance de TreeItem, qui :
• peut contenir un simple label textuel ou un widget quelconque ;
• peut avoir un nombre quelconque d’enfants (eux-mêmes des TreeItem).
54
Chapitre 4. Widgets, panels, etc.
On peut combiner texte et widgets dans un Tree à volonté, comme le montre
l’exemple suivant :
TreeItem racine = new TreeItem("Noeud Racine");
TreeItem noeud1 = new TreeItem("Elements Texte");
noeud1.addItem("Noeud textuel");
noeud1.addItem("<b>HTML pour la mise en forme</b>");
racine.addItem(noeud1);
TreeItem noeud2 = new TreeItem("Widgets");
noeud2.addItem(new CheckBox("Case à cocher"));
HorizontalPanel panelNode = new HorizontalPanel();
panelNode.add(new Label("Et même une TextBox: "));
panelNode.add(new TextBox());
noeud2.addItem(panelNode);
racine.addItem(noeud2);
Tree tree = new Tree();
tree.addItem(racine);
panel.add(tree);
Figure 4.10 — Une arborescence de widgets
SuggestBox
SuggestBox est une TextBox complétée d’un mécanisme qui fait apparaître des
suggestions au fur et à mesure que l’utilisateur introduit le texte. Si une suggestion
correspond à ce que souhaite l’utilisateur, il peut alors la sélectionner et ainsi abréger
sa frappe.
Chaque SuggestBox fonctionne en collaboration avec un SuggestOracle
qui est responsable de fournir les suggestions en fonction du texte déjà saisi. Par
défaut, SuggestBox utilise MultiWordSuggestOracle qui tire ses propositions d’une
liste statique ; cependant, il est tout à fait possible d’écrire un SuggestOracle
qui interroge un serveur au moyen de RPC ou une autre méthode (voir chapitre 5)
pour construire sa liste de propositions.
Pour implémenter un SuggestOracle, il suffit d’étendre la classe abstraite
SuggestOracle et d’implémenter la méthode abstraite :
public void requestSuggestions(Request request, Callback callback)
4.2 Widgets et panels
55
Cette méthode sera appelée par la SuggestBox lorsqu’elle souhaitera obtenir une
liste de suggestions. Dans le profil de cette méthode, Request est une inner class de
SuggestOracle qui contient les données de la requête, en particulier le texte saisi, et
Callback est une interface définie également dans SuggestBox, dont il faudra appeler
la méthode :
void onSuggestionsReady(Request request, Response response)
lorsque des suggestions seront prêtes.
La Response passée au callback sert essentiellement à encapsuler une liste de
suggestions sous la forme d’objets implémentant l’interface Suggestion. Cette dernière
interface expose deux méthodes :
String getDisplayString();
ou
String getReplacementString();
La première fournit la chaîne qui sera affichée par la SuggestBox dans la liste de
choix proposés, la seconde fournit la chaîne qui sera effectivement utilisée pour le
remplacement.
À titre d’exemple, on va implémenter un SuggestOracle qui propose de capitaliser
le texte saisi, c’est-à-dire de passer tout en majuscules ou minuscules. Pour commencer
il faut définir une implémentation de Suggestion afin de pouvoir retourner les
propositions à la SuggestBox. Il s’agit d’une simple classe non mutable qui encapsule
les deux informations (replacementString et displayString) :
class SimpleSuggestion implements Suggestion {
private final String replacementString;
private final String displayString;
public SimpleSuggestion(String replacementString,
String displayString) {
this.replacementString = replacementString;
this.displayString = displayString;
}
public SimpleSuggestion(String replacementString) {
this(replacementString, replacementString);
}
public String getReplacementString() {
return replacementString;
}
public String getDisplayString() {
return displayString;
}
}
56
Chapitre 4. Widgets, panels, etc.
On peut maintenant définir le SuggestOracle :
// Un SuggestOracle qui offre toujours deux propositions:
// une correspondant au texte saisi passé en minuscules, l’autre
// en majuscules.
class CapsOracle extends SuggestOracle {
@Override
public void requestSuggestions(Request request, Callback callback) {
String query = request.getQuery();
Suggestion s0 = new SimpleSuggestion(query.toUpperCase());
Suggestion s1 = new SimpleSuggestion(query.toLowerCase());
Response response = new Response(Arrays.asList(s0, s1));
callback.onSuggestionsReady(request, response);
}
}
Pour utiliser ce SuggestOracle, il suffit de le passer au constructeur de SuggestBox :
SuggestBox suggestBox = new SuggestBox(new CapsOracle());
Figure 4.11 — La SuggestBox avant sélection
Figure 4.12 — La SuggestBox après sélection
On peut également réagir à la sélection d’une proposition de l’oracle en attachant
un SelectionHandler à la SuggestBox. Les événements générés sont de type SelectionEvent<Suggestion> :
suggestBox.addSelectionHandler(new SelectionHandler<Suggestion>() {
public void onSelection(SelectionEvent<Suggestion> event) {
GWT.log("Suggestion choisie: "
+ event.getSelectedItem().getDisplayString(), null);
}
});
Ceci peut s’avérer utile lorsqu’on souhaite effectuer des actions supplémentaires
(autres que simplement remplacer le texte), ou bien récupérer des informations
supplémentaires contenues dans l’objet Suggestion.
La SuggestBox et son menu popup associé sont personnalisables au moyen de styles
CSS.
57
4.2 Widgets et panels
Tableau 4.5 — Styles référencés par la SuggestBox
Style CSS
.gwt-SuggestBox
.gwt-SuggestBoxPopup
.gwt-SuggestBoxPopup .item
.gwt-SuggestBoxPopup .item-selected
.gwt-SuggestBoxPopup .suggestPopup
{Top|Middle|Bottom}
{Left|LeftInner|Center|CenterInner|
Right|RightInner}
Élément concerné
La TextBox proprement dite
Le menu popup de choix
Une suggestion
Une suggestion sélectionnée
Composants des cellules du menu popup ;
respectivement :
– haut, milieu, bas
– gauche, gauche (intérieur), centre, centre
(intérieur), droite, droite (intérieur)
RichTextArea
Une RichTextArea est un éditeur de texte qui autorise l’utilisateur à entrer du texte
mis en forme (gras, italique, etc.). Le contenu est récupérable et positionnable sous
forme de code HTML. Ce composant est souvent utilisé pour saisir du texte destiné à
être publié ou en général lu par des humains.
Attention car le support de ce composant est fortement dépendant du navigateur
cible... Son implémentation peut donc varier d’un navigateur à l’autre.
Contrairement à ce qu’on pourrait imaginer, ce composant ne contient pas de
barre d’outils ni aucun contrôle permettant à l’utilisateur d’effectuer le formatage ;
c’est entièrement au programmeur de fournir ce genre de contrôle. Pour interagir avec
le contenu de la RichTextArea, les contrôles peuvent utiliser l’une ou l’autre des classes
suivantes :
• BasicFormatter fournit des fonctions de formatage de base (gras, italique,
souligné, etc.) ;
• ExtendedFormatter étend BasicFormatter en y ajoutant des fonctions plus
avancées (liens, images, listes, etc.).
Les références vers les instances de ces formateurs s’obtiennent via les méthodes
et RichtextArea.getExtendedFormatter().
Attention toutefois car, selon le navigateur, ces méthodes peuvent retourner null si
le formateur demandé n’est pas disponible sur la plate-forme de l’utilisateur !
RichTextArea.getBasicFormatter()
Dans l’exemple suivant, on instancie une RichTextArea, et on y ajoute deux
boutons, un bouton Gras qui fait appel à la méthode toggleBold() du BasicFormatter
pour passer en gras la sélection courante, et un bouton Barré qui fait appel à la méthode
toggleStrikeThrough() pour barrer la sélection courante. À noter qu’on n’instancie
chacun de ces boutons que si le formateur auquel il fait appel n’est pas null, ce qui
garantit que la fonctionnalité correspondante est disponible.
58
Chapitre 4. Widgets, panels, etc.
VerticalPanel vpanel = new VerticalPanel();
vpanel.setSpacing(15);
HorizontalPanel buttonPanel = new HorizontalPanel();
vpanel.add(buttonPanel);
RichTextArea area = new RichTextArea();
vpanel.add(area);
final BasicFormatter basicFormatter = area.getBasicFormatter();
if (basicFormatter != null) {
Button gras = new Button("Gras");
buttonPanel.add(gras);
gras.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
basicFormatter.toggleBold();
}
});
}
final ExtendedFormatter extendedFormatter = area.getExtendedFormatter();
if (extendedFormatter != null) {
Button barre = new Button("Barré");
buttonPanel.add(barre);
barre.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
extendedFormatter.toggleStrikethrough();
}
});
}
RootPanel.get().add(vpanel);
Figure 4.13 — Widget RichTextArea (avec contrôles associés)
DialogBox
DialogBox est une classe de base pour créer des fenêtres de dialogue. Elle possède un
titre et peut être déplacée par l’utilisateur. Son contenu est un widget qu’on lui passera
via setWidget(), après quoi on pourra appeler la méthode show() pour afficher le
dialogue.
Un DialogBox peut optionnellement être modal, c’est-à-dire bloquer toute interaction avec des widgets autres que lui-même tant qu’il est affiché. Il peut également
être en auto hide, c’est-à-dire se masquer automatiquement si on clique en dehors. Ces
propriétés sont spécifiées lors de l’instanciation du DialogBox.
59
4.2 Widgets et panels
Si on veut construire un dialogue complet sur base de DialogBox, on pourra
procéder comme suit :
/**
* DialogBox contenant un TextBox et un bouton OK.
*/
class InputDialog extends DialogBox {
/**
* Créer le dialogue
* @param title le titre
*/
public InputDialog(String title) {
super(false, true); // autohide désactivé, modal activé
setText(title);
// panel qui sera le contenu du widget
VerticalPanel panel = new VerticalPanel();
panel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
// ajout de la TextBox
panel.add(new TextBox());
// ajout du bouton OK
Button okButton = new Button("OK");
okButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// un clic sur le bouton OK masque le dialogue
hide();
}
});
panel.add(okButton);
// spécifier le panel comme contenu du dialogue
setWidget(panel);
}
}
Le nouveau dialogue s’utilise alors de la façon suivante :
Button showDialogButton = new Button("afficher DialogBox");
showDialogButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
new InputDialog("Votre nom ?").show();
}
});
panel.add(showDialogButton);
Figure 4.14 — Une boîte de dialogue simple
60
Chapitre 4. Widgets, panels, etc.
On peut remarquer au passage qu’au lieu d’utiliser DialogBox directement, on
l’a sous-classé pour en faire InputDialog ; ce faisant on a naturellement et presque
sans s’en rendre compte créé un nouveau widget GWT ! L’héritage est une des
méthodes possibles pour créer son propre widget, vous pourrez en savoir davantage au
chapitre 10.
4.2.3 Panels
Les panels sont des widgets un peu spéciaux, qui ont la caractéristique de pouvoir
contenir d’autres widgets. Tous les panels exposent une méthode add(Widget) qui
permet l’ajout d’un widget au panel, et remove(Widget) qui permet de retirer un
widget du panel.
En outre, la plupart des panels génériques offrent aussi des méthodes permettant
d’accéder aux widgets-fils :
• getWidgetCount() retourne le nombre de widgets-fils ;
• getWidget(int n) retourne le widget à l’index spécifié ;
• iterator() retourne un itérateur pour parcourir les widgets-fils ;
• remove(int n) permet de retirer un widget-fils désigné par son index.
Selon le cas, le panel peut aussi exposer des méthodes spécifiques, par exemple
AbsolutePanel possède une méthode add(Widget, int, int) qui ajoute un widget-fils
à une position absolue.
Il est important de comprendre qu’à chaque type de panel est associé un layout,
c’est-à-dire une façon de disposer les widgets-fils à l’intérieur du panel. Par exemple,
les widgets peuvent être simplement disposés l’un à droite du précédent (HorizontalPanel), l’un par-dessus l’autre (DeckPanel), dans des cellules individuelles (HTMLTable),
etc.
RootPanel
RootPanel est un panel particulier puisqu’il représente le « point d’attache » d’une
arborescence de widgets GWT dans la page HTML hôte. Un RootPanel n’est jamais
créé explicitement par l’application GWT, mais il est obtenu :
• soit via la méthode RootPanel.get(String id), qui retourne un RootPanel
attaché à l’élément HTML possédant l’id spécifié dans la page HTML hôte ;
• soit via la méthode RootPanel.get(), qui retourne un RootPanel par défaut
attaché à l’élément BODY de la page hôte. Cette méthode fonctionne toujours
même si aucun id n’est défini dans la page.
Si l’on veut qu’ils soient visibles, les widgets créés par l’utilisateur devront
obligatoirement, directement ou indirectement, être attachés à un RootPanel.
61
4.2 Widgets et panels
AbsolutePanel
Un AbsolutePanel est un panel qui positionne ses fils via des coordonnées absolues.
La superposition totale ou partielle de widgets est permise.
Exemple :
AbsolutePanel panel = new AbsolutePanel();
panel.setPixelSize(200, 200);
Button b1 = new Button("Bouton 1");
panel.add(b1, 50, 50);
Button b2 = new Button("Bouton 2");
panel.add(b2, 70, 65);
Figure 4.15 — AbsolutePanel avec deux boutons qui se superposent
PopupPanel
Un PopupPanel est un panel destiné à être affiché sous forme de popup, c’est-à-dire
par-dessus les autres widgets de l’application. À cet effet, il ne doit pas être ajouté à un
autre panel, mais doit être affiché/masqué au moyen des méthodes show() et hide().
PopupPanel est une sous-classe de SimplePanel, c’est-à-dire un panel qui n’accepte
qu’un seul widget-fils ; par conséquent, pour construire un PopupPanel complexe,
il faudra l’encapsuler dans un panel approprié, et ce dernier sera déclaré comme
widget-fils du PopupPanel. À noter que le fait d’appeler une seconde fois la méthode
add() sur un SimplePanel se traduit par une IllegalStateException.
L’exemple suivant illustre une utilisation possible de PopupPanel pour implémenter
un embryon d’infobulle (tooltip) : on crée un bouton, et on y associe un MouseOverHandler qui affiche un PopupPanel (supposé contenir un message d’aide) à la position
de la souris.
final Button bouton = new Button("Bouton");
final PopupPanel popup = new PopupPanel(true);
Label label = new Label("Ceci est un bouton.");
popup.add(label);
bouton.addMouseOverHandler(new MouseOverHandler() {
public void onMouseOver(MouseOverEvent event) {
// le pointeur de la souris survole le bouton:
// afficher le popup
popup.setPopupPosition(event.getClientX(), event.getClientY());
popup.show();
// ajout d’un Timer pour faire disparaitre le popup après 2 s
Timer timer = new Timer() {
@Override
public void run() {
popup.hide();
}
62
Chapitre 4. Widgets, panels, etc.
};
timer.schedule(2000);
}
});
panel.add(bouton);
Notez l’utilisation d’un Timer pour faire disparaître automatiquement le PopupPanel après un délai si l’utilisateur ne clique pas en dehors.
Figure 4.16 — PopupPanel utilisé comme tooltip
StackPanel
Un StackPanel gère un ensemble de widgets-fils empilés verticalement, de sorte que
seul l’un d’entre eux soit visible à un instant donné. Chaque widget est précédé
d’un en-tête, et le fait de double-cliquer sur un en-tête va développer le widget-fils
correspondant, et réduire celui qui était précédemment développé s’il y en avait un.
Ce genre de dispositif est parfois appelé accordéon (accordion) dans d’autres toolkits.
Figure 4.17 — StackPanel avec trois éléments
HorizontalPanel et VerticalPanel
HorizontalPanel et VerticalPanel sont deux panels qui positionnent leurs widgets-fils
l’un à côté de l’autre, respectivement horizontalement et verticalement.
Les widgets-fils sont placés dans des cellules rectangulaires :
• la taille des cellules peut se régler via les méthodes setCellWidth() et setCellHeight() ;
• chaque widget peut être aligné indépendamment à l’intérieur de sa cellule via les
méthodes setCellHorizontalAlignement() et setCellVerticalAlignment().
En outre, on peut spécifier l’espacement entre deux cellules voisines via setSpacing(), et la largeur de la bordure avec setBorderWidth().
63
4.2 Widgets et panels
Figure 4.18 — HorizontalPanel avec spacing=15
Figure 4.19 — VerticalPanel avec spacing=15
FlowPanel
FlowPanel est un panel qui n’ajoute aucune contrainte sur ses fils ; le résultat est donc
mis en page comme le seraient des éléments HTML consécutifs. En général, le résultat
est que les widgets-fils sont positionnés l’un à côté de l’autre horizontalement, avec
un retour à la ligne en cas de débordement.
Figure 4.20 — FlowPanel
DeckPanel
DeckPanel positionne tous ses widgets-fils l’un sur l’autre comme une pile de cartes, de
sorte que seulement l’un d’entre eux (le sommet de la pile) soit visible à un moment
donné. Les lecteurs qui ont pratiqué Swing reconnaîtront l’équivalent du CardLayout.
Utilisation :
• les widgets-fils sont ajoutés normalement via la méthode add(Widget) ou
insert(Widget, int) ;
• le widget à placer en premier plan est désigné par la méthode showWidget(int).
À noter que ce panel sert de base au TabPanel (voir plus loin) dans lequel la
sélection du widget actif se fait via des onglets placés au nord.
64
Chapitre 4. Widgets, panels, etc.
VerticalSplitPanel et HorizontalSplitPanel
VerticalSplitPanel et HorizontalSplitPanel sont des panels qui acceptent unique-
ment deux widgets-fils et les placent respectivement l’un au-dessus de l’autre et l’un à
côté de l’autre, en permettant à l’utilisateur d’ajuster l’espace alloué à l’un et l’autre
widget en déplaçant la barre qui les sépare. Au besoin, des ascenseurs seront ajoutés si
un widget ne pouvait pas être contenu en totalité dans l’espace qui lui est alloué.
Utilisation :
• Pour un VerticalSplitPanel, on spécifie les widgets-fils avec setTopWidget() ou
setBottomWidget(), et on y accède avec getTopWidget() ou getBottomWidget().
• Pour un HorizontalSplitPanel, on spécifie les widgets-fils avec setLeftWidget() ou setRightWidget(), et on y accède avec getLeftWidget() ou getRightWidget().
• Dans les deux cas, on peut aussi utiliser la méthode add(Widget) commune
aux panels ; le premier appel positionnera le widget du haut (respectivement
de gauche), le second du bas (respectivement de droite). Tout appel ultérieur
résultera en une IllegalStateException.
• La position du séparateur entre les deux widgets se règle avec setSplitPosition(). Comme pour toutes les coordonnées, cette position s’indique en
unités CSS ; dans le cas du séparateur, il peut être judicieux de l’indiquer
en pourcentage, par exemple setSplitPosition("33%") donnera un tiers de
l’espace au premier composant.
Figure 4.21 — HorizontalSplitPanel séparant deux labels
Figure 4.22 — VerticalSplitPanel séparant deux labels
65
4.2 Widgets et panels
Dockpanel
DockPanel est un panel à la fois puissant et complexe, et qui peut servir de base à des
interfaces riches ; les lecteurs qui ont pratiqué Swing reconnaîtront sans doute une
ressemblance avec le fameux BorderLayout.
DockPanel divise son espace en cinq zones (nord, sud, est, ouest et centre), comme
le montre la figure 4.23.
Figure 4.23 — Les cinq zones de l’espace de DockPanel
L’ajout de widget-fils se fait grâce à la méthode add(Widget, DockLayoutConstant).
Le second paramètre précise l’endroit où sera placé le widget ; les valeurs possibles
sont :
• NORTH ou SOUTH : le composant sera placé au nord ou au sud ;
• EAST ou WEST : le composant sera placé à l’est ou à l’ouest ;
• LINE_START ou LINE_END sont des synonymes respectivement pour WEST et EAST
mais qui dépendent de la direction d’écriture. Dans les environnements où
l’écriture va de droite à gauche, la correspondance est inversée.
• CENTER : le composant sera placé au centre.
À l’exception du centre, il est possible d’ajouter plusieurs widgets dans une même
zone ; les widgets seront arrangés de l’extérieur vers l’intérieur.
Figure 4.24 — DockPanel avec plusieurs éléments par zone
Comme HorizontalPanel et VerticalPanel, DockPanel est un CellPanel et arrange
ses fils dans des cellules. Il est donc possible de la même manière de spécifier
l’alignement des widgets à l’intérieur des cellules et l’espacement entre ces derniers.
66
Chapitre 4. Widgets, panels, etc.
TabPanel
TabPanel est l’implémentation en GWT du classique sélecteur à onglets. Chaque
widget ajouté est associé à un onglet, et un clic sur un onglet fait passer au premier
plan le widget associé.
L’ajout d’un widget se fait avec une des méthodes :
• add(Widget, String) ajoute un widget avec le texte spécifié dans l’onglet ;
• add(Widget, String, boolean) ajoute un widget avec le texte spécifié dans
l’onglet, qui peut être du HTML si le booléen est true ;
• add(Widget, Widget) permet de mettre un widget quelconque dans l’onglet à
la place d’un simple texte. Ceci permet de créer des onglets constitués d’une
image par exemple.
Figure 4.25 — TabPanel
DisclosurePanel
DisclosurePanel qu’on pourrait traduire par « panneau escamotable » est un widget
composé d’un en-tête et d’un contenu ; le contenu peut se cacher ou s’afficher en
cliquant sur l’entête. En principe, ce dernier est précédé d’un petit triangle pointe à
droite pour indiquer l’existence d’un contenu non affiché, et pointe en bas lorsque ce
contenu est affiché.
Utilisation :
• utiliser le constructeur DisclosurePanel(String) pour créer un en-tête textuel,
ou DisclosurePanel(Widget) pour utiliser un widget comme en-tête ;
• utiliser add(Widget) ou setContent(Widget) pour spécifier le contenu du panel
(c’est à dire la partie escamotable) ;
• utiliser setOpen(boolean) pour passer programmatiquement de l’état ouvert à
fermé ou vice versa.
Grid et FlexTable
Les tableaux en GWT peuvent être manipulés via deux classes :
• Grid est une table rectangulaire qu’on doit dimensionner explicitement ;
• FlexTable est une table qui s’agrandit automatiquement au fur et à mesure que
des cellules sont créées. La table n’est pas forcément rectangulaire (chaque ligne
peut contenir un nombre différent de cellules), et des cellules peuvent être
fusionnées.
67
4.2 Widgets et panels
Figure 4.26 — DisclosurePanel (fermé, ouvert)
Toutes deux héritent de la classe abstraite HTMLTable, et partagent un certain
nombre de caractéristiques ; en particulier, chaque cellule peut contenir du texte, du
HTML, ou un widget quelconque.
L’utilisation de Grid est très simple :
• instancier Grid avec les dimensions initiales (lignes × colonnes) ;
• positionner le contenu des cellules avec setText pour du texte simple, setHTML
pour du HTML ou setWidget pour insérer un widget. La cellule doit exister
auparavant, faute de quoi une IndexOutOfBoundsException sera levée ;
• utiliser insertRow, removeRow, resizeColumns, resizeRows ou resize pour agir
dynamiquement sur les dimensions de la table.
L’utilisation de FlexTable est légèrement différente :
• instancier FlexTable (le constructeur ne prend pas de paramètres) ;
• positionner le contenu des cellules avec setText pour du texte simple, setHTML
pour du HTML ou setWidget pour insérer un widget. Si une cellule n’existe pas,
la table sera étendue à la taille nécessaire ;
• utiliser
getFlexCellFormatter().setColSpan
ou
getFlexCellFormatter().setRowSpan pour fusionner une cellule avec ses
voisines ;
• utiliser éventuellement insertRow, removeRow ou addCell pour agir dynamiquement sur les dimensions de la table.
À titre d’exemple, nous allons créer une FlexTable avec une « cellule baladeuse »
qu’on pourra déplacer dans les quatre directions au moyen de boutons ; au fur et à
mesure qu’elle se déplacera vers la droite ou vers le bas, les cellules seront créées au
besoin.
68
Chapitre 4. Widgets, panels, etc.
Tout d’abord, on définit une sous-classe de Button, dont l’appui appellera la
méthode move (définie ultérieurement) en lui passant des valeurs fixées à la création
du bouton, correspondant à un décalage vertical et horizontal.
// sous-classe de Button spécialisée qui fait appel à la méthode move
// avec les paramètres passés à son constructeur
class DirButton extends Button{
public DirButton(String label, final int deltax, final int deltay) {
super(label);
addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
move(deltax, deltay);
}
});
}
}
La table est créée comme suit :
private
private
private
private
FlexTable table;
VerticalPanel buttonPanel;
int row = 0;
int col = 0;
// créer la FlexTable
table = new FlexTable();
table.setBorderWidth(1);
table.setCellPadding(25);
La « cellule baladeuse » sera constituée d’un panel qui regroupera les quatre
boutons directionnels :
// créer le groupe de boutons qui constituera la "cellule baladeuse"
buttonPanel = new VerticalPanel();
buttonPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
buttonPanel.add(new DirButton("^", -1, 0));
HorizontalPanel hPanel = new HorizontalPanel();
hPanel.add(new DirButton("<", 0, -1));
hPanel.add(new DirButton(">", 0, +1));
buttonPanel.add(hPanel);
buttonPanel.add(new DirButton("v", +1, 0));
On place le groupe de boutons à la position initiale, c’est à dire (0, 0) :
//positionner initialement le groupe de boutons
table.setWidget(row, col, buttonPanel);
// ajouter la table
RootPanel.get("contenu").add(table);
69
4.2 Widgets et panels
Il reste à définir la méthode move qui sera appelée par les boutons directionnels
lorsque ceux-ci seront pressés :
/**
* Déplace le groupe de boutons
* @param deltax décalage horizontal
* @param deltay décalage vertical
*/
protected void move(int deltax, int deltay) {
// retirer le groupe de boutons de la table
table.remove(buttonPanel);
// calculer les nouvelles positions
row += deltax;
if (row < 0) {
row = 0;
}
col += deltay;
if (col < 0) {
col = 0;
}
// replacer le groupe de boutons
table.setWidget(row, col, buttonPanel);
}
Et le résultat après avoir fait faire un peu de chemin à la cellule baladeuse
(figure 4.27).
Figure 4.27 — La FlexTable et sa cellule baladeuse
Le listing complet de cet exemple est accessible en annexe 2.
5
Communiquer avec le serveur
Objectif
Dans ce chapitre, nous allons détailler les principes et l’utilisation du mécanisme de
RPC (Remote Procedure Call) de GWT, qui permet d’effectuer des appels à la partie
serveur de l’application.
5.1 CODE CLIENT VS CODE SERVEUR
Une différence fondamentale entre les applications AJAX (GWT y compris) et les
applications web traditionnelles est qu’à la différence de ces dernières, une application
AJAX n’a besoin que de récupérer les données dont elle a besoin, pas de recharger la
totalité de la page HTML.
Par cet aspect, une application développée avec GWT est essentiellement similaire
à une application client-serveur traditionnelle, et de la même manière elle doit donc
partager les responsabilités entre le client et le serveur. Une partie du code réside
donc sur un serveur, et est invoquée par le client, par exemple lorsqu’il y a besoin de
récupérer des données pour rafraichir une liste, etc.
Lorsque le code client GWT (c’est-à-dire le code qui s’exécute dans le navigateur)
a besoin de communiquer avec le serveur, la manière la plus naturelle de le faire est
d’utiliser le mécanisme de RPC (Remote Procedure Call, appel de procédure à distance)
standard de GWT, sur lequel portera l’essentiel de ce chapitre. Cependant, ce n’est
pas une obligation, et il est également possible d’appeler des services de nature diverse
comme des web services ou des services encodant les données avec JSON.
72
Chapitre 5. Communiquer avec le serveur
5.2 LES PRINCIPES DE GWT RPC
Le mécanisme de RPC promu par GWT s’appuie sur la norme Servlets Java, qui est
le standard de fait pour l’implémentation de services distants en Java. Les services
ainsi implémentés peuvent donc être déployés sur tous les serveurs d’application Java
implémentant la spécification « servlets », c’est-à-dire l’immense majorité d’entre
eux : Tomcat, jetty, GlassFish, WebSphere, WebLogic, etc. En utilisant ce mécanisme
RPC, on bénéficie également de l’optimisation du transport des objets sérialisés (via
le compilateur GWT), ce qui n’est pas forcément le cas pour les autres mécanismes.
Il peut paraître inutile de le repréciser, mais le compilateur GWT ne convertit
que le code « client » en JavaScript... GWT n’influe en rien sur votre capacité
à exécuter du bytecode natif dans une JVM standard côté serveur. En particulier,
les limitations qui s’appliquent au code Java client n’ont pas cours pour le code
serveur ! Vous êtes libre d’employer toutes les bibliothèques Java et toutes les
techniques de programmation que vous souhaitez.
Un point important à avoir en tête lorsqu’on écrit du code client serveur avec
GWT est la notion d’asynchronisme : tous les appels RPC sont forcément asynchrones.
Qu’est-ce que cela signifie ? Concrètement, cela veut dire que lorsqu’on effectue un
appel RPC, on récupère la main immédiatement, sans attendre la réponse du serveur.
Lorsque la réponse arrive, une méthode désignée du client (appelée callback) est
invoquée, et celle-ci a accès aux résultats de l’appel. Il est de fait impossible de faire
un appel RPC et de « bloquer » jusqu’à la réception de la réponse.
Ce choix se justifie par le caractère mono-thread de JavaScript ; en effet des
opérations longues peuvent provoquer un blocage de l’application du point de vue de
l’utilisateur, voire même un blocage complet du navigateur. Pour éviter ce risque, les
concepteurs de GWT ont purement et simplement banni les appels RPC synchrones.
Cela peut sembler extrêmement contraignant, mais en pratique il est toujours
possible de contourner cette limitation. Par exemple, si vous devez afficher les résultats
d’une recherche qui s’effectue côté serveur, au lieu de coder comme il serait plus naturel
de le faire (et comme il est courant de procéder dans les clients lourds classiques) :
1. appeler le service de recherche ;
2. attendre le retour ;
3. afficher les résultats ;
le flux des traitements GWT sera plutôt :
1. appeler le service de recherche ;
2. (faire autre chose...) ;
3. lorsque les résultats arrivent, afficher les résultats.
La différence peut paraître minime, mais elle est essentielle : dans le deuxième
cas, l’utilisateur conserve la main et peut effectuer d’autres actions, alors que dans le
premier cas, il est bloqué tant que le serveur n’a pas répondu.
73
5.2 Les principes de GWT RPC
Le caractère asynchrone impose tout de même une certaine prudence, puisque
par nature on ne sait pas exactement à quel moment la réponse va arriver, ni par
conséquent quand le callback sera exécuté. Le traitement des erreurs peut s’avérer aussi
d’autant plus délicat.
5.2.1 Classes et interfaces mises en jeu
Chaque service RPC GWT est en réalité une petite famille de classes et interfaces
qui ont chacune leur utilité. Heureusement, certaines sont générées dynamiquement
au runtime par GWT, de sorte que l’on n’a pas besoin de s’en préoccuper. D’autres
peuvent être construites de façon mécanique à partir de l’interface propre du service,
et d’ailleurs plusieurs plugins GWT s’en chargent automatiquement.
Voyons le diagramme des classes mises en jeu :
Figure 5.1 — Diagramme des classes mises en jeu
• YourService est l’interface qui contient les méthodes qui seront exposées par
votre service. C’est à vous de l’écrire ; il n’y a pas de contraintes sur ces méthodes,
à part le fait que tous les objets passés en paramètre ou retournés par le service
doivent être sérialisables au sens de GWT RPC. Cette notion est subtilement différente de la notion habituelle de sérialisabilité de Java ; nous y reviendrons plus
loin. Cette interface étend com.google.gwt.user.client.rpc.RemoteService,
cette dernière étant une simple interface marqueur (sans méthodes).
• YourServiceImpl est l’implémentation de cette interface. Le code de cette classe
tournera côté serveur, il est donc possible d’utiliser toutes les techniques de
programmation offertes par Java. Cette classe doit en outre étendre RemoteServiceServlet.
74
Chapitre 5. Communiquer avec le serveur
• YourServiceAsync est une interface dérivée mécaniquement de YourService,
en transformant chaque méthode de la façon suivante :
– le type de retour original, s’il y en a un, est remplacé par void,
– un paramètre est ajouté en fin de liste ; ce paramètre est de type AsyncCallback<T>, où T est le type de retour de la méthode originale. Si la méthode
retournait void, on utilisera AsyncCallback seul ou AsyncCallback<Void>.
Cette interface sera celle que vous manipulerez dans le code client.
• Enfin, YourServiceProxy est une implémentation de YourServiceAsync (la
version asynchrone de votre interface de base). Cette classe est générée pour
vous automatiquement par le compilateur GWT, de sorte que vous n’aurez pas
à vous en préoccuper. Son rôle est de router les appels provenant du client
vers l’implémentation réelle du service, qui se trouve sur le serveur (classe
YourServiceImpl). Elle prend en charge de façon totalement transparente toute
la « mécanique » qui est impliquée par un appel RPC, notamment la création
de la requête, la sérialisation des paramètres, l’envoi de la requête, le traitement
de la réponse, la désérialisation du résultat, et pour finir l’appel du callback
que vous lui avez passé. YourServiceProxy implémente également l’interface
ServiceDefTarget, qui permet de spécifier l’URL à utiliser pour communiquer
avec le serveur.
Remarque à propos de YourServiceAsync
Si l’interface asynchrone ne correspond pas à l’interface de base moyennant les
règles de transformation énoncées, une erreur sera émise par le hosted mode lors du
lancement de l’application.
Une fois qu’on a bien compris le rôle de chacune de ces classes/interfaces, leur
raison d’être devient évidente, de sorte que le diagramme initial n’est plus si complexe...
Dans la suite, nous allons voir comment mettre en pratique cette théorie en voyant
comment implémenter un service RPC puis comment l’appeler depuis le client.
5.3 LA CRÉATION D’UN SERVICE PAS À PAS
5.3.1 Écriture des interfaces et de l’implémentation
La création d’un service implique les étapes suivantes :
1. Écrire l’interface de base du service (YourService). Cette interface est dite
aussi « interface synchrone », par opposition à l’interface asynchrone. Cette
interface doit étendre RemoteService.
2. Écrire l’implémentation de l’interface synchrone. Cette implémentation doit
étendre RemoteServiceServlet.
3. Générer la version asynchrone de l’interface synchrone, comme décrit
plus haut. Il est toujours préférable de faire cela automatiquement si votre
IDE le permet.
5.3 La création d’un service pas à pas
75
À titre d’exemple, nous allons implémenter un service à haute valeur ajoutée :
l’addition...
L’interface synchrone pourra ressembler à ce qui suit :
package oge.gwt.chap52.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
public interface AdditionService extends RemoteService {
long additionner(long x, long y);
}
Rien de bien mystérieux ici, on déclare simplement que notre service va exposer
une méthode « additionner » qui prend deux long en paramètre, et retourne un long.
L’implémentation du service sera simpliste car là n’est pas le propos, mais vous
imaginez bien qu’on peut substituer ce traitement trivial par tout autre traitement
complexe :
package oge.gwt.chap52.server;
import oge.gwt.chap52.client.AdditionService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class AdditionServiceImpl extends RemoteServiceServlet implements
AdditionService {
public long additionner(long x, long y) {
return x + y;
}
}
Notez simplement que cette classe est déployée uniquement côté serveur, c’est
pour cette raison qu’elle se trouve dans le sous-package server.
Enfin, l’interface asynchrone est directement déduite de l’interface synchrone ;
comme on le voit, le type de retour est devenu void et un troisième paramètre s’est
ajouté pour permettre de passer un callback qui sera appelé lorsque le service aura
retourné sa réponse :
package oge.gwt.chap52.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface AdditionServiceAsync {
void additionner(long x, long y, AsyncCallback<Long> callback);
}
Le type exact du callback est AsyncCallback<Long>, ce qui veut dire qu’on va passer
au callback un objet de type Long. Notez bien que le callback est du domaine du client,
l’implémentation du service n’a pas à s’en soucier.
Attention, cette interface asynchrone n’existe que pour permettre au client
d’appeler le service RPC, mais ce n’est pas à vous de fournir son implémentation :
GWT la génèrera automatiquement pour vous.
Voilà, le service est défini ! Mais avant de pouvoir l’appeler il faut bien sûr le
déployer sur un serveur.
76
Chapitre 5. Communiquer avec le serveur
5.3.2 Déploiement sur le serveur embarqué
Comme nous l’avons vu, l’implémentation du service, en plus d’implémenter l’interface synchrone, étend RemoteServiceServlet. En réalité chaque service GWT RPC
est une servlet, et peut donc être déployé sur tout conteneur de servlets compatible
avec cette spécification. Attention, vous ne devez pas étendre HttpServlet comme si
vous implémentiez une servlet complète ; GWT se chargera en effet de l’interprétation
de la requête, de la désérialisation des paramètres, de l’appel à la méthode adéquate,
de la construction de la réponse et de la sérialisation du paramètre de retour (le cas
échéant).
Pour faciliter le test des services GWT RPC en phase de développement, le serveur
du hosted mode embarque une version de Jetty, un conteneur de servlets qui permet de
déployer très facilement des services GWT RPC localement. Pour déclarer un service
GWT RPC dans le serveur embarqué, il suffit d’ajouter les lignes suivantes au fichier
WEB-INF/web.xml qui doit se trouver dans le dossier war de votre projet :
<servlet>
<servlet-name>addServlet</servlet-name>
<servlet-class>oge.gwt.chap52.server.AdditionServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>addServlet</servlet-name>
<url-pattern>/gwtchap52/add</url-pattern>
</servlet-mapping>
Cela signifie en d’autres termes :
• on déclare une servlet nommée « addServlet » dont l’implémentation est
fournie par la classe oge.gwt.chap52.server.AdditionServiceImpl ;
• cette servlet est associée à l’URL /gwtchap52/add.
On peut constater que cette méthode est strictement identique à la façon standard
de déclarer une servlet, ce qui veut dire que le service pourra être déployé sur tout
autre conteneur de servlets avec la même configuration.
Une note sur l’URL : la première partie (gwtchap52) doit correspondre au nom
du module GWT, ou bien à son alias si la fonction « rename-to » a été utilisée (voir
§ 2.3.4 Le fichier module) ; quoiqu’il en soit, elle doit correspondre au sous-dossier du
dossier war dans lequel se trouvent les fichiers, c’est-à-dire l’URL de base. La seconde
partie est un nom arbitraire que vous donnez à votre service, et qui sera utilisé par le
client pour désigner le service à appeler.
Dernier point à vérifier : le fichier gwt-servlet.jar, qui contient le code de GWT
dédié à la partie serveur du mécanisme RPC, doit se trouver dans war/WEB-INF/lib,
et les classes Java compilées (notamment l’implémentation du service) doivent se
trouver sous war/WEB-INF/classes. Encore une fois, ce ne sont là que les pratiques
standards du déploiement d’applications web.
5.3 La création d’un service pas à pas
77
5.3.3 La réalisation d’un appel RPC
Une fois la partie serveur en place, on peut enfin implémenter l’appel au service côté
client.
Pour appeler un service GWT RPC, il faut obtenir une référence vers un objet qui
implémente l’interface asynchrone du service. Cette référence s’obtient de façon très
simple en appelant la méthode suivante :
GWT.create(MyService.class)
Le résultat est un objet qui implémente MyServiceAsync (pour autant que celle-ci
ait été définie correctement). Grâce aux types génériques de Java 5, le résultat n’a pas
besoin d’être transtypé (cast) et on peut donc écrire :
MyServiceAsync service = GWT.create(MyService.class) ;
Une fois la référence obtenue, il faut indiquer l’URL cible du service, c’est-à-dire
l’URL sur laquelle est déployée la servlet qui implémente le service. Pour cela, il faut
appeler la méthode ServiceDefTarget.setServiceEntryPoint(String url).
Cette méthode fait partie de l’interface ServiceDefTarget que l’instance obtenue
de GWT.create() implémente également (en plus de l’interface asynchrone du service).
Il suffit donc de caster cette référence vers ServiceDefTarget ; on pourra donc écrire :
((ServiceDefTarget) service).setServiceEntryPoint("/path.to.service");
Par exemple, pour accéder à notre service d’addition, en supposant qu’il a été
déployé sur /gwtchap52/add (voir url-pattern dans war/WEB-INF/web.xml), on écrira :
((ServiceDefTarget) service).setServiceEntryPoint("/gwtchap52/add");
Pour rendre cet appel indépendant de l’URL de base, on pourra écrire aussi :
((ServiceDefTarget) service). setServiceEntryPoint(
GWT.getModuleBaseURL() + "add");
Enfin, une fois ceci fait, on peut procéder à l’appel, non sans avoir défini un
callback à appeler en cas de succès (ou d’échec) de l’appel. On peut écrire une classe
spécialisée pour implémenter ce callback, mais il est souvent plus aisé de l’écrire sous
forme d’inner-classe anonyme.
Le callback doit obligatoirement implémenter deux méthodes :
• void onSuccess(T result) où T est le type de retour de la méthode synchrone
correspondante, méthode qui sera appelée en cas de succès, avec comme
paramètre le résultat de l’appel ;
• void onFailure(Exception e), méthode qui sera appelée en cas d’échec, avec
comme paramètre l’exception traduisant la cause de l’échec.
78
Chapitre 5. Communiquer avec le serveur
L’appel se fera donc comme suit :
service.myMethod(x, y, new AsyncCallback<Long>() {
public void onFailure(Throwable caught) {
// faire quelque chose en cas d’échec
}
public void onSuccess(Long result) {
//faire quelque chose en cas de succès
}
});
Encore plus concis
Pour rendre le code client encore plus concis, on peut faire précéder la déclaration de l’interface synchrone du service par l’annotation @RemoteServiceRelativePath(relativeURL).
Si cette annotation est présente, alors le proxy GWT effectuera automatiquement
à votre place l’appel à setServiceEntryPoint en lui passant comme paramètre
GWT.getModuleBaseURL() + relativeURL, où relativeURL est la valeur de l’unique
paramètre de l’annotation.
Par exemple, on pourra réécrire l’interface de notre service d’addition comme suit :
@RemoteServiceRelativePath("add")
public interface AdditionService extends RemoteService {
long additionner(long x, long y);
}
Côté client, l’appel se résumera alors à obtenir une référence vers l’implémentation
de l’interface asynchrone via GWT.create(), et appeler la méthode voulue de cette
interface.
5.4 EXEMPLE COMPLET
Pour mettre en œuvre notre service d’addition, nous allons réaliser une interface
graphique très simple qui permettra de saisir deux nombres ; un bouton déclenchera
l’appel à notre service d’addition et affichera le résultat. Le listing complet de l’exemple
est accessible en annexe 3.
La déclaration du service sera identique à ce que nous avons défini plus haut.
L’interface synchrone :
package oge.gwt.chap52.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
public interface AdditionService extends RemoteService {
long additionner(long x, long y);
}
5.4 Exemple complet
79
L’interface asynchrone :
package oge.gwt.chap52.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface AdditionServiceAsync {
void additionner(long x, long y, AsyncCallback<Long> callback);
}
L’implémentation :
package oge.gwt.chap52.server;
import oge.gwt.chap52.client.AdditionService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class AdditionServiceImpl extends RemoteServiceServlet implements
AdditionService {
public long additionner(long x, long y) {
return x + y;
}
}
Le fichier HTML sera simpliste puisque nous allons créer les éléments d’interface au
travers du code GWT. On se contentera d’un élément <div> avec un ID « container »
qui nous permettra de le référencer dans le code GWT.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" language="javascript"
src="gwtchap52/gwtchap52.nocache.js">
</script>
</head>
<body>
<h1><p align="center">Exemple GWT-RPC</p></h1>
<div align="center" id="container"></div>
</body>
</html>
Après avoir vérifié que tout le code est correct (annexe 3), vous pouvez lancer le
test en mode hôte avec le bouton Run as... > Web application. Après quelques instants,
vous verrez la fenêtre « Google Web Toolkit Hosted Mode » s’ouvrir (figure 5.2) ; dans
la console, vous noterez que la première ligne signale le démarrage du serveur Jetty
embarqué. Par défaut, Jetty écoute sur le port 8080, mais c’est bien sûr paramétrable.
80
Chapitre 5. Communiquer avec le serveur
Figure 5.2 — Console en mode hôte
La fenêtre de navigateur associée au mode hôte montre l’interface que nous avons
définie (figure 5.3). Un petit clic sur le bouton = et on peut vérifier que l’appel au
serveur fonctionne correctement (figure 5.4)...
Figure 5.3 — L’interface du service d’addition
81
5.5 Utilisation d’un serveur externe
Figure 5.4 — Le résultat de l’addition...
5.5 UTILISATION D’UN SERVEUR EXTERNE
Le serveur embarqué n’est destiné qu’à tester plus facilement votre application GWT
en phase de développement. Lorsque sera venu le temps de passer en environnement
de production ou assimilé, il faudra alors la déployer sur un serveur web de production.
Deux types de contenu sont à déployer : d’une part les pages statiques (on entend
par là les ressources dont le contenu n’est pas généré dynamiquement, mais issu d’un
fichier ; ceci comprend donc les fichiers JavaScript issus de la compilation GWT)
et d’autre part, le cas échéant, les servlets implémentant les services RPC auxquels
accède le client.
Pour les fichiers statiques, tout serveur web fera l’affaire, cependant pour déployer
les servlets, il faudra au minimum un conteneur (ou moteur) de servlets. De nombreux
serveurs web répondent à cette contrainte, commerciaux ou non, mais heureusement,
grâce à la normalisation des API « servlets », la façon de déployer une application
web est relativement similaire pour tous, d’autant plus que depuis la version 1.6,
l’organisation d’un projet GWT suit cette norme pour faciliter le travail de packaging
et de déploiement.
En règle générale, le déploiement se résume à :
1. exécuter la compilation GWT de votre projet ;
2. créer un fichier .war qui reprend le contenu du dossier war de votre projet ;
3. déposer ce fichier dans un dossier spécifique du serveur web où il sera détecté
et autodéployé (si le serveur est configuré dans ce sens), ou bien déployer
manuellement le fichier .war.
En effet, si vous avez suivi les recommandations pour la structure de votre projet
(voir § 2.3 Anatomie d’un projet GWT), le dossier war contient déjà tout ce qui est
nécessaire au déploiement : fichiers statiques, web.xml, bibliothèques Java additionnelles, et code Java serveur compilé ; ne manque que le résultat de la compilation
GWT.
82
Chapitre 5. Communiquer avec le serveur
Attention
Ne pas oublier d’inclure le fichier gwt-servlet.jar dans les bibliothèques additionnelles
(WEB-INF/lib) si vous avez du code serveur !
Lorsque le déploiement est terminé, vous devriez pouvoir accéder à votre application à l’adresse http://serveur:8080/MonAppli/MonAppli.html, en supposant que le
serveur écoute sur le port 8080.
Remarque
Les problèmes les plus courants qui peuvent survenir lors du déploiement d’une
application GWT sur un serveur sont des erreurs d’URL, par exemple si le client
tente d’accéder à un service sur une URL incorrecte. Pour résoudre ces erreurs,
activez le journal des accès (access log) sur le serveur web et vérifiez l’URL appelée ;
assurez-vous que la servlet est mappée sur la bonne URL dans le fichier web.xml
(élément <url-pattern>).
5.6 CONTRAINTES DE SÉRIALISATION
Dans l’exemple précédent, nous avons transmis au service distant deux paramètres de
type long, et en retour le callback nous a transmis un résultat de type long. Bien sûr,
GWT RPC n’est pas limité à ce type de paramètres, et on peut transmettre et recevoir
en retour des instances de toutes sortes de classes, pour peu que celles-ci respectent
quelques contraintes.
Pour transmettre les objets au travers de la connexion réseau entre le client GWT
et le serveur, GWT s’appuie sur le concept de sérialisation. La sérialisation est ce qui
permet de transformer l’état interne d’un objet en une série d’octets pour pouvoir soit
le transmettre via un lien réseau (c’est notre cas), soit le stocker dans le but de le « ressusciter » plus tard. La sérialisation est un concept natif de Java, mais il convient d’être
prudent car le concept de sérialisation de GWT diffère subtilement de celui de Java.
Un type est dit sérialisable au sens de GWT si une des conditions suivantes est
vraie :
• il s’agit d’un type primitif, tel que char, byte, short, int, long, boolean, float,
double ;
• il s’agit d’un « wrapper » d’un type primitif (Char, Byte, Integer, Long, Boolean,
Float, Double) ;
• il s’agit d’une instance de String ou Date ;
• il s’agit d’une énumération (attention toutefois, seul le nom de la constante est
sérialisé, pas les éventuelles valeurs des champs qu’elle peut contenir) ;
• il s’agit d’un tableau (array) de types sérialisables ;
• il s’agit d’un type dont il existe au moins une sous-classe sérialisable connue ;
• il s’agit d’un type déclaré comme sérialisable par l’utilisateur.
5.7 Les exceptions
83
5.6.1 Types sérialisables déclarés par l’utilisateur
En dehors des types sérialisables par GWT nativement, une classe quelconque est
également sérialisable si toutes les conditions suivantes sont remplies :
• elle implémente com.google.gwt.user.client.rpc.IsSerializable
java.io.Serializable, soit directement, soit au travers de l’héritage ;
ou
• tous les champs non final et non transient sont eux-mêmes sérialisables ;
• elle possède un constructeur sans argument (ou pas de constructeur).
Pourquoi IsSerializable ?
L’existence de l’interface IsSerializable s’explique pour des raisons historiques :
dans les premières versions de GWT, les concepteurs voulaient clairement marquer
la différence avec l’interface Serializable de Java. En effet, cette dernière recouvre
une sémantique plus complexe et différente de la sérialisation GWT, qui se veut plus
simple et spécialisée. De plus, l’interface Serializable appartient au package java.io
qui ne fait pas partie des classes émulées par GWT.
Le risque de confusion existant, les concepteurs de GWT ont choisi de rendre la
différence explicite en introduisant l’interface IsSerializable. Cependant, l’existence
de ces deux interfaces complexifie la réutilisation de code qui utilise l’interface Java
Serializable. Par conséquent, depuis la version 1.4 de GWT, il est possible d’utiliser
aussi l’interface Serializable pour dénoter une classe sérialisable pour GWT, avec
une contrainte cependant : lors de la compilation, GWT génère un fichier .gwt.rpc
contenant la politique de sérialisation, c’est-à-dire les types autorisés à la sérialisation ;
ce fichier doit être déployé sur le serveur, faute de quoi le serveur peut refuser la
sérialisation.
5.7 LES EXCEPTIONS
Lors d’un appel RPC, bien des raisons peuvent faire en sorte que l’appel n’aboutisse
pas : indisponibilité du réseau, impossibilité de joindre le serveur, erreur sur le serveur
lors de l’exécution de la méthode... Ces conditions sont traitées par GWT RPC « en
termes d’exceptions Java » ; la nuance est importante, car il ne s’agit pas d’exceptions
qui sont levées de façon classique.
En effet, rappelez-vous que l’appel à la méthode distante est asynchrone, ce qui
signifie que, tout comme la disponibilité du résultat de l’appel, une éventuelle erreur
se produisant lors de l’appel ne sera pas disponible immédiatement à la sortie de la
méthode, mais à un moment ultérieur. Il est donc impossible de lever une exception
dans la méthode appelée ; c’est pour cette raison que le callback qui est passé en
paramètre lors de l’appel RPC doit définir, outre la méthode onSuccess(), une méthode
onFailure() qui accepte un paramètre de type Throwable. En cas d’erreur, c’est la
méthode onFailure() qui sera invoquée en lieu et place de onSuccess(), avec comme
paramètre l’exception traduisant la condition d’erreur.
84
Chapitre 5. Communiquer avec le serveur
On le voit, il s’agit bien de traiter les cas d’erreurs au travers d’exceptions Java,
puisque c’est bien une instance de Throwable qui est passée à onFailure(), mais sans
qu’une exception Java ne soit levée côté client.
Les erreurs qui peuvent survenir lors d’un appel RPC peuvent être classées en
deux catégories : les exceptions contrôlées (checked) et les exceptions non contrôlées
(unchecked) :
• Les exceptions contrôlées sont celles qui sont explicitement déclarées par
l’interface (synchrone) de votre service au moyen du mot-clé « throws ». Ces
exceptions sont susceptibles de se produire lors de l’exécution de la méthode
appelée sur le serveur. Notez qu’ici c’est bien le mécanisme standard des
exceptions Java qui s’applique, puisque l’implémentation de cette interface
s’exécute sous forme de code Java classique, côté serveur.
• Les exceptions non contrôlées sont toutes celles qui peuvent survenir en dehors
du cas précédent, et qui traduisent des conditions anormales empêchant l’appel
RPC de se terminer correctement et proprement.
GWT peut générer uniquement les exceptions non contrôlées suivantes :
• InvocationException est générée lorsque la requête ne parvient pas au serveur,
pour une raison quelconque (réseau indisponible, échec de la résolution DNS,
connexion impossible, etc.), ou bien lorsqu’une exception non contrôlée
survient lors de l’exécution de la méthode appelée (par exemple, NullPointerException). Dans ce dernier cas l’exception originale est encapsulée en tant
que cause dans l’InvocationException.
• IncompatibleRemoteServiceException est générée lorsqu’un client essaye d’accéder à un service dont une version plus récente a été déployée sur le serveur.
En général le simple fait de rafraîchir le client suffit à rétablir la situation.
Dans tous les cas, le traitement de l’exception par GWT est le même : au lieu
d’appeler la méthode onSuccess() du callback, c’est la méthode onFailure() qui est
appelée, avec en paramètre l’exception survenue.
Tableau 5.1 — Synthèse des différents cas d’exception
Condition d’erreur
Exception contrôlée t (de type T) survenant
lors de l’exécution de la méthode sur le
serveur
Exception non contrôlée t survenant lors de
l’exécution de la méthode sur le serveur
L’invocation du service n’a pas pu se faire
ou ne s’est pas terminée de façon propre
La version du service déployée sur le serveur
est incompatible avec la version attendue
Type de Throwable passée à onFailure
T
InvocationException avec cause = t
InvocationException
IncompatibleRemoteServiceException
5.7 Les exceptions
85
Attention
Il serait erroné de penser que le fait qu’une InvocationException a été générée et
passée à la méthode onFailure() signifie que la méthode du service distant n’a pas
été appelée ! En effet, il peut se produire une erreur après l’exécution de celle-ci, lors
de l’envoi de la réponse au client. Dans ce cas précis, une InvocationException sera
levée, bien que la méthode ait été exécutée avec succès.
Une façon élégante de traiter les différents cas d’exception dans la méthode
onFailure() est de relancer l’exception qui a été reçue à l’intérieur du bloc try
d’un try-catch ; ainsi on peut utiliser le mécanisme habituel, comme dans l’exemple
suivant :
public void onFailure(Throwable caught) {
try {
// relancer l’exception reçue
throw caught;
} catch (IncompatibleRemoteServiceException e) {
// traiter le cas de l’incompatibilité
} catch (InvocationException e) {
// traiter le cas de l’échec de l’appel
} catch (CheckedException1 e) {
// une des exceptions déclarées par la méthode originale
} catch (CheckedException2 e) {
// une autre des exceptions déclarées
} catch (Throwable e) {
// en dernier ressort... toute autre exception
}
}
Il est bien sûr possible de redéfinir ses propres exceptions en sous-classant
Exception, mais il faut garder à l’esprit que le code de la classe, comme tout code
tournant côté client, doit s’inscrire dans le sous-ensemble du JRE émulé par GWT. En
pratique, cela n’est pas une contrainte forte car les exceptions sont rarement des types
complexes.
DEUXIÈME PARTIE
Aller plus loin avec
GWT
6
Internationalisation
Objectif
Dans ce chapitre, nous allons voir comment rendre votre application facilement
adaptable à d’autres langues et cultures ; et plus généralement, comment séparer
proprement les ressources du code.
6.1 LES POSSIBILITÉS
D’INTERNATIONALISATION DE GWT
L’internationalisation, aussi désignée i18n pour économiser quelques octets, est l’ensemble des techniques qui permettent de rendre un logiciel facilement adaptable à
la langue et aux usages régionaux de l’utilisateur (ce qu’on appelle la locale). Une
application internationalisée peut alors être localisée, c’est-à-dire adaptée à une langue
particulière.
GWT possède un arsenal permettant de rendre une application facilement
localisable, et ce dans différentes configurations : soit qu’il s’agisse de la simple
traduction de libellés ou de l’externalisation de paramètres, soit de l’externalisation
de messages complexes avec des parties variables, soit de l’accès à des ressources déjà
localisées. Le même mécanisme peut également être utilisé pour placer des propriétés
(paramètres, etc.) dans des fichiers externes facilement accessibles depuis votre code,
indépendamment de la locale.
La technique la plus simple est appelée internationalisation statique. Dans ce cas,
les différents libellés et messages sont associés à des clés, chaque clé se traduit par une
méthode dans une interface et GWT fournit l’implémentation de cette interface lors
90
Chapitre 6. Internationalisation
de l’exécution, en fonction de la locale choisie. Les chaînes correspondant aux clés
sont stockées dans de simples fichiers texte similaires aux fichiers properties.
Il est également possible d’utiliser une technique d’internationalisation dite
dynamique, qui permet d’accéder à un dictionnaire contenu dans la page HTML hôte.
Cette méthode est dite dynamique car les clés de cette mappe ne sont pas forcément
connues d’avance au moment de la compilation. Elle est particulièrement adaptée pour
intégrer des composants GWT dans une application déjà internationalisée, lorsque
les messages localisés sont générés par le serveur et déjà présents dans la page HTML.
Enfin, il existe une technique générique (Localizable) qui permet de sélectionner
une implémentation spécifique d’une interface en fonction de la locale.
Nous ne détaillerons dans la suite que la méthode statique, qui est la plus simple à
utiliser et la plus appropriée pour une application GWT « propre ».
6.1.1 Importer le module I18N
Toutes les fonctionnalités i18n de GWT dépendent d’un module nommé judicieusement « I18N ». Par conséquent, pour utiliser une des classes que nous allons évoquer,
il faudra toujours prendre la précaution de référencer ce module dans le fichier module
de votre application, en y incluant un élément XML inherits additionnel dans
l’élément modules :
<module>
<!- - - - Les classes de base GWT-->
<inherits name=’com.google.gwt.user.User’/>
<!-- Autres modules référencés -->
<inherits name=’com.google.gwt.i18n.I18N’/>
<!-- Le point d’entrée -->
<entry-point class=’xxx.xxx.xxxx’/>
</module>
6.2 INTERNATIONALISATION « STATIQUE »
L’internationalisation statique repose sur la définition d’interfaces ; l’ensemble
des clés servant d’index aux messages est donc connu au moment de la compilation
GWT, ce qui permet au compilateur d’effectuer des optimisations. D’un autre
côté, le fait de devoir connaître à l’avance les clés de traduction peut être une
limitation ; dans ce cas peut-être que l’internationalisation dynamique est plus
adaptée à ce que vous souhaitez faire.
Les interfaces permettant l’internationalisation statique sont Constants,
ConstantsWithLookup et Messages. La première permet d’associer simplement des
chaînes de caractères à des clés définies sous forme de méthodes dans une interface ;
la seconde y ajoute la possibilité d’interroger une clé dynamique ; la troisième permet
de générer des messages incluant des paramètres, à la manière de la classe native Java
MessageFormat.
6.2 Internationalisation « statique »
91
6.2.1 L’interface Constants
L’interface Constants est une interface « marqueur » destinée à être étendue. Son
utilisation est très simple :
• créer une interface qui étend Constants ;
• définir les valeurs associées aux clés ;
• accéder aux valeurs dans le code client.
Créer l’interface
Lorsque vous souhaitez externaliser certaines chaînes de caractères pour pouvoir les
localiser, vous devrez donc tout d’abord créer une interface qui étend Constants. Dans
cette interface, vous déclarez une fonction par chaîne que vous souhaitez externaliser,
par exemple pour les jours de la semaine :
package oge.gwt.chap63.client;
import com.google.gwt.i18n.client.Constants;
public interface AppConstants extends Constants {
String lun();
String mar();
String mer();
String jeu();
String ven();
String sam();
String dim();
}
Ce faisant, vous avez déclaré que vous alliez définir sept ressources externes,
dont les clés seront respectivement « lun », « mar », « mer », « jeu », « ven »,
« sam » et « dim ».
Définir les valeurs associées
Ensuite, il faut définir les valeurs associées à ces clés ; pour cela il faut écrire un
fichier nommé interface.properties où interface est le nom de notre interface
(AppConstants dans notre exemple).
Le contenu des fichiers .properties associés aux interfaces d’internationalisation de
GWT est similaire à la notion de fichier properties de Java ; pour simplifier, il s’agit
d’un fichier texte contenant en ensemble de lignes de la forme :
clé = valeur
Les fichiers .properties Java et GWT
À la différence des fichiers .properties Java natifs, les fichiers .properties de GWT
doivent être encodés en UTF-8 et supportent l’inclusion directe de caractères Unicode.
Si vous allez afficher des caractères internationaux, assurez-vous que la page hôte
contient le tag méta suivant :
<meta http-equiv="content-type"
content="text/html;charset=utf-8" />
92
Chapitre 6. Internationalisation
Si on reprend notre exemple, il nous faut définir la valeur correspondant à
chacune des sept clés « lun », « mar », « mer », etc. Cela donne un fichier AppConstants.properties contenant :
lun
mar
mer
jeu
ven
sam
dim
=
=
=
=
=
=
=
lundi
mardi
mercredi
jeudi
vendredi
samedi
dimanche
Ce fichier doit se trouver dans le même package que l’interface correspondante.
Accéder aux valeurs dans le code client
Pour accéder aux valeurs correspondant aux clés depuis le code client, on demande
simplement à GWT de nous fournir une implémentation de notre interface grâce à
l’omniprésent GWT.create(). Par exemple on écrira :
AppConstants appConstants = GWT.create(AppConstants.class);
On obtient en retour une référence vers une implémentation de l’interface telle
que les appels aux méthodes donnent en retour la valeur associée au travers du fichier
properties. Ainsi, appContants.lun() donnera « lundi », appConstants.mar() donnera
« mardi », etc.
Notez que l’implémentation de cette interface est générée automatiquement par
GWT, par conséquent vous ne devez normalement pas l’étendre directement.
Exemple
Voici un exemple d’utilisation de l’interface Constants. Les fichiers AppConstants.java
et AppConstants.properties sont tels que présentés plus haut.
On va simplement afficher le résultat de l’appel aux méthodes de l’interface dans
une Grid. La page HTML sera très concise :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!-- Chargement de l’appli -->
<script type="text/javascript" language="javascript"
src="gwtchap63/gwtchap63.nocache.js"></script>
</head>
<body>
<h1><p align="center">Internationalisation</p></h1>
<div id=’content’ align="center" />
</body>
</html>
6.2 Internationalisation « statique »
93
On définit un élément div avec un id qu’on référencera dans le code client.
Le code client va, lui, appeler GWT.create() pour récupérer la référence vers
l’implémentation de l’interface AppConstants, puis construire une Grid pour illustrer
l’appel aux fonctions.
package oge.gwt.chap63.client;
import
import
import
import
com.google.gwt.core.client.EntryPoint;
com.google.gwt.core.client.GWT;
com.google.gwt.user.client.ui.Grid;
com.google.gwt.user.client.ui.RootPanel;
public class GwtChap63 implements EntryPoint {
/**
* Le point d’entrée.
*/
public void onModuleLoad() {
AppConstants appConstants = GWT.create(AppConstants.class);
RootPanel rootPanel = RootPanel.get("content");
final Grid grid = new Grid(7, 2);
grid.setBorderWidth(1);
grid.setCellPadding(5);
grid.setCellSpacing(0);
int i =0;
grid.setText(i, 0, "lun()");
grid.setText(i++, 1, appConstants.lun());
grid.setText(i, 0, "mar()");
grid.setText(i++, 1, appConstants.mar());
grid.setText(i, 0, "mer()");
grid.setText(i++, 1, appConstants.mer());
grid.setText(i, 0, "jeu()");
grid.setText(i++, 1, appConstants.jeu());
grid.setText(i, 0, "ven()");
grid.setText(i++, 1, appConstants.ven());
grid.setText(i, 0, "sam()");
grid.setText(i++, 1, appConstants.sam());
grid.setText(i, 0, "dim()");
grid.setText(i++, 1, appConstants.dim());
rootPanel.add(grid);
}
}
94
Chapitre 6. Internationalisation
Enfin le fichier module, GwtChap63.gwt.xml :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN"
"http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distrosource/core/src/gwt-module.dtd">
<module rename-to=’gwtchap63’>
<!-- inclure le module principal GWT -->
<inherits name=’com.google.gwt.user.User’/>
<!-- inclure le module d’internationalisation -->
<inherits name=’com.google.gwt.i18n.I18N’/>
<!-- Feuille de style standard-->
<inherits name=’com.google.gwt.user.theme.standard.Standard’/>
<!-- Point d’entrée -->
<entry-point class=’oge.gwt.chap63.client.GwtChap63’/>
</module>
Si tout est correctement configuré, vous pouvez exécuter l’application en mode
hôte (Run as > Web Application), et le résultat sera celui affiché en figure 6.1.
Figure 6.1 — Exécution de l’application
Autres types
L’externalisation n’est pas limitée à des chaînes de caractères ; elle peut s’appliquer à
différents types. La déclaration dans l’interface doit toujours avoir la forme :
T méthode() ;
Où T est un des types autorisés selon le tableau 6.1.
6.2 Internationalisation « statique »
95
Tableau 6.1 — Types autorisés pour l’externalisation
T
String
String[]
int
float
double
boolean
Map
Interprétation de la valeur de la propriété
Une chaîne de caractères sans traitement
Une liste de chaînes séparées par des virgules. Utiliser \\, pour insérer une
virgule à l’intérieur d’une chaîne.
Un entier (vérifié à la compilation)
Un nombre compatible avec le type float (vérifié à la compilation)
Un nombre compatible avec le type double (vérifié à la compilation)
Un booléen représenté par true ou false (vérifié à la compilation)
Une liste de clés séparées par des virgules. La Map sera constituée de l’ensemble
des paires clé à valeur. Par exemple, si le fichier properties contient :
dico=k1,k2,k3
k1=v1
k2=v2
k3=v3
alors la méthode Map dico() retournera une Map telle que k1àv1, k2 à v2 et
k3 à v3.
Par exemple, il peut être plus judicieux de représenter les jours de la semaine dans
un tableau de String. On pourra ainsi redéfinir l’interface AppConstants comme suit :
package oge.gwt.chap63.client;
import com.google.gwt.i18n.client.Constants;
public interface AppConstants extends Constants {
String[] jours();
}
Le fichier AppConstants.properties sera réécrit comme suit :
jours = lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche
L’utilisation devient alors :
package oge.gwt.chap63.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.RootPanel;
public class GwtChap63 implements EntryPoint {
/**
* Le point d’entrée.
*/
public void onModuleLoad() {
AppConstants appConstants = GWT.create(AppConstants.class);
RootPanel rootPanel = RootPanel.get("content");
final Grid grid = new Grid(7, 2);
grid.setBorderWidth(1);
96
Chapitre 6. Internationalisation
grid.setCellPadding(5);
grid.setCellSpacing(0);
String[] jours = appConstants.jours();
for (int i = 0; i < 7; i++) {
grid.setText(i, 0, "jours[" + i + "]");
grid.setText(i, 1, jours[i]);
}
rootPanel.add(grid);
}
}
Le résultat est présenté en figure 6.2.
Figure 6.2 — Le résultat avec un tableau de strings
6.2.2 Gestion des locales multiples
Dans l’exemple précédent, on a externalisé un certain nombre de chaînes de caractères
au moyen de l’interface Constants, mais les valeurs retournées ne dépendaient pas
d’une locale particulière. Pour fournir les différentes traductions ou variantes locales
correspondant à une interface Constants, il faut créer différents fichiers properties
nommés de la façon suivante :
interface_locale.properties
où interface est le nom de votre interface (AppConstants dans notre exemple), et
locale un identifiant d’environnement régional ou « locale ».
97
6.2 Internationalisation « statique »
Locale ou paramètres régionaux
Le terme « locale » est un anglicisme désignant, en informatique, l’ensemble des
paramètres qui définissent la langue et les préférences culturelles qui influencent une
interface utilisateur. La traduction la plus proche est « paramètres régionaux », mais
par abus de langage on désigne souvent par « locale » l’identifiant qui la représente ;
c’est pourquoi on a choisi de conserver le terme de « locale » dans la suite.
L’identifiant de locale est composé de :
• soit un code de langue à deux caractères (par ex. fr pour le français, en pour
l’anglais, de pour l’allemand, etc.) ;
• soit un code langue complété d’un code pays à deux lettres, pour tenir compte
des variantes entre pays partageant une langue (par ex. fr_FR pour le français
en France, fr_CA pour le français au Canada.
Les codes langue et pays
Le code langue utilisé dans la désignation de la locale est le code ISO
à deux lettres nommé ISO 639-1, dont on peut trouver une liste sur
http://fr.wikipedia.org/wiki/Liste_des_codes_ISO_639-1. Le code pays utilisé
est le code ISO à deux lettres ISO 3166-1, dont on peut trouver une liste sur
http://fr.wikipedia.org/wiki/ISO_3166-1. À noter que l’usage est de représenter le
code langue en minuscules et le code pays en majuscules, mais GWT n’oblige pas à
cela, de même qu’il ne vérifie pas l’existence réelle des codes utilisés mais fournit
seulement un mécanisme.
Les valeurs retournées par l’instance de l’interface fournie par GWT.create() sont
déterminées comme suit (xx et YY représentent respectivement un code langue et un
code pays).
Tableau 6.2 — Valeurs retournées par GWT.create
Locale courante
Non spécifiée
xx
xx_YY
Source des valeurs retournées
Interface.properties
Interface_xx.properties s’il existe et contient la propriété
recherchée, sinon comme pour « locale non spécifiée »
Interface_xx_YY.properties s’il existe et contient la propriété
recherchée, sinon comme pour « locale xx»
Il faut noter que l’algorithme de recherche de valeur est appliqué indépendamment
pour chaque clé, c’est-à-dire que ce n’est pas un fichier properties dans son ensemble
qui est choisi, mais chaque valeur sera extraite de la source la plus appropriée.
98
Chapitre 6. Internationalisation
L’outil i18nCreator
Si vous avez déjà sous la main un ou des fichiers properties contenant des traductions
que vous souhaitez réutiliser, ou si vous préférez gérer les fichiers properties comme
« maître » (et non l’interface), l’outil i18nCreator peut vous aider ; en effet, il crée des
scripts qui permettent de générer automatiquement une interface de type Constants à
partir d’un fichier properties. Lorsque ce dernier change, il suffit de relancer les scripts
pour mettre à jour l’interface. i18nCreator est un des outils en ligne de commande qui
font partie de la distribution standard de GWT ; pour plus de détails sur son utilisation,
référez-vous à l’aide en ligne.
Lorsque des traductions adaptées à différentes locales ont été établies au moyen de
fichiers properties tel que décrit ci-dessus, il faut encore effectuer deux étapes :
• déclarer les locales additionnelles dans le fichier module ;
• donner un moyen à GWT de savoir quelle est la locale en cours, afin que la
méthode GWT.create() puisse fournir une implémentation de l’interface prenant
en compte cette locale.
Déclarer les locales dans le fichier de module XML
Les locales additionnelles sont déclarées au moyen de l’élément extend-proterty. Il
doit être répété autant de fois que de locales définies, par exemple pour inclure des
localisations en anglais et allemand, on écrira :
<!-- Locales à inclure -->
<extend-property name="locale" values="en"/>
<extend-property name="locale" values="de"/>
Attention car chaque locale supplémentaire déclarée fait augmenter le nombre
de permutations à compiler, c’est-à-dire de couples « navigateur cible/locale ». Notez
qu’il existe toujours au moins une locale à compiler qui est la locale par défaut,
bien que celle-ci ne soit pas déclarée dans le fichier du module. Par conséquent, en
déclarant deux locales supplémentaires, on a en fait multiplié par trois le nombre de
permutations à compiler !
Spécifier la locale
Il reste maintenant à dire à GWT quelle est la locale à utiliser lors de l’exécution de
l’application. Pour cela il existe deux moyens : soit un tag meta dans la page HTML
hôte, soit un paramètre dans l’URL de la page.
6.2 Internationalisation « statique »
99
Pour spécifier la locale dans la page HTML hôte, il faut insérer un élément meta
de la forme suivante :
<meta
name="gwt:property"
content="locale=xx">
ou :
<meta
name="gwt:property"
content="locale=xx_YY">
Par exemple, pour utiliser la locale allemande on utilisera :
<meta name="gwt:property" content="locale=de">
Pour spécifier la locale dans l’URL, on ajoutera un paramètre de la requête nommé
locale et qui prendra la valeur de la locale souhaitée, par exemple :
http://localhost:8080/GwtChap63.html?locale=en
Lorsque les deux méthodes sont utilisées en même temps, c’est la locale spécifiée
dans l’URL qui a la priorité.
Exemple
Reprenons l’exemple initial et ajoutons-y les locales « anglais » (en) et « allemand »
(de). L’interface AppConstants ne change pas :
package oge.gwt.chap63.client;
import com.google.gwt.i18n.client.Constants;
public interface AppConstants extends Constants {
String lun();
String mar();
String mer();
String jeu();
String ven();
String sam();
String dim();
}
Le fichier contenant les valeurs par défaut, AppConstants.properties, ne change
pas non plus et contiendra les valeurs pour le français :
lun
mar
mer
jeu
ven
sam
dim
=
=
=
=
=
=
=
lundi
mardi
mercredi
jeudi
vendredi
samedi
dimanche
100
Chapitre 6. Internationalisation
À côté de cela, on va créer un fichier pour chacune des locales supplémentaires ;
AppConstants_en.properties pour l’anglais :
lun
mar
mer
jeu
ven
sam
dim
=
=
=
=
=
=
=
monday
tuesday
wednesday
thursday
friday
saturday
sunday
Et AppConstants_de.properties pour l’allemand :
lun
mar
mer
jeu
ven
sam
dim
=
=
=
=
=
=
=
Montag
Dienstag
Mittwoch
Donnerstag
Freitag
Samstag
Sonntag
N’oublions pas d’ajouter ces deux locales nouvelles dans le fichier module XML :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN"
"http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distrosource/core/src/gwt-module.dtd">
<module rename-to=’gwtchap63’>
<!-- inclure le module principal GWT -->
<inherits name=’com.google.gwt.user.User’/>
<!-- inclure le module d’internationalisation -->
<inherits name=’com.google.gwt.i18n.I18N’/>
<!-- Feuille de style standard-->
<inherits name=’com.google.gwt.user.theme.standard.Standard’/>
<!-- Locales à inclure -->
<extend-property name="locale" values="en"/>
<extend-property name="locale" values="de"/>
<!-- Point d’entrée -->
<entry-point class=’oge.gwt.chap63.client.GwtChap63’/>
</module>
Le code de l’application, lui, ne change pas du tout (c’est bien là un des objectifs
de l’internationalisation).
Reste à spécifier la locale à utiliser. Si on ne change rien à la page HTML hôte, ce
sont les traductions françaises qui seront utilisées, puisque celles-ci sont dans le fichier
properties par défaut.
6.2 Internationalisation « statique »
101
On va maintenant ajouter un tag meta dans la page HTML hôte pour spécifier la
locale allemande ; le code HTML de la page devient :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!-- Spécifier la locale à utiliser -->
<meta name="gwt:property" content="locale=de">
<!-- Chargement de l’appli -->
<script type="text/javascript" language="javascript"
src="gwtchap63/gwtchap63.nocache.js"></script>
</head>
<body>
<h1><p align="center">Internationalisation</p></h1>
<div id=’content’ align="center" />
</body>
</html>
Si on lance l’application, le résultat est tel que ce ne sont plus les valeurs françaises
issues du fichier AppConstants.properties qui sont affichées, mais bien les valeurs
allemandes issues du fichier AppConstants_de.properties (figure 6.3).
Figure 6.3 — Résultat avec la locale « de » issue d’un tag méta de la page hôte
102
Chapitre 6. Internationalisation
On peut facilement passer à la locale anglaise en ajoutant ?locale=en dans l’URL
du navigateur, même en mode hôte (figure 6.4).
Figure 6.4 — Résultat avec la locale « en » issue d’un paramètre de l’URL
6.2.3 Annotations
L’interface Constants possède encore d’autres secrets ; ainsi il est possible de spécifier
directement des valeurs par défaut dans l’interface elle-même, au moyen d’annotations
Java.
Par exemple, on peut réécrire l’interface initiale de notre exemple de la façon
suivante :
package oge.gwt.chap63.client;
import com.google.gwt.i18n.client.Constants;
public interface AppConstants extends Constants {
@DefaultStringValue("lundi")
String lun();
@DefaultStringValue("mardi")
String mar();
@DefaultStringValue("mercredi")
String mer();
@DefaultStringValue("jeudi")
String jeu();
@DefaultStringValue("vendredi")
String ven();
@DefaultStringValue("samedi")
String sam();
@DefaultStringValue("dimanche")
String dim();
}
103
6.2 Internationalisation « statique »
On peut alors se passer totalement du fichier AppConstants.properties, les valeurs
associées aux clés seront directement tirées de l’interface. Toutefois, en présence d’un
ou plusieurs fichiers properties, ce sont ces derniers qui auront priorité, selon les règles
énoncées plus haut.
L’utilisation des annotations pour spécifier les valeurs par défaut, en plus de ne pas
nécessiter la création d’un fichier supplémentaire, permet d’utiliser des constantes Java
et expressions constantes, et de ne pas se préoccuper de l’interprétation des caractères
spéciaux dans les valeurs.
On peut également, grâce aux annotations, modifier la clé de recherche (le nom
de la propriété) dans les fichiers properties. Par défaut, la clé est identique au nom
de la méthode dans l’interface, ainsi la méthode lun() utilisera la clé « lun » pour
rechercher la valeur associée dans les différentes locales. Pour modifier cette clé de
recherche, il suffit d’annoter la méthode avec @Key(cle), par exemple :
@Key("labels.jours.lun")
String lun();
@Key("labels.jours.mar")
String mar();
@Key("labels.jours.mer")
String mer();
@Key("labels.jours.jeu")
String jeu();
@Key("labels.jours.ven")
String ven();
@Key("labels.jours.sam")
String sam();
@Key("labels.jours.dim")
String dim();
Le fichier properties devra alors utiliser les clés renommées :
labels.jours.lun
labels.jours.mar
labels.jours.mer
labels.jours.jeu
labels.jours.ven
labels.jours.sam
labels.jours.dim
=
=
=
=
=
=
=
lundi
mardi
mercredi
jeudi
vendredi
samedi
dimanche
6.2.4 L’interface ConstantsWithLookup
Constants offre une possibilité précieuse d’externaliser des valeurs et de les rendre
dépendants de la locale ; cependant, on ne peut accéder à ces valeurs qu’en appelant la
méthode associée de l’interface. Ainsi, dans notre exemple précédent, la seule manière
d’accéder au nom d’un jour de la semaine est d’appeler une des méthodes lun(), mar(),
mer(), jeu(), ven() sam() ou dim() ; il est impossible d’accéder à la valeur d’une clé
qui serait contenue dans une String.
104
Chapitre 6. Internationalisation
C’est pour autoriser cette possibilité qu’a été conçue l’interface ConstantsWithLookup. Cette interface s’utilise exactement comme Constants (qu’elle étend d’ailleurs),
mais ajoute des méthodes pour obtenir la valeur associée à une clé dont le nom est
contenu dans une String :
• boolean getBoolean(String)
• double getDouble(String)
• float getFloat(String)
• int getInt(String)
• Map<String, String> getMap(String)
• String getString(String)
• String[] getStringArray(String)
Attention
Le paramètre passé aux méthodes de ConstantsWithLookup n’est pas le nom de la clé
utilisée dans le fichier properties associé, mais celui de la méthode de l’interface...
Ces noms peuvent différer dans le cas où l’annotation @Key est utilisé.
En règle générale, si on n’a pas besoin de la fonctionnalité offerte par ConstantsWithLookup, il est préférable d’utiliser Constants car cette dernière se prête mieux à
l’optimisation.
6.2.5 L’interface Messages
Comme Constants, Messages est une interface « marqueur » qui s’utilise d’une façon
similaire. Cependant, à la différence de Constants, les méthodes qu’on place dans une
interface qui étend Messages peuvent accepter un nombre quelconque de paramètres ;
en contrepartie, les valeurs associées aux clés au travers des fichiers properties peuvent
inclure des parties variables, désignées par {0}, {1}, etc.
Le format supporté est identique à celui des patterns de la classe
native Java MessageFormat. S’il est simple à comprendre pour des cas
triviaux, ce format est toutefois très riche et permet des formatages
complexes. Il est décrit en détail dans la Javadoc de la classe MessageFormat :
http://java.sun.com/javase/6/docs/api/java/text/MessageFormat.html
Lorsqu’une méthode d’une interface qui étend Messages est appelée, les parties
variables sont remplacées par la valeur des paramètres de la méthode, convertis en
String. Elles peuvent figurer dans un ordre quelconque : {0} fera toujours référence au
premier paramètre de la méthode, quelle que soit sa position dans le message ; {1} au
deuxième, etc.
105
6.3 Le formatage des dates et nombres
6.3 LE FORMATAGE DES DATES ET NOMBRES
Comme on l’a mentionné au § 3.2.2 Émulation des classes du JRE, la bibliothèque
d’émulation JRE de GWT ne contient pas les classes java.text.DateFormat,
java.text.DecimalFormat,
java.text.NumberFormat,
java.TimeFormat,
etc. Cependant, un sous-ensemble de la fonctionnalité de ces classes est
fourni
par
les
classes
com.google.gwt.i18n.client.NumberFormat
et
com.google.gwt.i18n.client.DateTimeFormat.
Une différence importante est que les classes natives Java sont capables de travailler
avec plusieurs locales différentes durant l’exécution, alors que la sélection de la locale
se fait une fois pour toutes au lancement de l’application, de la même façon que
la sélection des messages associés aux interfaces de type Constants ou Messages, au
travers du mécanisme de Deferred Binding.
Pour utiliser ces classes, il faudra également hériter le module I18N en ajoutant la
ligne suivante dans le fichier module :
<inherits name=’com.google.gwt.i18n.I18N’/>
6.3.1 NumberFormat
Pour formater un nombre, il faut tout d’abord obtenir une instance de NumberFormat ;
on utilise pour cela une des méthodes statiques getXxxFormat(), qui retourne une
instance de NumberFormat appropriée à générer une représentation d’un nombre.
Tableau 6.3 — Méthodes pour le formatage d’un nombre
Méthode
getCurrencyFormat()
getDecimalFormat()
getPercentFormat()
getScientificFormat()
getFormat(String
pattern)
Format que génère le formateur obtenu
montant dans la locale courante
nombre au format décimal dans la locale courante
pourcentage dans la locale courante
nombre au format scientifique standard dans la locale courante
format personnalisé défini par le pattern spécifié.
Le format de la spécification du pattern est décrit dans la
Javadoc de la classe NumberFormat : http://google-webtoolkit.googlecode.com/svn/javadoc/1.6/com/google/gwt/
i18n/client/NumberFormat.html
La méthode format(double) peut alors être utilisée sur l’instance de NumberFormat
obtenue pour produire une chaîne formatée selon le format voulu. Inversement, on
peut aussi utiliser la méthode parse(String) qui lit un nombre dans une chaîne de
caractères.
106
Chapitre 6. Internationalisation
6.3.2 DateTimeFormat
La classe DateTimeFormat fonctionne de façon similaire à NumberFormat, pour les dates
et heures. Les différentes méthodes retournent donc une instance de DateTimeFormat
appropriée à générer une représentation d’une date.
Tableau 6.4 — Méthodes pour le formatage d’une date
Méthode
getFullDateFormat()
getFullDateTimeFormat()
getFullTimeFormat()
getLongDateFormat()
getLongDateTimeFormat()
getLongTimeFormat()
getMediumDateFormat()
getMediumDateTimeFormat()
getMediumTimeFormat()
getShortDateFormat()
getShortDateTimeFormat()
getShortTimeFormat()
getFormat(String pattern)
Format que génère le formateur obtenu
date au format complet dans la locale courante
date et heure au format complet dans la locale courante
heure au format complet dans la locale courante
date au format long dans la locale courante
date et heure au format long dans la locale courante
heure au format long dans la locale courante
date au format médian dans la locale courante
date et heure au format médian dans la locale courante
heure au format médian dans la locale courante
date au format court dans la locale courante
date et heure au format court dans la locale courante
heure au format court dans la locale courante
format personnalisé défini par le pattern spécifié.
Le format de la spécification du pattern est décrit dans la
Javadoc de la classe DateTimeFormat : http://google-webtoolkit.googlecode.com/svn/javadoc/1.6/com/google/gwt/
i18n/client/DateTimeFormat.html
La méthode format(Date) peut alors être utilisée sur l’instance de DateTimeFormat
obtenue pour produire une chaîne formatée selon le format voulu. Inversement, on
peut aussi utiliser la méthode parse(String) qui lit une Date dans une chaîne de
caractères.
7
Mécanismes avancés du
compilateur
Objectif
Ce chapitre vous montrera comment mettre en œuvre deux fonctionnalités avancées
du compilateur GWT :
D’une part JSNI (JavaScript Native Interface), qui prévoit la possibilité d’intégrer dans
votre propre code GWT du code JavaScript écrit à la main.
D’autre part le Deferred Binding, mécanisme par lequel le compilateur génère
différentes variantes de code JavaScript, dont une seule sera chargée à l’exécution.
Le compilateur GWT est au centre de l’architecture de GWT, et la viabilité
du toolkit dépend grandement de son efficacité ; à ce titre, il effectue un travail
remarquable et discret d’optimisation. Les concepteurs de GWT y ont donc inclut des
fonctionnalités aussi astucieuses qu’efficaces : JSNI pour faciliter l’interfaçage avec
les bibliothèques JavaScript, et le Deferred Binding pour optimiser et adapter le code
spécifiquement en fonction de l’environnement d’exécution. Nous allons détailler ces
deux points.
108
Chapitre 7. Mécanismes avancés du compilateur
7.1 JSNI (JAVASCRIPT NATIVE INTERFACE)
7.1.1 Le principe
Bien que cela semble aller à l’opposé de la philosophie de GWT, il existe des situations
où il est souhaitable de pouvoir intégrer dans une classe Java/GWT du code JavaScript
écrit à la main (par opposition au code généré par le compilateur GWT), par exemple :
• pour accéder à des fonctionnalités du navigateur qui ne sont pas exposées par
les API GWT ;
• pour s’interfacer avec une bibliothèque JavaScript tierce.
C’est pour répondre à ces problématiques que GWT propose une fonctionnalité
dite JSNI (JavaScript Native Interface). JSNI s’inspire de JNI (Java Native Interface), la
technique utilisée par Java pour exécuter du code qui n’est pas écrit en Java, adaptée
au cas particulier de GWT.
En ouvrant la porte à du code JavaScript écrit à la main, JSNI offre un
formidable potentiel, mais par la même occasion rend le code dans lequel il est
intégré potentiellement moins portable entre navigateurs, plus sujet aux fuites
mémoire, moins apte à être traité par les outils Java, et plus difficile à optimiser
par le compilateur GWT. Par conséquent, l’utilisation de JSNI doit être réservée
aux cas où il n’existe pas de meilleure option.
7.1.2 Écrire une méthode JSNI
Une méthode JSNI s’écrit simplement de la façon suivante :
• elle est précédée du mot-clé native ;
• le code JavaScript est contenu dans un bloc de commentaires encadré par /*-{
et }-*/, situé entre la parenthèse fermante et le point-virgule.
Exemple :
public static native void alert(String msg) /*-{
$wnd.alert(msg);
}-*/;
Ici on fait appel à la méthode alert(), qui est une méthode native de JavaScript.
Notez qu’on doit utiliser $wnd pour faire référence à l’objet Window. De même, pour
faire référence à l’objet Document, on doit utiliser $doc.
7.1.3 Accéder à des objets Java depuis JSNI
Le code JavaScript inclus dans une méthode JSNI peut également manipuler des objets
Java. Les paramètres passés aux méthodes JSNI peuvent être désignés directement
(comme le paramètre msg dans l’exemple ci-dessus). Attention toutefois aux règles de
passage de paramètre qui s’appliquent dans le sens JavaScript > Java comme Java >
JavaScript ; ces règles seront détaillées plus loin.
109
7.1 JSNI (JavaScript Native Interface)
L’invocation d’une méthode Java nécessite l’utilisation d’une syntaxe spécifique :
[instance.]@classe::méthode(signature)(arguments)
Où :
• instance est l’instance sur laquelle porte l’appel (à omettre pour appeler une
méthode statique) ;
• classe est le nom qualifié de la classe (précédé du nom du package) ;
• méthode est le nom de la méthode à appeler ;
• signature est
le profil de la méthode, afin de distinguer les
éventuelles variantes surchargées. Ce profil doit être encodé selon les
spécifications JNI (chapitre 3 de la spécification JNI, accessible ici :
http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/types.html# wp276) ;
• arguments est la liste des valeurs des arguments à passer à la méthode.
Le tableau 7.1 résume la codification à utiliser pour spécifier les profils de méthodes.
Tableau 7.1 — Codification de spécification des profils de méthodes
Type Java
boolean
byte
char
short
int
long
float
double
String
Xxx
...[]
Code de la
signature
Z
B
C
S
I
J
F
D
Ljava/lang/String;
Laaa/bbb/ccc/Xxx;
[...
Exemple (sans paramètres) :
public native String toStringJS() /*-{
return [email protected]::toString()();
}-*/;
Exemple (avec paramètres) :
public native void something() /*-{
[email protected]::setValue(
Ljava/lang/String;I)("Hello", 2);
}-*/;
110
Chapitre 7. Mécanismes avancés du compilateur
Il est également possible d’accéder à des variables avec une syntaxe similaire :
[instance.]@classe::variable
Où :
• instance est l’instance à laquelle on accède (à omettre pour accéder à une
variable statique) ;
• classe est le nom qualifié de la classe (précédé du nom du package) ;
• variable est le nom de la variable.
7.1.4 Règles de passage de paramètres
À cause des différences de gestion des types entre Java et JavaSript, certaines règles
doivent être respectées lors du passage de paramètres de et vers une méthode JSNI
(tableau 7.2).
Le passage de long dans un sens ou dans l’autre est interdit, car JavaScript ne
supporte pas de type équivalent. On peut si c’est nécessaire passer un objet de type
Long, qui sera traité de façon opaque comme tous les types non reconnus, c’est-à-dire
qu’il pourra uniquement être passé en retour lors de l’appel à une méthode Java.
7.1.5 Traitement des exceptions
Les exceptions se produisant dans du code JavaScript issu d’une méthode JSNI
remontent tout comme une exception Java ; cependant si une telle exception est
« catchée » par un bloc try-catch dans du code Java, elle est convertie en une
JavaScriptException, qui ne contient que le nom de la classe et la description de
l’erreur. Par conséquent, il est toujours préférable de traiter les exceptions JavaScript
dans le code JavaScript.
En revanche, si une exception Java se produit, même dans une méthode Java
appelée depuis une méthode JavaScript, l’exception conserve son intégrité lorsqu’elle
remonte.
7.2 DEFERRED BINDING
7.2.1 Principes
Comme on l’a déjà mentionné, lors de la compilation GWT, le compilateur génère
plusieurs versions du code JavaScript pour prendre en compte les différences entre les
navigateurs existants ; lors du chargement de l’application, c’est la version adaptée
qui est chargée.
Ce mécanisme est en fait totalement générique, et ne se limite pas à la prise
en compte des différents navigateurs. GWT offre en effet la possibilité de générer
plusieurs versions du code JavaScript lors de la compilation, sachant qu’une seule doit
être chargée lors de l’exécution. Le terme « Deferred Binding » (liaison différée) fait
Tout autre type
JavaScriptObject
Autre type numérique primitif (int,
short, byte, char, float, double)
Tableau Java
long
boolean
String
Type Java
Objet (reçu en paramètre)
Valeur opaque (ne peut pas être
manipulé mais peut être repassé à
une méthode Java)
JavaScriptObject (provenant de
code JavaScript)
Valeur opaque, accessible grâce à la
syntaxe spéciale décrite plus haut
Objet (reçu en paramètre)
Un objet natif JavaScript quelconque
Sens JavaScript vers Java
(ce qui doit être passé par le
code JavaScript)
Une chaîne de caractères JavaScript
Une valeur booléenne JavaScript
Interdit
Une valeur numérique JavaScript
Sens Java vers JavaScript
(ce qui est reçu dans le code
JavaScript)
Une chaîne de caractères JavaScript
Une valeur booléenne JavaScript
Interdit
Une valeur numérique JavaScript
Tableau 7.2 — Règles de passage de paramètre de et vers les méthodes JSNI
7.2 Deferred Binding
111
112
Chapitre 7. Mécanismes avancés du compilateur
référence au fait que l’opération de sélection de la version de code à charger s’effectue
au démarrage de l’application, par opposition à ce que serait une liaison totalement
statique (le code est lié lors de la compilation) ou totalement dynamique (le code est
chargé dynamiquement au besoin, comme c’est le cas dans la JVM standard grâce au
class loader).
C’est ce mécanisme qui est mis en œuvre dans les techniques d’internationalisation
« statique » à base d’interfaces (voir § 6.2 Internationalisation « statique »). Au
minimum, le compilateur génère une version de code par navigateur cible, mais lorsque
plusieurs locales sont déclarées, le compilateur examine toutes les combinaisons
(Firefox + français, Firefox + anglais, IE + français, IE + anglais, etc.) et génère une
version de code optimisée pour chaque combinaison.
Le résultat est un temps de chargement optimal, puisque seul le code nécessaire
pour l’environnement de l’utilisateur est téléchargé. En outre, il n’y a pas de temps
perdu à rechercher une implémentation particulière lors de l’exécution, puisque toutes
les implémentations sont déjà connues à la compilation. Revers de la médaille, les
temps de compilation peuvent devenir sensiblement longs lorsque le nombre de
combinaisons augmente.
Le mécanisme de Deferred Binding est mis en jeu explicitement lorsque le code fait
appel à la méthode GWT.create() ; cette méthode permet d’instancier une classe, mais
à la différence de l’utilisation classique de new, elle indique au compilateur GWT que
la classe spécifiée en paramètre peut être remplacée par une autre implémentation,
selon le contexte de l’utilisateur. Le compilateur GWT créera alors une variante
supplémentaire du code JavaScript pour toutes les substitutions possibles de la classe
spécifiée. Évidemment, pour que le compilateur GWT puisse déterminer les variantes,
il doit connaître cette classe lors de la compilation, c’est pourquoi le paramètre de
GWT.create() doit être une constante (du type MyClass.class) et ne peut pas être une
variable.
7.2.2 Mise en oeuvre du Deferred Binding
Il existe deux façons de fournir une implémentation alternative à une classe instanciée
par GWT.create() :
• par substitution : la classe est remplacée par une autre selon des règles spécifiées
dans le fichier module ;
• par génération : les différentes variantes de l’implémentation fournie sont
générées lors de la compilation GWT par un « générateur ».
Substitution
La méthode par substitution est la plus simple à utiliser, puisqu’il suffit d’ajouter
quelques directives au fichier module. Pour comprendre, illustrons-la par un exemple.
Supposons que notre code client fasse appel à une classe MaClasse en l’instanciant
via GWT.create() :
MaClasse impl = GWT.create(MaClasse.class);
7.2 Deferred Binding
113
Si on souhaite par exemple fournir une implémentation différente de MaClasse,
pour une raison quelconque, lorsque le navigateur de l’utilisateur est IE 6, on crée une
classe MaClasseIE6, qui étend MaClasse. On ajoute ensuite les lignes suivantes dans le
fichier module :
<module>
<!-- ... -->
<!-- implémentation spécifique pour IE6 -->
<replace-with class="oge.gwt.client.GWtTest.MaClasseIE6">
<when-type-is class=" oge.gwt.client.GWtTest.MaClasse"/>
<when-property-is name="user.agent" value="ie6" />
</replace-with>
</module>
Avec cette configuration, l’appel à GWT.create(MaClasse.class) sera remplacé par
le compilateur GWT par une instanciation de MaClasseIE6 dans toutes les variantes
destinées à IE 6 (valeur de la propriété user.agent).
Génération
Dans la méthode par substitution, les différentes variantes d’une classe existent en
tant que classe à part entière dans le code source, et le compilateur GWT décide de
l’implémentation à inclure dans le code généré selon le contexte de la compilation. Par
opposition, dans la méthode par génération, le code source à utiliser comme implémentation est généré par une instance de la classe com.google.gwt.core.ext.Generator.
Supposons qu’au lieu de remplacer notre classe MaClasse par une implémentation
spécifique, on souhaite faire générer l’implémentation par un générateur. On écrira
dans le fichier module :
<module>
<!-- ... -->
<generate-with class="oge.gwt.client.GwtTest.MaClasseGenerator">
<when-type-assignable class=" oge.gwt.client.GwtTest.MaClasse" />
</generate-with>
</module>
Cette directive indique au compilateur que lorsque le code source fait appel
à GWT.create() pour instancier MaClasse (ou une sous-classe de celle-ci), alors
l’implémentation à utiliser sera fournie par l’invocation de la méthode generate() du
générateur MaClasseGenerator.
L’implémentation d’un générateur est une tâche complexe qui dépasse largement
la portée de cet ouvrage. Si vous souhaitez obtenir plus de détails sur la façon
d’implémenter un générateur, reportez-vous à la documentation en ligne.
Developper’s Guide
http://code.google.com/webtoolkit/doc/1.6/DevGuide.html
Section « Coding Basics »
8
Le mécanisme d’historique
de GWT
Objectif
Nous allons voir comment GWT résout le problème récurrent dans les applications
AJAX de la gestion des boutons Précédent et Suivant du navigateur.
8.1 LE PROBLÈME
Les applications AJAX fonctionnent sur un principe d’interaction qui tend à imiter
celui des applications de bureau traditionnelles. Cependant, ces applications sont le
plus souvent hébergées dans une page HTML hôte unique, à l’intérieur d’un navigateur
web, pour lequel les utilisateurs sont accoutumés à un principe d’interaction différent.
Dans le monde du Web traditionnel, un clic sur un lien peut être renversé par un
appui sur le bouton de retour arrière du navigateur. Or, si une application AJAX ne
prend pas de précaution, un appui sur le bouton de retour arrière du navigateur peut
être catastrophique en ramenant le navigateur sur la page web précédente, faisant du
même coup perdre tout travail effectué dans l’application AJAX.
116
Chapitre 8. Le mécanisme d’historique de GWT
8.2 L’APPROCHE DE GWT
Heureusement, GWT offre une technique qui permet d’éviter ce désagrément, et qui
rend pleinement fonctionnels les boutons précédent/suivant du navigateur, moyennant
tout de même un léger travail de la part du programmeur.
8.2.1 URL et fragment
Le mécanisme d’historique de GWT s’appuie sur la notion de « fragment » d’URL.
Comme on l’a vu au chapitre 1, une URL quelconque peut éventuellement être
complétée d’une partie optionnelle précédée du signe # appelée « fragment » ; cette
chaîne de caractères est prévue à l’origine pour désigner une sous-partie du document
référencé par l’URL. En pratique, le fragment est habituellement utilisé pour désigner
un signet à l’intérieur de la page HTML, par exemple un début de paragraphe, pour
permettre d’y accéder directement.
Un des points intéressants à propos de ce fragment est que lorsqu’on active un
lien pour lequel seul le fragment change par rapport à l’URL courante, le navigateur
ne recharge pas la page (il suppose qu’elle est déjà chargée). En revanche cela crée
bien une entrée dans l’historique du navigateur. Par conséquent, si une application
JavaScript contenue dans la page est capable de détecter les changements de ce
fragment, elle pourra réagir aux appuis sur précédent ou suivant. C’est sur ce principe
que fonctionne le mécanisme d’historique de GWT.
8.2.2 Encoder l’état de l’application
La contrainte qui en découle est que l’application doit être capable d’encoder son état
interne, ou tout au moins chaque état pour lequel on souhaite pouvoir naviguer grâce
aux boutons précédent/suivant, dans une chaîne de caractères qui sera stockée dans la
partie « fragment » de l’URL ; c’est ce que GWT appelle un « history token » (jeton
d’historique). Bien entendu, l’application doit également être capable de décoder ce
token, pour se remettre dans l’état correspondant au moment où le token a été généré.
Il faut souligner que le contenu et le format de ce token sont totalement à la
charge de l’application (donc du programmeur) ; en effet, GWT ne peut pas savoir
quelles informations seront nécessaires pour rétablir l’application, car cela dépend
entièrement de la structure de celle-ci. Dans les cas très simples, le token peut consister
simplement en une constante qui désigne un écran sur lequel se placer, mais dans
les cas plus complexes, il devra contenir toute l’information nécessaire à restaurer le
contexte.
Il faut noter également que c’est à l’application de décider des « points de
sauvegarde », c’est-à-dire à quel moment créer une entrée dans l’historique. En effet,
chaque clic ne doit pas donner lieu systématiquement à un point de retour, et ici
encore GWT n’a pas de moyen de savoir quel clic entraîne un changement d’état
significatif tel qu’il doive donner lieu à la création d’une entrée d’historique.
8.3 Un exemple
117
8.2.3 Mise en oeuvre
La mise en œuvre du mécanisme d’historique de GWT nécessite l’inclusion du code
suivant dans la page HTML hôte :
<iframe src="javascript:’’"
id="__gwt_historyFrame"
style="width:0;height:0;border:0"></iframe>
Notez que si vous n’utilisez pas le mécanisme d’historique, la présence de ce code
est inoffensive.
Ensuite, pour faire jouer le mécanisme :
• Lorsque l’application veut créer un point de sauvegarde, elle construit un jeton
d’historique contenant une représentation de son état, et appelle la méthode
statique History.newItem(token) où token est le jeton historique.
• Pour réagir à un changement d’état correspondant à un appui sur précédent ou suivant, voire même à l’ouverture directe d’un lien contenant un
jeton d’historique, l’application doit déclarer un handler en appelant History.addValueChangeHandler(). Lorsqu’il est invoqué, le handler doit parser
le jeton d’historique et rétablir l’état de l’application correspondant.
8.3 UN EXEMPLE
Nous allons illustrer le mécanisme d’historique avec un exemple dont le listing
complet est accessible en annexe 4. On suppose qu’on a une application structurée
sous forme d’une arborescence d’écrans ; la sélection d’un écran se fait par la sélection
d’un nœud dans un arbre GWT (Tree).
On décide que chaque changement d’écran, c’est-à-dire chaque clic sur un
nœud de l’arbre, doit ajouter une entrée dans l’historique du navigateur. On
suppose que l’état de l’application se résume à l’écran courant ; par conséquent
on choisit de représenter l’état de l’application (et donc le token historique) par
un identifiant du nœud sélectionné.
Pour simplifier ici, on utilisera le hashcode du texte du nœud comme identifiant,
mais dans une application réelle, il faudrait utiliser un identifiant plus fiable, par
exemple la classe implémentant l’écran correspondant. L’avantage de notre solution
est de fonctionner avec n’importe quel Tree.
8.3.1 Créer une entrée dans l’historique
La première partie de l’implémentation consiste en créer une entrée d’historique lors
de l’activation d’un écran ; on va donc effectuer cette action lors de la sélection d’un
nœud dans l’arbre, grâce à un SelectionHandler.
118
Chapitre 8. Le mécanisme d’historique de GWT
//
// ajout SelectionHandler
//
tree.addSelectionHandler(new SelectionHandler<TreeItem>() {
public void onSelection(SelectionEvent<TreeItem> event) {
// construire le token sur base du hashcode du texte
// du noeud sélectionné
TreeItem selectedItem = event.getSelectedItem();
String token = Long.toHexString(selectedItem.getText().hashCode());
// créer une entrée d’historique
History.newItem(token);
}
});
Une fois le nœud sélectionné récupéré, on génère le token en convertissant en hexadécimal le hashcode du texte du nœud, puis on appelle History.newItem avec ce token.
8.3.2 Réagir à un événement historique
La seconde partie de l’implémentation consiste en réagir à un événement historique,
via un ValueChangeHandler :
//
// ajout du handler historique
//
History.addValueChangeHandler(new ValueChangeHandler<String>() {
public void onValueChange(ValueChangeEvent<String> event) {
// récupérer le token
String token = event.getValue();
// tenter de trouver le noeud corespondant
TreeItem item = findNode(tree, token);
if (item != null) {
// noeud trouvé: le sélectionner
tree.setSelectedItem(item);
// s’assurer que sa branche est déployée
ensureExpanded(item);
}
}
});
Le token reçu est récupéré de l’événement, ensuite on appelle une méthode maison
findItem() qui parcourt l’arbre pour tenter de trouver le nœud dont l’identifiant
(hashcode dans notre cas) correspond au token reçu. Si un tel nœud est trouvé, on le
sélectionne et on s’assure qu’il est visible en déployant sa branche.
On peut vérifier en exécutant l’application qu’après avoir sélectionné plusieurs
nœuds à la suite, on peut utiliser les boutons Back et Forward du hosted mode pour
naviguer dans l’historique ; avec à chaque fois la sélection du nœud correspondant.
On constate au passage qu’en hosted mode, la partie « fragment » de l’URL n’est
pas mise à jour dans la barre d’adresse tant qu’on ne clique pas sur Refresh. C’est un
peu dommage car l’appui sur Refresh recharge l’application, ce qui n’est pas forcément
souhaitable. En revanche, en mode web, on peut voir le fragment se mettre à jour à
chaque fois que History.newItem() est appelé.
8.4 Le widget Hyperlink
119
Figure 8.1 — Le jeton d’historique est visible dans la barre d’adresse en mode web
Dans cet exemple, le token n’est pas véritablement formaté, mais en pratique on
pourra utiliser un encodage du type :
param1=val1;param2=val2...
Il convient d’être prudent lors du parsing d’un token, car il ne faut pas oublier que
le token peut également provenir d’une URL qui a été saisie manuellement par un
utilisateur directement dans la barre d’adresse.
8.4 LE WIDGET HYPERLINK
L’historique peut également être utilisé en conjonction avec un widget Hyperlink ;
ce dernier crée un lien similaire à un lien web classique, mais il a pour effet de
déclencher l’activation d’un jeton d’historique à l’intérieur de l’application courante.
Bien entendu, le jeton associé à l’Hyperlink doit être valide et pouvoir être parsé
correctement.
9
Envoyer des requêtes HTTP
Objectif
Si le mécanisme de RPC de GWT est idéal lorsqu’on maîtrise les deux côtés (client et
serveur), il est des cas où on souhaite avoir plus de maîtrise sur les requêtes envoyées
et construire soi-même la requête HTTP. GWT le permet également au travers de
modules dédiés.
9.1 AU-DELÀ DE GWT RPC
L’essentiel du chapitre 5 était centré sur GWT RPC, le mécanisme d’appels clientserveur préféré de GWT. Toutefois, il arrive qu’on ne puisse pas ou qu’on ne souhaite
pas utiliser ce mécanisme ; c’est le cas par exemple si le backend ne permet pas de
déployer des servlets (par exemple, si on ne dispose que de PHP côté serveur), ou
bien si on souhaite accéder à des services existants et qui n’ont pas été développés
spécifiquement pour un client GWT (par exemple des web services).
Dans ces différents cas, GWT permet tout de même de faire appel à ces services en
offrant des outils pour gérer manuellement l’envoi de requêtes HTTP personnalisées.
De plus, GWT offre également des facilités pour encoder/décoder le contenu des
messages échangés au format JSON ou XML.
122
Chapitre 9. Envoyer des requêtes HTTP
JSON et XML
JSON, acronyme de JavaScript Object Notation, est un format d’échange de données
« allégé », simple à générer et à parser, communément utilisé pour le transport de
données sur un réseau. Il est notamment très répandu dans les applications AJAX, où
il constitue une alternative moins lourde à XML.
Techniquement, JSON est une notation dérivée de JavaScript, ce qui le rend plus facile
à manipuler dans ce langage ; cependant il est possible de manipuler JSON dans la
plupart des langages actuels.
Si en théorie un web service se doit de manipuler des messages SOAP (et donc XML),
de plus en plus de web services publics (tel Google ou Yahoo) adoptent JSON comme
format de sortie.
Tous les détails sur JSON se trouvent sur le site http://json.org.
9.2 REQUÊTES HTTP EN GWT
L’envoi de requêtes HTTP depuis le code GWT fonctionne globalement de façon
similaire à n’importe quel langage, c’est-à-dire :
1. construction de la requête ;
2. envoi de la requête ;
3. traitement de la réponse.
Cependant, la nature de GWT implique certaines particularités qu’il faut garder
en tête :
• comme pour les requêtes RPC, et pour les mêmes raisons, une requête HTTP
est forcément asynchrone (voir § 5.2 Les principes RPC) ;
• les applications GWT étant exécutées à l’intérieur d’un navigateur web, tous les
accès réseau sont soumis à la politique de sécurité du navigateur, en particulier
la politique de même origine (Same Origin Policy ou SOP). En vertu de cette
politique, le code exécuté dans le navigateur ne peut accéder à des ressources
provenant de serveurs autres que celui d’où a été chargée la page hôte. Ceci
complique l’accès à des ressources situées hors du serveur ; cependant, des
techniques existent pour contourner cette limitation (voir § 9.5 Accès à des
web services JSON).
9.2.1 Réalisation d’un appel HTTP
Tout d’abord, si vous voulez effectuer des requêtes HTTP directement en GWT, vous
devrez hériter du module GWT « HTTP », ceci se fait simplement en ajoutant la
ligne suivante dans votre fichier module :
<inherits name="com.google.gwt.http.HTTP" />
9.2 Requêtes HTTP en GWT
123
La réalisation d’un appel HTTP se fait alors comme suit :
1. on instancie un objet RequestBuilder en spécifiant la méthode (GET ou POST)
et l’URL cible ;
2. optionnellement, on complète les paramètres de la requête en appelant :
– setUser() et setPassword() pour spécifier un nom d’utilisateur et un mot
de passe,
– setTimeoutMillis() pour spécifier une durée maximale d’attente de la
réponse,
– setHeader() pour positionner des headers HTTP dans la requête ;
3. lorsque la requête est prête, on appelle sendRequest(String, RequestCallback), où le premier paramètre contient des données à passer à la requête, et le
second est un callback destiné à traiter le résultat de l’appel ;
4. selon le résultat de l’appel :
– si l’appel a abouti et qu’une réponse a été reçue du serveur (même si celle-ci
ne porte pas le code de succès HTTP 200), la méthode onResponseReceived(Request, Response) du callback est appelée. Le code de retour HTTP,
les headers et le contenu de la réponse sont accessibles via les méthodes de
la classe Response,
– si l’appel a échoué ou que la réponse n’a pas été reçue avant l’écoulement de
la durée du timeout, la méthode onError(Request, Throwable) du callback
est appelée ; les causes de l’erreur sont contenues dans l’exception passée
en second paramètre.
Remarque
La méthode sendRequest() retourne un objet Request qui peut être utilisé pour
interroger le statut de la requête via isPending(), ou l’annuler via cancel().
On peut noter la similarité avec le mécanisme d’appel RPC ; en effet, ce n’est pas
un hasard car le mécanisme RPC est construit sur la base du mécanisme de requête
HTTP.
Une fois la réponse reçue, son traitement est entièrement à la charge de l’application. Toutefois, si la réponse est encodée en XML ou JSON, GWT propose des
bibliothèques dédiées à la manipulation de ces formats et qui faciliteront grandement
la tâche.
124
Chapitre 9. Envoyer des requêtes HTTP
9.3 MANIPULATION DE XML
XML est une part essentielle du Web et bien sûr de l’écosystème AJAX dont, rappelonsnous, il constitue le X... Sa standardisation, son interopérabilité, sa versatilité en
ont fait le format de prédilection pour de nombreux usages, depuis les fichiers de
configuration jusqu’au transport de données pour les services réseau.
À la différence d’HTML qui s’attache à fournir la représentation des données
dans le but unique de les afficher dans un navigateur web, XML se concentre sur la
sémantique des données, débarrassées de tout aspect de présentation. Par conséquent,
XML doit être interprété et transformé pour être affiché ou autrement utilisé ; C’est
dans ce but que GWT fournit une bibliothèque qui permet de parser un document
XML et de le parcourir sous forme d’arbre DOM, et inversement de créer un document
DOM et de le sérialiser sous forme de XML.
Les classes liées à la manipulation d’XML dans GWT se trouvent dans le package
com.google.gwt.xml.client ; pour pouvoir les utiliser, il faut une fois de plus « héri-
ter » du module GWT correspondant en ajoutant la ligne suivante au fichier de
configuration du module :
<inherits name="com.google.gwt.xml.XML" />
9.3.1 Le DOM XML
DOM (Document Object Model) est en réalité une abstraction qui permet de manipuler un document structuré sous forme arborescente au travers de ses composants
élémentaires. Cette abstraction s’applique à la structure d’une page web, mais aussi à
un document XML.
Il faut noter que les classes de GWT qui manipulent le DOM XML sont totalement
indépendantes de leurs homologues qui manipulent de DOM HTML ; les premières
sont dans le package com.google.gwt.xml.client alors que les secondes sont dans le
package com.google.gwt.dom.client. En outre, les types du DOM XML sont tous des
interfaces, alors que les types du DOM HTML sont des classes.
Le diagramme en figure 8.1 représente une vue partielle des types (interfaces) du
DOM XML de GWT. Tous les types héritent de Node qui encapsule la relation parentenfants. On y retrouve tous les constituants d’un document XML, essentiellement
éléments et attributs.
9.3.2 Parsing d’un document XML
Le parsing d’un document XML se résume à l’appel de la méthode statique XMLParser.parse(String) en lui passant la chaîne contenant la représentation XML du
document. Le résultat est un objet de type Document que l’on peut manipuler au travers
d’une interface de type DOM.
L’objet Document ne possède qu’un seul fils qui est l’élément racine du document
XML ; on l’obtient avec la méthode getDocumentElement().
125
9.3 Manipulation de XML
Figure 9.1 — Types du DOM XML
Pour naviguer dans l’arbre DOM, il existe plusieurs méthodes, selon la structure
du XML et ce que l’on cherche :
• Si on cherche à accéder à un élément désigné par un attribut de type ID, on
peut le retrouver directement en appelant getElementById() sur l’instance de
Document.
• Si on veut accéder à tous les éléments portant un nom donné, on utilisera
getElementsByTagName() qui retourne un objet de type NodeList permettant
d’itérer sur la liste des éléments.
• Pour naviguer de proche en proche, on utilisera les méthodes getChildNodes(),
getParentNode() et getNextSibling() qui donnent respectivement la liste des
enfants directs (NodeList), le nœud parent, et le nœud collatéral suivant dans
l’ordre de parcours de l’arbre.
Attention
Dans la première méthode, l’attribut doit être déclaré comme un ID au moyen d’un
Document Type Definition (DTD) associé au document XML ; il ne suffit pas d’appeler
un attribut « id » pour en faire un ID au sens XML...
9.3.3 Création d’un document XML
La création d’un document XML sous forme d’arbre abstrait se fait grâce à la méthode
statique createDocument() de la classe XMLParser. On peut alors utiliser les méthodes
createXxxx() pour créer des nœuds (éléments, attributs, texte, etc.) et les « greffer »
au document avec les méthodes appendChild() ou insertBefore().
126
Chapitre 9. Envoyer des requêtes HTTP
Une fois le document créé et peuplé, on peut obtenir la représentation textuelle
de ce document (c’est-à-dire le code XML correspondant) en appelant la méthode
toString().
L’exemple suivant crée un document décrivant un flux Atom :
Document document = XMLParser.createDocument();
Element feedElement = document.createElement("feed");
feedElement.setAttribute("xmlns","http://www.w3.org/2005/Atom");
Element titleElement = document.createElement("title");
titleElement.appendChild(document.createTextNode("Exemple"));
feedElement.appendChild(titleElement);
Element linkElement = document.createElement("link");
linkElement.setAttribute("href", "http://exemple.org");
feedElement.appendChild(linkElement);
Element updatedElement = document.createElement("updated");
updatedElement.appendChild(document.createTextNode("2009-12-13T18:30:02Z"));
feedElement.appendChild(updatedElement);
Element authorElement = document.createElement("author");
Element nameElement = document.createElement("name");
nameElement.appendChild(document.createTextNode("Marcel Marceau"));
authorElement.appendChild(nameElement);
feedElement.appendChild(authorElement);
document.appendChild(feedElement);
String xml = document.toString();
Le résultat (avec retours ligne ajoutés) :
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Exemple</title>
<link href="http://exemple.org"/>
<updated>2009-12-13T18:30:02Z</updated>
<author><name>Marcel Marceau</name></author>
</feed>
9.4 MANIPULATION DE JSON
Comme pour XML, la bibliothèque JSON de GWT est contenue dans un module
séparé qu’il faut inclure en ajoutant la ligne suivante dans le fichier module :
<inherits name="com.google.gwt.json.JSON" />
Typiquement, une structure JSON est reçue sous forme de texte en réponse à
une requête HTTP. De façon similaire à XML, cette chaîne doit être parsée pour
être convertie en une structure qu’on peut explorer et manipuler ; c’est le rôle de la
méthode statique parse() de le classe JSONParser. Cette méthode accepte une String
et retourne un objet de type JSONValue.
127
9.4 Manipulation de JSON
Figure 9.2 — Types JSON de GWT
La figure 8.2 présente la hiérarchie des types JSON de GWT. JSONValue est la
superclasse abstraite de tous les types JSON ; pour savoir quel type est réellement
représenté par un objet JSONValue, on peut utiliser une des méthodes isXxxx() qui
renvoie une référence non nulle vers une instance du type Xxxx si et seulement si la
valeur représentée est du type Xxxx. Par exemple, isNumber() retourne un JSONNumber
si la JSONValue représente un JSONNumber, ou null dans le cas contraire. Bien sûr, si
on connaît avec certitude le type concret de la JSONValue, on peut directement le
convertir via un cast.
La création d’un document JSON se fait de façon très simple en instanciant
directement les classes JSON. Les types composites JSONObject et JSONArray sont
remplis respectivement avec les méthodes put(String, JSONValue) et set(int,
JSONValue).
L’exemple suivant montre la création d’un document JSON :
JSONObject livre = new JSONObject();
livre.put("Titre", new JSONString("GWT"));
JSONArray chapitres = new JSONArray();
JSONObject chap1 = new JSONObject();
chap1.put("Titre", new JSONString("Introduction"));
chap1.put("Page", new JSONNumber(10));
chapitres.set(0, chap1);
JSONObject chap2 = new JSONObject();
chap2.put("Titre", new JSONString("Hello world"));
chap2.put("Page", new JSONNumber(38));
chapitres.set(1, chap2);
livre.put("Chapitres", chapitres);
String json = livre.toString();
Le résultat (avec retours ligne ajoutés) :
{
"Titre":"GWT",
"Chapitres":[
{"Titre":"Introduction", "Page":10},
{"Titre":"Hello world", "Page":38}]
}
128
Chapitre 9. Envoyer des requêtes HTTP
9.5 ACCÈS À DES WEB SERVICES JSON
On l’a dit, l’envoi direct de requêtes HTTP depuis le code client GWT n’est possible
que vers le serveur d’où a été chargé ce code, à cause de la contrainte de sécurité « Same
Origin Policy » (SOP) imposée par les navigateurs. Ce problème n’affecte d’ailleurs pas
que GWT mais toutes les applications AJAX.
Dès lors, comment faire pour accéder à un web service dont le serveur se trouve
n’importe où sur le Web ? Heureusement, une technique existe ; celle-ci consiste à
injecter dynamiquement (au travers de manipulations du DOM) un élément <SCRIPT>
dont la source pointe vers l’URL du web service à appeler. Ce script étant inséré
dynamiquement, il n’est pas soumis à la contrainte SOP et peut donc accéder à
n’importe quelle URL. Cependant, l’utilisation de cette technique n’est possible que
sous certaines conditions.
• Étant donné que le résultat de la requête est utilisé comme source du script
injecté, celui-ci doit être du JavaScript. Fort heureusement, beaucoup de web
services peuvent retourner le résultat au format JSON, qui est lui-même un
sous-ensemble de JavaScript. Par conséquent cette technique n’est utilisable
qu’avec des web services capables de produire du JSON.
• Le web service doit également être capable de générer du code qui encapsule
la réponse dans un appel à une fonction JavaScript arbitraire, pour pouvoir
signaler au code GWT qu’une réponse a été reçue. Cette variante de JSON est
quelquefois appelée JSONP.
Pour faciliter la mise en œuvre de cette technique, on peut écrire une classe
utilitaire JSONRequestHelper qui gère toute cette mécanique.
On va tout d’abord définir une interface JSONResponseHandler de type callback,
que le code client devra implémenter pour recevoir la réponse. Pour la compacité, on
a implémenté cette interface comme un type imbriqué de JSONRequestHandler :
public interface JSONResponseHandler {
public void onResponseReceived(JavaScriptObject json);
}
On suppose qu’on fournira à notre classe utilitaire une URL à laquelle il restera
juste à concaténer le nom du callback JavaScript. L’appel au web service se fera en
passant cette URL incomplète et une instance de JSONResponseHandler à une méthode
statique :
/**
* Effectue un appel à un web service JSON. Le web service doit
* supporter les options "JSON in script" et "JavaScript callback".
*
* @param url L’URL du service à appeler, moins le nom du callback.
*
Le nom du callback sera concaténé à la fin de l’URL; celle-ci
*
devra donc se terminer par un paramètre du type "callback=".
* @param handler une instance de JSONResponseHandler dont
*
la méthode onResponseReceived
9.5 Accès à des web services JSON
129
*
sera appelée après réception du résultat.
*/
public static void get(String url, JSONResponseHandler handler) {
// Construire un nom de callback propre au handler.
// Ceci permet de gérer des situations
// où plusieurs appels simultanés sont faits
// avec des handlers différents.
String callbackName = "callback" + handler.hashCode();
get(url + callbackName, callbackName, handler);
}
Le cœur de cette classe est constitué par une méthode native JSNI (voir § 7.1 JSNI
(JavaScript Native interface)) :
/**
* Méthode native JSNI qui effectue la manipulation du DOM.
* @param url L’URL complète (y compris le callback)
* @param callbackName Le nom du callback
* @param handler Le handler client à appeler
*/
private native static void get(String url, String callbackName,
JSONResponseHandler handler) /*-{
callback = function(j) {
[email protected].
JSONRequestHelper$JSONResponseHandler::onResponseReceived
(Lcom/google/gwt/core/client/JavaScriptObject;)(j);
};
$wnd[callbackName] = callback;
var script = $wnd.document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", url);
$wnd.document.getElementsByTagName("head")[0].appendChild(script);
}-*/;
Quelques mots d’explication : cette méthode commence par enregistrer une
fonction portant le nom du callback passé en paramètre, et qui se contente d’appeler
le handler spécifié. Ensuite, on insère l’élément SCRIPT avec l’URL spécifiée comme
source ; ceci déclenche l’envoi d’une requête HTTP. Lorsque le résultat est reçu, il est
interprété comme du JavaScript qui doit déclencher l’appel à la fonction de callback
(pourvu que le serveur honore la convention JSONP), puis à notre handler client.
Ouf !
L’appel lui-même se résume à ceci :
JSONRequestHelper.get(url, new JSONResponseHandler() {
public void onResponseReceived(JavaScriptObject json) {
// Faire quelque chose avec l’objet reçu.
// En general il s’agit d’un flux JSON qu’on peut
// convertir en JSONObject pour le manipuler.
JSONObject jsonValue = new JSONObject(json);
// Traiter l’objet jsonValue...
}
});
Le code complet de la classe JSONRequestHelper est accessible en annexe 5.
130
Chapitre 9. Envoyer des requêtes HTTP
Attention
Cette classe est à considérer comme une preuve de concept, mais en aucun cas comme
une classe de production ; en particulier les scripts insérés ne sont pas nettoyés après
usage, ce qui risque de provoquer des fuites de mémoire en cas d’usage intense.
Pour illustrer l’utilisation de cette classe, nous allons créer une petite application
qui accède à un web service compatible. L’interface permettra de saisir l’URL du
service à appeler, un bouton Envoyer effectuera l’appel, et affichera le résultat dans
une TextArea (figure 8.3).
Figure 9.3 — Interface utilisateur du client de test JSONP
Le code de la page HTML hôte et le code client sont accessibles en annexe 5.
Par défaut, nous allons utiliser un web service des API Google GData ; celui-ci
supporte le format de résultat nécessaire grâce aux paramètres de la requête alt=jsonin-script et callback=xxx. L’URL qu’on va utiliser sera :
http://www.google.com/calendar/feeds/developer-calendar
@google.com/public/full?alt=json-in-script&callback=
On voit que la valeur du paramètre callback n’est pas renseignée ; c’est normal car
elle le sera par JSONRequestHelper.
Si on accepte l’URL par défaut et qu’on clique sur Envoyer, après quelques secondes
le résultat doit apparaître dans la TextArea sous forme d’un flux JSON (figure 8.4).
Bien sûr, dans une application réelle, il faudrait interpréter le JSON reçu pour en
faire quelque chose d’utile, ceci au moyen des classes JSON dédiées à cet effet (voir
§ 8.4 Manipulation de JSON).
131
9.6 Proxy serveur
Figure 9.4 — Le flux JSON retourné
9.6 PROXY SERVEUR
La technique précédente fondée sur JSONP fonctionne effectivement, et selon les
choix d’architecture, elle peut s’avérer la seule option viable, en particulier s’il n’est
pas possible ou souhaitable d’implémenter des services sur le serveur qui sert le code
GWT.
En revanche, si on a la possibilité d’implémenter un service, que ce soit via GWT
RPC ou une autre technique, alors ce service peut jouer le rôle de proxy et dialoguer
avec tout serveur web, où qu’il soit. Les contraintes de la politique SOP ne s’appliquent
plus puisque la requête n’émane pas du navigateur, mais du serveur d’applications, qui
agit comme intermédiaire.
10
Créer ses propres widgets
Objectif
Ce chapitre vous expliquera les différentes façons de créer un widget GWT
spécifique.
Les widgets utilisables avec GWT ne se limitent heureusement pas au catalogue
de widgets natifs ; GWT a en effet prévu la possibilité pour les développeurs et
fournisseurs de solutions tierces de créer des widgets spécifiques.
Il existe différentes techniques pour y parvenir :
• sous-classer un widget existant ;
• créer un widget composite grâce à la classe Composite ;
• implémenter complètement un widget en Java ;
• faire appel à JSNI pour implémenter tout ou partie d’un widget en JavaScript.
Nous allons détailler ces différentes techniques.
134
Chapitre 10. Créer ses propres widgets
10.1 SOUS-CLASSER UN WIDGET EXISTANT
Lorsqu’on a affaire à une bibliothèque de composants objet, cette méthode est
la plus naturelle : on utilise comme super-classe le widget qui ressemble le plus
à ce qu’on veut implémenter, et on surcharge les méthodes adéquates pour obtenir
le comportement voulu.
De fait, cette technique a déjà été mise en œuvre à plusieurs reprises dans les
exemples de code illustrant la description de certains widgets :
• dans la section consacrée au DialogBox du paragraphe § 4.2.2 Widgets simples ;
• dans la section consacrée aux Grid et FlexTable du paragraphe § 4.2.3 Panels.
Cette technique fonctionne parfaitement, mais elle n’est pas recommandée par
Google pour des raisons de performance, et aussi parce que la sous-classe peut ne pas
souhaiter exposer toutes les méthodes de la classe qu’elle étend. Ainsi, si vous créez un
widget sur base de la classe HorizontalPanel, vous allez exposer toutes les méthodes
de HorizontalPanel, telles que add, insert, etc., ce qui n’est probablement pas ce que
vous souhaitez. Il reste la possibilité de surcharger ces méthodes pour ne rien faire ou
lever une exception, mais cette solution est pour le moins peu élégante.
Pour ces raisons, GWT suggère de préférer la technique faisant usage de la classe
Composite.
10.2 UTILISER LA CLASSE COMPOSITE
Un Composite est un widget particulier qui contient un autre widget et se comporte
exactement comme ce widget. En général, le widget encapsulé est un panel, ce qui
permet de créer des composants riches en combinant différents widgets.
Cette technique est d’ailleurs utilisée à plusieurs reprises dans GWT lui-même :
• TabPanel est un Composite qui combine un TabBar et un DeckPanel ;
• SuggestBox est un Composite qui combine une TextBox et un MenuBar.
Il est au passage vivement conseillé de consulter le code source de ces classes pour
mieux comprendre comment fonctionne un Composite ! C’est un des avantages de
l’Open Source pour les développeurs.
La création d’un widget grâce à la classe Composite est assez similaire au sousclassage, avec les différences présentées dans le tableau 10.1.
C’est l’appel à initWidget(Widget) qui spécifie au Composite quel est le widget
encapsulé.
135
10.3 Implémenter complètement un widget en Java
Tableau 10.1 — Différences entre le sous-classage et la classe Composite
Sous-classage
La classe hérite de la classe de widgets W
Le constructeur fait appel au constructeur
de W et initialise les sous-widgets
Utilisation de Composite
La classe hérite de Composite
La classe possède une variable de type W
Le constructeur instancie W et le configure, puis
appelle initWidget(w)
À titre d’exemple, on va implémenter un widget composite qui peut servir à
sélectionner le sexe d’une personne au moyen de deux boutons radio. Avec l’approche
d’héritage direct, on pourrait créer une sous-classe de VerticalPanel, au risque d’exposer les méthodes de VerticalPanel. Il est donc préférable d’en faire un composite,
dont le widget principal est un VerticalPanel :
public class SexeRadio extends Composite {
private VerticalPanel panel;
private RadioButton rbm;
private RadioButton rbf;
public SexeRadio() {
panel = new VerticalPanel();
rbm = new RadioButton("sexerb", "M");
rbf = new RadioButton("sexerb", "F");
panel.add(rbm);
panel.add(rbf);
initWidget(panel);
}
public boolean isM() {
return rbm.getValue();
}
public boolean isF() {
return rbf.getValue();
}
}
On peut ensuite l’instancier et l’utiliser comme n’importe quel widget. Les seules
méthodes exposées par ce widget, outre celles de Composite, sont les méthodes isM()
et isF() qui permettent de savoir quelle case a été cochée.
10.3 IMPLÉMENTER COMPLÈTEMENT UN WIDGET
EN JAVA
Plutôt que d’implémenter un widget par héritage ou composition de widgets existants,
on peut s’attaquer à la création d’un widget personnalisé à un niveau d’abstraction
en dessous, c’est-à-dire en manipulant directement le DOM. Bon nombre de widgets
de base du catalogue GWT sont écrits de cette manière, comme Button ou TextBox,
en s’appuyant sur des composants HTML natifs. De nouveau, nous vous invitons
à consulter le code source de ces widgets si vous souhaitez voir comment ils sont
implémentés.
136
Chapitre 10. Créer ses propres widgets
La manipulation du DOM se fait au travers de classes GWT spécialement prévues
à cet effet :
• Document est la classe principale ; elle encapsule toute la hiérarchie HTML
de la page et fournit en outre une flopée de méthodes utilitaires pour créer
des éléments HTML et événements, et obtenir des informations sur la page.
On obtient l’instance courante de Document en appelant la méthode statique
Document.get().
• Element est la classe de base de tous les éléments ; il existe une sous-classe
correspondant à chacun des types d’éléments HTML, par exemple ButtonElement représente un élément <button>, InputElement représente un élément
<input>, etc. Pour créer un Element, on appellera la méthode correspondante de
Document, par exemple createButtonElement() pour créer un ButtonElement.
Voici par exemple comment est implémenté le constructeur de Button dans le code
source de GWT :
/**
* Creates a button with no caption.
*/
public Button() {
super(Document.get().createPushButtonElement());
setStyleName("gwt-Button");
}
On voit que le Button de GWT s’appuie totalement sur l’élément HTML correspondant, obtenu par l’appel à createPushButtonElement(). Ceci illustre une politique
de GWT qui est de s’appuyer sur les contrôles HTM natifs lorsque c’est possible.
10.4 IMPLÉMENTER TOUT OU PARTIE D’UN WIDGET
EN JAVASCRIPT
En dernier recours, il est aussi possible d’utiliser JSNI (voir § 7.1 JSNI (JavaScript
Native Interface)) pour écrire tout ou partie de l’implémentation d’un widget personnalisé en JavaScript. Cependant, si on choisit cette méthode, il faut être conscient des
points suivants (qui sont valables de façon générique pour toute utilisation de JSNI) :
• les différences éventuelles de comportement entre les différentes plates-formes
cibles ne sont plus prises en charge par GWT. En cas de divergence d’un
navigateur à l’autre, ce sera à vous de prendre vos précautions pour assurer
un comportement homogène ;
• vous ne bénéficierez plus des contrôles statiques du compilateur Java, et
d’éventuels problèmes dans le code JavaScript imbriqué ne seront détectés que
lors de l’exécution de ce code ;
• le débogage en sera d’autant plus compliqué.
10.4 Implémenter tout ou partie d’un widget en JavaScript
137
Lorsque vous choisissez d’utiliser cette méthode, il est conseillé de regrouper
les méthodes faisant appel à JSNI dans une classe instanciée par Deferred Binding
(voir § 7.2 Deferred Binding), c’est-à-dire au travers de GWT.create(), et non par une
référence directe. Ceci permettra, le cas échéant, de fournir une version différente de
cette classe en fonction de la plate-forme.
11
Bibliothèques tierces
Objectif
En donnant la possibilité de créer ses propres widgets, les créateurs de GWT ont
permis l’apparition de bibliothèques de composants qui complètent ou remplacent les
widgets de base. Néanmoins, les bibliothèques tierces ne se limitent pas aux aspects
d’IHM, et de véritables frameworks basés sur GWT ont vu le jour.
Ce chapitre a pour objectif de présenter les principales d’entres elles.
11.1 BIBLIOTHÈQUES DE COMPOSANTS
11.1.1 GWT-ext
Ceux d’entre vous qui ont déjà pratiqué la programmation JavaScript connaissent
peut-être « Ext JS » (autrefois appelé simplement « Ext ») : il s’agit d’une bibliothèque
JavaScript articulée autour d’une palette de composants graphiques riches et flexibles,
par exemple : des grilles incluant le filtrage et le tri, la possibilité de réordonner les
colonnes, le support de la pagination, les combo boxes, les barres d’outils, etc. Ext JS est
destiné au départ à la création d’applications RIA « à l’ancienne » c’est-à-dire codées
directement en JavaScript.
GWT-ext est un projet qui vise à permettre l’utilisation des composants Ext
JS dans une application GWT ; pour cela, GWT-ext met à profit la possibilité
offerte par GWT d’implémenter une fonction en JavaScript natif grâce à JSNI.
GWT-ext constitue donc un « wrapper » ou adaptateur, qui expose une interface
Java/GWT pour les fonctionnalités de Ext JS. La présence de la bibliothèque
Ext JS est cependant toujours requise, puisque les appels aux méthodes de GWT-Ext
sont en réalité convertis en appels Ext JS.
140
Chapitre 11. Bibliothèques tierces
GWT-Ext souffre toutefois d’un handicap important : en effet, suite à un changement de la licence accompagnant Ext JS depuis la version 2.1, GWT-Ext ne peut plus
utiliser de version de Ext JS postérieure à la version 2.0.2. Cependant, malgré cela,
GWT-Ext offre une richesse et une maturité inégalées, c’est pourquoi elle reste une
option viable pour une nouvelle application GWT ; il suffit de voir le « showcase » qui
présente tous les composants pour s’en convaincre : http://www.gwt-ext.com/demo.
La dernière version de GWT-Ext est 2.0.6, qu’on peut trouver sur la page suivante
(avec un lien pour télécharger Ext JS 2.0.2) : http://code.google.com/p/gwt-ext.
À noter que le développeur principal de GWT-Ext, Sanjiv Jivan, travaille désormais sur une autre bibliothèque, SmartGWT, que nous évoquerons plus loin.
Installation
L’installation de GWT-Ext est assez mal documentée sur le Web, mais si vous suivez
attentivement les étapes ci-après, cela ne posera pas de problèmes. En raison de sa
nature, l’installation se passe en deux étapes :
1. installation de la bibliothèque JavaScript Ext JS ;
2. installation de la bibliothèque et du module GWT-ext.
Étape 1 : Installer Ext JS
1. Téléchargez Ext JS 2.0.2 depuis la page citée plus haut. Vous devriez obtenir
un fichier ext-2.0.2.zip.
2. Décompressez ce fichier dans un dossier temporaire. Vous devriez obtenir les
fichiers présentés en figure 11.1.
Figure 11.1 — Contenu de l’archive EXT JS
3. Dans le dossier war de votre projet GWT, créez un dossier js puis un sous-dossier
ext.
11.1 Bibliothèques de composants
141
4. Copiez les dossiers et fichiers suivants depuis l’endroit où vous avez décompressé
l’archive Ext-JS vers le dossier js/ext que vous venez de créer :
–
–
–
–
–
–
adapter/
resources/
ext-all.js
ext-all-debug.js
ext-core.js
ext-core-debug.js
5. Après l’opération, rafraîchissez le projet dans Eclipse (clic droit puis Refresh)
pour prendre en compte les nouveaux fichiers. Dans la vue Navigator d’Eclipse,
le projet doit avoir la structure présentée en figure 11.2.
Figure 11.2 — Structure du projet pour Ext JS
Étape 2 : Installer GWT-Ext
1. Téléchargez la dernière version de GWT-Ext depuis la page citée plus haut.
Vous devriez obtenir un fichier gwtext-2.0.x.zip.
2. Décompressez ce fichier dans un dossier temporaire. Vous devriez obtenir les
fichiers présentés en figure 11.3.
Figure 11.3 — Contenu de l’archive GWT-Ext
142
Chapitre 11. Bibliothèques tierces
3. Copiez le fichier gwtext.jar dans le dossier war/WEB-INF/lib de votre projet.
4. Après l’opération, rafraîchissez le projet dans Eclipse (clic droit puis Refresh)
pour prendre en compte le nouveau fichier. Dans la vue Navigator d’Eclipse, le
projet doit avoir la structure présenté en figure 11.4.
Figure 11.4 — Structure du projet pour GWT-Ext
5. Ajoutez le nouveau JAR dans le build path du projet :
–
–
–
–
–
clic droit sur le projet puis Properties ;
sélectionnez dans la liste de gauche la catégorie Java Build Path ;
sélectionnez l’onglet Libraries ;
cliquez sur Add JARs... ;
dans la fenêtre de sélection qui s’ouvre, sélectionnez le fichier JAR que vous
venez d’ajouter à votre projet (figure 11.5).
Figure 11.5 — Sélection du JAR gwtext.jar
11.1 Bibliothèques de composants
143
Au final, le build path devra être comme sur la figure 11.6.
Figure 11.6 — Build path final pour GWT-Ext
6. Modifiez le fichier module du projet pour y ajouter les lignes suivantes :
<!-- Inclure le module GWT-ext -->
<inherits name=’com.gwtext.GwtExt’ />
<!-- Inclure le CSS et le code Ext JS -->
<stylesheet src="/js/ext/resources/css/ext-all.css" />
<script src="/js/ext/adapter/ext/ext-base.js" />
<script src="/js/ext/ext-all.js" />
Voilà, le projet est prêt pour développer avec GWT-ext !
Utilisation
GWT-Ext introduit une hiérarchie de widgets parallèle à celle de GWT. Dans
GWT, tous les composants héritent de la classe Widget, et les composants qui
peuvent contenir d’autres éléments héritent de la classe Panel. Par contraste, tous
les composants de GWT-Ext héritent de la classe Component, et ceux qui peuvent
contenir d’autres composants héritent de la classe Container.
Fort heureusement, Component est lui-même une sous-classe de Widget, ce qui
permet de faire cohabiter des éléments de GWT-Ext et des éléments natifs de GWT.
Cependant, la richesse des composants GWT-Ext rend cela sans intérêt.
Le composant central de GWT-Ext est Panel ; il s’agit d’un composant très
versatile qui peut aussi bien être un simple conteneur pour gérer la disposition d’autres
composants, que contenir une barre d’outils, des sections d’entête et de pied, etc.
144
Chapitre 11. Bibliothèques tierces
L’exemple suivant crée deux panels « collapsibles » (réductibles) côte à côte.
package oge.gwt.chap1111.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;
import com.gwtext.client.widgets.Panel;
import com.gwtext.client.widgets.layout.HorizontalLayout;
/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class GwtChap1111 implements EntryPoint {
private static final String HTML_TEXT =
"<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. "
+ "Sed metus nibh, sodales a, porta at, vulputate eget, dui. "
+ "In pellentesque nisl non sem. Suspendisse nunc sem, pretium eget, "
+ "cursus a, fringilla vel, urna.";
/**
* Point d’entrée
*/
public void onModuleLoad() {
Panel main = new Panel();
main.setLayout(new HorizontalLayout(15));
main.setPaddings(15);
Panel panel = new Panel();
panel.setTitle("Panel 1");
panel.setWidth(200);
panel.setCollapsible(true);
panel.setHtml(HTML_TEXT);
main.add(panel);
Panel panel2 = new Panel();
panel2.setTitle("Panel 2");
panel2.setWidth(300);
panel2.setCollapsible(true);
panel2.setHtml(HTML_TEXT);
main.add(panel2);
RootPanel.get().add(main);
}
}
On voit qu’il est très aisé de créer des interfaces complexes, et avec un résultat
graphique intéressant et en tout cas plus évolué que GWT de base (figure 11.7).
Une différence importante avec GWT est la façon de gérer la disposition (layout)
des sous-widgets. En effet, alors que les panels de GWT sont spécifiquement dédiés à
un type de disposition (HorizontalPanel, VerticalPanel, FlexGrid, etc.), au contraire
dans GWT-Ext, un conteneur n’impose pas par lui-même de disposition de ses
composants-fils, et on peut lui affecter un type de disposition au moyen de la méthode
setLayout(ContainerLayout).
En fonction de cette disposition, il est ensuite possible d’ajouter des contraintes
spécifiques à un des fils au moment de son ajout, en passant un paramètre supplémentaire de type LayoutData à la méthode add(Component) de la classe Container. La
classe concrète de l’instance de LayoutData doit correspondre au layout du conteneur ;
par exemple, si on affecte à un panel le layout BorderLayout, c’est une instance de
11.1 Bibliothèques de composants
145
BorderLayoutData qu’il faudra passer à la méthode add. Ce pattern de conception se
retrouve dans d’autres bibliothèques d’interface utilisateur, comme Swing.
Figure 11.7 — Exemple d’interface créée avec GWT-Ext
Pour aller plus loin
GWT-Ext hérite de toute la richesse de Ext-JS, ce qui en fait une bibliothèque
relativement complexe qu’il est impossible de décrire complètement dans ce livre.
Malheureusement, la documentation est assez disparate et parfois lacunaire. Par contre,
chacun des exemples du showcase (dont on a donné l’adresse plus haut) est accompagné
du code source Java correspondant, ce qui constitue une ressource précieuse !
Il existe aussi un forum (anglophone) à l’adresse http://gwt-ext.com/forum.
11.1.2 Ext-GWT
En parallèle au développement de Ext JS, une bibliothèque nommée MyGWT a vu le
jour. Son but était simple : reprendre toutes les caractéristiques qui ont fait le succès
de Ext JS, y compris le look & feel des composants, mais en Java/GWT pur, c’est-à-dire
sans utiliser Ext JS (contrairement à GWT-ext).
MyGWT a été renommé Ext-GWT en 2008, et intégré au portfolio de la société
Ext. Si l’objectif visé est sans doute d’homogénéiser les noms des produits de la gamme
(« Ext JS » et « Ext GWT »), cela n’est pas sans créer une certaine confusion avec
la bibliothèque GWT-Ext. Pour éviter (ou ajouter à) cette confusion, Ext-GWT est
parfois aussi appelé GXT.
Fonctionnellement, Ext-GWT présente de nombreuses nouveautés par rapport à
GWT-Ext ; on peut citer entre autres :
• une API de génération de graphiques (qui s’appuie sur flash) ;
• le « data binding » c’est-à-dire la possibilité de lier directement les champs d’un
formulaire aux propriétés d’un objet ;
• un éditeur HTML intégré.
146
Chapitre 11. Bibliothèques tierces
Tableau 11.1 — Différences entre les deux bibliothèques Ext-GWT et GWT-Ext
Type
Dernière version
Prérequis
Licence
Développeur/éditeur
Site web
Ext-GWT = GXT
(anciennement MyGWT)
Bibliothèque 100 % Java/GWT
2.0.1
GWT 1.6+
au choix : GPL v3 ou
commerciale
Darrel Meyer / Ext
http://extjs.com/products/gxt
GWT-Ext
Wrapper JavaScript
2.0.6
GWT 1.5.3+, Ext JS 2.0.2
LGPL
Sanjiv Jivan & autres
http://gwt-ext.com,
http://code.google.com/p/gwt-ext
Installation
GWT-Ext étant « stand-alone » (il ne dépend pas d’une bibliothèque externe JavaScript), son installation est beaucoup plus simple que celle de GWT-ext.
1. Téléchargez « Ext GWT 2.0.x SDK » (dernière version) depuis la page
http://extjs.com/products/gxt/download.php. Vous devriez obtenir un fichier
gxt-2.0.x.zip.
2. Décompressez ce fichier dans un dossier temporaire. Vous devriez obtenir les
fichiers présentés en figure 11.8.
Figure 11.8 — Contenu de l’archive GXT
3. Copiez le fichier gxt.jar dans le dossier war/WEB-INF/lib de votre projet.
4. Copiez tous les dossiers contenus dans le sous-dossier resources vers le dossier
war de votre projet. Les dossiers concernés sont :
–
–
–
–
–
–
charts/
css/
desktop/
flash/
images/
themes/
147
11.1 Bibliothèques de composants
5. Après l’opération, rafraîchissez le projet dans Eclipse (clic droit puis Refresh)
pour prendre en compte les nouveaux fichiers. Dans la vue Navigator d’Eclipse,
le projet doit avoir la structure présenté en figure 11.9.
Figure 11.9 — Structure du projet pour GXT
6. Ajoutez le nouveau JAR dans le build path du projet :
–
–
–
–
–
clic droit sur le projet puis Properties ;
sélectionnez dans la liste de gauche la catégorie Java Build Path ;
sélectionnez l’onglet Libraries ;
cliquez sur Add JARs... ;
dans la fenêtre de sélection qui s’ouvre, sélectionnez le fichier JAR que vous
venez d’ajouter à votre projet (figure 11.10).
Figure 11.10 — Sélection de l’archive JAR gxt.jar
148
Chapitre 11. Bibliothèques tierces
Au final le build path devra être comme présenté sur la figure 11.11.
Figure 11.11 — Build path final pour GXT
7. Modifiez le fichier module du projet pour y ajouter les lignes suivantes :
<!-- Inclure le module Ext GWT -->
<inherits name=’com.extjs.gxt.ui.GXT’/>
<!-- Inclure la feuille de style -->
<stylesheet src="/css/gxt-all.css" />
Utilisation
Bien que réécrite en GWT/Java sans l’aide de Ext-JS, Ext-GWT montre clairement
son lien de parenté avec Ext-JS ; en effet on retrouve beaucoup de similitudes avec
GWT-Ext. Ainsi la classe ContentPanel est le pendant de la classe Panel de GWT-Ext.
On retrouve aussi dans Ext-GWT une classification des widgets similaire :
• tous les widgets de Ext-GWT sont des sous-classes de Component ;
• les composants pouvant avoir des composants-fils sont des sous-classes de
Container.
Une autre similitude est la gestion des layouts : comme pour GWT-Ext, on associe
un Layout au conteneur, et on ajoute les composants-fils en passant un LayoutData
approprié à la méthode add.
Ext-GWT offre en outre une possibilité intéressante de lier directement les champs
d’un formulaire aux propriétés d’un objet ; c’est ce qu’on appelle le « data binding ». De
cette manière, par le jeu de listeners et de convertisseurs, le changement d’une valeur
dans un champ entraîne immédiatement la mise à jour de la propriété correspondante
de l’objet ; inversement, un changement dans la valeur d’une propriété entraînera la
mise à jour du champ qui lui est lié.
Malheureusement, étant donné l’impossibilité de faire de l’introspection en GWT,
le data binding n’est pas possible avec n’importe quel Java Bean, mais seulement avec
11.1 Bibliothèques de composants
149
un type de modèle spécifique à Ext-GWT (BaseModel) dans lequel les propriétés sont
stockées sous forme de Map.
Ext-GWT contient aussi un embryon de framework MVC (Model-View-Controller),
ou qui se dit comme tel ; cependant il possède de nombreux vices de conception qui
le rendent difficile à utiliser, et il est souhaitable de s’en passer au profit d’un autre
framework MVC tel PureMVC, ou d’un développement propre.
Pour aller plus loin
Contrairement à GWT-Ext qui est sous licence LGPL, Ext-GWT est disponible soit
sous licence GPL, ce qui oblige tout logiciel l’utilisant à être lui-même disponible sous
GPL, soit avec une licence commerciale donnant droit à un support préférentiel. Cela
peut influer fortement sur le choix d’une bibliothèque.
Ext-GWT est un peu mieux documenté que GWT-Ext, mais contrairement à
l’affirmation du site web, son code source est encore loin d’être « bien conçu,
cohérent et entièrement documenté ». La principale source de documentation
reste le showcase, qui donne également accès au code source des exemples :
http://www.extjs.com/examples.
Un forum est également ouvert, mais attention, certaines sections dédiées au
support préférentiel ne sont accessibles qu’aux titulaires d’une licence commerciale :
http://www.extjs.com/forum.
11.1.3 SmartGWT
SmartGWT est le nouveau projet de Sanjiv Jivan, le créateur de GWT-Ext ; il
s’agit là aussi d’un wrapper, cette fois pour la partie « client AJAX » de SmartClient
(http://www.smartclient.com), un framework complet de développement qui offre non
seulement des composants pour la réalisation de l’interface utilisateur, mais en outre
permet de lier ces widgets avec les données côté serveur.
Même si SmartClient est conçu pour être une solution de bout en bout (du serveur
au poste client), SmartGWT peut cependant être utilisé comme une solution « client
seulement ».
SmartGWT, tout comme l’ensemble de SmartClient, est disponible avec plusieurs
options de licence, dont la licence LGPL. Ceci le met en bonne position par rapport
à GWT-Ext (dont le développement se limite désormais à la correction de bugs), et
Ext-GWT (qui n’est pas disponible sous licence LGPL).
SmartGWT est hébergé sur Google Code et peut être téléchargé depuis la
page suivante : http://code.google.com/p/smartgwt/
11.1.4 Autres composants
En plus des bibliothèques décrites ci-avant, GWT a donné naissance à une myriade
de projets plus petits, moins aboutis ou moins cohérents que celles-ci. Nous allons en
citer quelques uns qui présentent un intérêt particulier.
150
Chapitre 11. Bibliothèques tierces
GWT Incubator
GWT Incubator n’est pas un projet à proprement parler, mais plutôt l’endroit où sont
développées les futures extensions de GWT, widgets ou bibliothèques, avant qu’elles
soient intégrées au toolkit de base (pour celles qui le seront).
GWT Incubator contient à ce jour les fonctionnalités suivantes :
• catégorie « widgets » :
– ScrollTable, une table scrollable avec un entête et un pied fixes,
– PagingScrollTable, une table scrollable et paginée,
– Bulk table renderers, des utilitaires permettant d’optimiser l’affichage des
tables en effectuant le rendu en une seule opération pour toute la table (au
lieu de cellule par cellule),
– CollapsiblePanel, un panel affichable quand la souris suvole une zone,
– GlassPanel, qui permet de griser/coloriser un panel,
– FastTree, un arbre optimisé pour gérer efficacement un grand nombre de
nœuds,
– SliderBar, un widget qui permet de faire une sélection grâce à un curseur
(slider),
– Spinner, un widget qui permet d’incrémenter/décrémenter la valeur d’un
champ grâce à un paire de boutons,
– DropDownListBox, un équivalent de ListBox mais qui facilite l’application
des styles,
– ProgressBar, un classique indicateur de progrès,
– SimpleWidget, une façon de créer un widget très facilement ;
• catégorie « bibliothèques » :
– une bibliothèque de validation pour la validation côté client,
– une bibliothèque permettant de faire du logging côté client,
– GWTCanvas, une bibliothèque qui permet de faire du dessin vectoriel dans le
navigateur ;
• catégorie « utilitaires » :
– Decorator, qui permet de décorer des widgets en leur ajoutant des éléments
HTML spécifiques,
– CssResource/StyleInjector, qui permet d’améliorer la gestion des feuilles
de style CSS en les rendant extensibles, modulaires, internationalisables,
injectables et manipulables dynamiquement, etc. Ce sous-projet est d’ores
et déjà planifié pour faire partie de la prochaine version de GWT (2.0),
– SoundRessource, qui permet de gérer les sons (nécessite le plugin Flash),
– LocalListLister, qui permettent d’obtenir dynamiquement la liste de
locales compilées dans un projet afin d’offrir un choix à l’utilisateur.
11.1 Bibliothèques de composants
151
GWT Component Library
GWT component Library est une collection de widgets divers qui répondent à des
besoins spécifiques sans cohérence particulière.
Ces widgets ont pour la plupart un caractère expérimental et ne sont donc pas
recommandés pour une application destinée à la production... ou alors, à vos risques et
périls. En outre, certains d’entre eux sont devenus obsolètes puisque leur fonctionnalité
a été depuis intégrée à GWT (Auto-Completion Textbox > SuggestBox, Simple XML
Parser > XMLParser).
On trouve ces composants sur la page suivante :
http://gwt.components.googlepages.com.
GWT Widget Library
GWT Widget Library est la partie « widgets » d’une bibliothèque qui comprend
également des classes destinées à faciliter l’implémentation de services RPC (côté
serveur) appelée GWT Server Library.
On y trouve également un certain nombre de widgets originaux comme une
calculatrice popup (PopupCalcPanel), un label texte qui se transforme en champ
éditable lorsqu’on clique dessus (EditableLabel), etc.
Malheureusement, la documentation se limite à la JavaDoc et aucun showcase pour
avoir un aperçu visuel...
Téléchargement : http://gwt-widget.sourceforge.net.
GWTiger
GWTiger est un framework léger bâti sur GWT qui apporte les fonctionnalités
suivantes :
• champs pour formulaires incluant label et gestion d’un éventuel message
•
•
•
•
•
d’erreur ;
validation ;
contrôles de saisie selon un masque ;
lazy loading ou instanciation tardive, permettant de ne construire un écran que
lorsqu’on en a besoin ;
écran de login ;
support automatique du bouton back.
Site web : http://gwtiger.org.
152
Chapitre 11. Bibliothèques tierces
11.2 BIBLIOTHÈQUES UTILITAIRES
11.2.1 Gwittir
Gwittir au nom imprononçable est un ensemble très cohérent de classes utilitaires
pour GWT qui se concentre sur des fonctionnalités manquantes, telles que :
• le binding, c’est-à-dire la possibilité de lier un composant graphique avec un
•
•
•
•
•
•
modèle, de sorte que tout changement sur l’un mette à jour l’autre ;
la validation, afin de vérifier la validité du modèle et de fournir un feedback à
l’utilisateur en cas d’erreur ;
une implémentation minimale de l’introspection qui permet d’interroger une
classe pour en connaître les propriétés ;
le support des raccourcis clavier ;
une API extensible de logging côté client ;
la gestion d’un flux applicatif (c’est-à-dire un enchaînement d’écrans) avec
support du mécanisme d’historique de GWT ;
une possibilité de streaming via RPC, c’est-à-dire la possibilité pour le serveur de
retourner un volume important de données en plusieurs fois, le tout étant géré
de façon transparente pour l’utilisateur.
Gwittir est documenté de façon succincte mais efficace dans le wiki du projet, qui
est hébergé sur google projets : http://code.google.com/p/gwittir/
11.2.2 GWT Server Library
GWT Server Library est la partie « serveur » qui fait le pendant de GWT Widget
Library déjà mentionné plus haut. GWT-SL est principalement axé sur l’intégration
de services GWT-RPC avec Spring sur le serveur.
En particulier, GWT-SL permet de publier sous forme de services RPC pour GWT
certaines méthodes d’un POJO (Plain Old Java Object, c’est-à-dire un objet Java sur
lequel on n’impose pas de contrainte spécifique). L’implémentation du service n’a par
conséquent aucune dépendance envers GWT.
GWT-SL permet aussi de mapper rapidement une URL avec un service RPC via
la configuration d’un bean (GWTHandler).
La documentation est concise mais complète, et se trouve avec les téléchargements
sur la page suivante : http://gwt-widget.sourceforge.net.
11.2.3 Google API
Indépendamment de GWT, Google propose aux développeurs JavaScript sous le nom
de « Google API » un certain nombre de bibliothèques permettant d’enrichir les
applications AJAX de fonctionnalités additionnelles, la plupart du temps en relation
avec des services en ligne de Google. Avec l’apparition de GWT, ces bibliothèques
11.2 Bibliothèques utilitaires
153
ont été portées pour GWT sous le nom de « Google API Libraries for Google
Web Toolkit », et permettent désormais de bénéficier des mêmes services que leurs
homologues JavaScript, directement en Java.
Les différentes API sont les suivantes :
• Gears permet de fournir des fonctionnalités locales aux applications qui s’exé-
•
•
•
•
•
cutent dans le navigateur, comme la sauvegarde de données sur le poste client,
des tâches d’arrière plan, etc. Cette API peut s’avérer essentielle dans le cadre
d’une application GWT, car elle peut faciliter l’implémentation d’un mode
« offline » lorsque le serveur n’est pas disponible par exemple ;
Gadgets facilite la création avec GWT de « gadgets », ces mini-applications
qu’on peut intégrer dans des pages web (comme la page d’accueil personnalisée
de igoogle.com) ;
Search permet l’intégration de la recherche Google dans une application GWT ;
Visualization permet de générer des représentations graphiques sous une
variété de formats tels que camemberts, timelines, cartes géographiques (toutes
les visualisations ne sont pas encore gérées par l’API GWT) ;
Language permet de fournir un accès aux fonctionnalités de traduction et de
translitération de Google Translate ;
Maps permet d’intégrer des cartes fournies par Google Maps dans une application
GWT.
Bien qu’elles fassent appel derrière les rideaux à des fonctions JavaScript, les API
Google pour GWT sont stand-alone et s’utilisent comme de simples bibliothèques Java.
En outre, la Javadoc incluse est complète. Cependant, il est toujours préférable de se
familiariser avec l’API originale, qui reste la référence.
Les API Google sont extrêmement riches, et leur étude n’entre pas dans le cadre
de ce livre. Cependant, à titre d’illustration, nous allons utiliser l’API Gears dans le
cadre d’un exemple.
Exemple d’utilisation de l’API Gears (Database)
Gears offre plusieurs fonctionnalités, parmi lesquelles :
• Database, une véritable base de données SQL locale ;
• LocalServer, qui permet de cacher localement des ressources (pages HTML,
fichiers JavaScript, images, etc.) destinées à faciliter l’utilisation en mode
déconnecté ;
• WorkerPool, qui permet de gérer des opérations en tâche de fond ;
• la géolocalisation, pour trouver aussi précisément que possible la position
géographique.
154
Chapitre 11. Bibliothèques tierces
Nous allons mettre en œuvre la partie « database », et créer une petite application
GWT qui stockera localement des données.
Installation
L’ensemble des bibliothèques GWT pour les API Google se trouve sur la page suivante :
http://code.google.com/p/gwt-google-apis.
L’installation de l’API Gears est aussi simple que possible :
1. téléchargez l’archive gwt-gears-1.2.x.zip depuis la page ci-dessus ;
2. décompressez l’archive dans un dossier temporaire. Celle-ci doit contenir
plusieurs fichiers et dossiers, dont le JAR lui même et de la documentation ;
3. copiez le fichier gwt-gears.jar dans le dossier war/WEB-INF/lib de votre projet
GWT ;
4. ajoutez ce fichier JAR au build path de votre projet (comme expliqué précédemment) ;
5. modifiez le fichier de module pour y inclure les lignes suivantes :
<!-- inclure le module Gears -->
<inherits name=’com.google.gwt.gears.Gears’/>
Utilisation
La base de données de Gears s’utilise au moyen d’un objet de type Database.
La séquence d’opérations est la suivante :
1. obtenir
une
instance
de
Database
Factory.getInstance().createDatabase() ;
en
faisant
appel
à
2. ouvrir une base de données avec Database.open() ;
3. exécuter des ordres SQL sur la base au moyen de Database.execute().
La base de données de Gears utilise le moteur Open Source SQLite, qui possède son
propre dialecte SQL. Le SQL de Gears est, à quelques variantes près, le même que celui
de SQLite ; il est documenté sur la page suivante : http://www.sqlite.org/lang.html.
Nous allons utiliser une base locale pour stocker des messages que l’utilisateur
entrera, associés à la date de leur saisie.
La création de la base se fait delà façon suivante :
// Créer la base si elle n’existe pas
try {
db = Factory.getInstance().createDatabase();
db.open("gears-database-demo");
db.execute("create table if not exists demo (
Message text, Timestamp int)");
} catch (GearsException e) {
Window.alert(e.toString());
}
11.2 Bibliothèques utilitaires
155
Ceci crée une base avec une table demo contenant deux colonnes. Pour y accéder
en lecture, on utilisera une instruction select avec un code similaire à ce qui suit :
// Récupérer les entrées existantes
List<String> messages = new ArrayList<String>();
List<String> timestamps = new ArrayList<String>();
try {
ResultSet rs = db.execute("select * from demo order by Timestamp");
for (int i = 0; rs.isValidRow(); ++i, rs.next()) {
messages.add(rs.getFieldAsString(0));
timestamps.add(rs.getFieldAsString(1));
}
rs.close();
} catch (DatabaseException e) {
Window.alert(e.toString());
}
Notez que l’API ressemble à ce qu’on ferait avec JDBC en Java natif.
De même, pour insérer une valeur, on procèdera comme suit :
// Ajouter une entrée dans la base
try {
db.execute("insert into demo values (?, ?)", new String[] { msg,
Long.toString(System.currentTimeMillis()) });
updateMessages();
msgBox.setText("");
} catch (DatabaseException e) {
Window.alert(e.toString());
}
Le code complet de l’exemple est accessible en annexe 6.
Enfin, la page HTML correspondante :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>GWT+Gears</title>
<script type="text/javascript" language="javascript"
src="gwtchap1124/gwtchap1124.nocache.js"></script>
</head>
<body>
<h1>Demo GWT+Gears</h1>
<div id="gears">
</body>
</html>
La figure 11.12 illustre le résultat dans un navigateur en mode web. Vous pouvez
vérifier que chaque fois que vous revenez sur la page, vous retrouvez la liste des messages
que vous avez entrés précédemment, grâce à la base de données locale.
156
Chapitre 11. Bibliothèques tierces
Figure 11.12 — Visuel de l’API Gears
Note importante
Il n’est actuellement possible de déboguer une application GWT utilisant Gears
que sous Windows ; sur les autres plates-formes comme Mac OS X ou Linux, le
mode hosted ne fonctionnera pas et une erreur sera générée lors du lancement de
l’application. Néanmoins, malgré cette erreur, il est toujours possible d’utiliser le bouton
Compile/Browse pour lancer l’application en mode web.
Cette restriction sera levée avec l’apparition de GWT 2.0 ; en effet cette dernière version
intègrera un mode d’exécution dit « Out of Process Hosted Mode », qui découplera le
navigateur du noyau du hosted mode, et permettra ainsi une plus grande indépendance
envers la plate-forme (lire à ce sujet le chapitre 12 sur GWT 2.0).
Le plugin Gears
Mis à part avec le navigateur Chrome (provenant de Google) pour lequel la fonctionnalité Gears est intégrée d’origine, l’utilisation de Gears sur les autres navigateurs
nécessite l’installation d’un plugin. Gears gère normalement le téléchargement du
plugin lors de la première utilisation. Cependant, il existe toujours la possibilité que le
plugin ne puisse pas être installé, ou que l’application soit accédée depuis un navigateur
non compatible.
Il existe heureusement une technique pour remplacer la page « normale » faisant
appel à Gears par une page alternative, qui pourra selon le cas soit afficher un message
d’erreur, soit permettre un fonctionnement en mode dégradé sans Gears.
Pour cela deux étapes sont nécessaires :
1. créer le point d’entrée alternatif ;
2. le déclarer dans le fichier module.
157
11.2 Bibliothèques utilitaires
Le point d’entrée alternatif peut être n’importe quel point d’entrée GWT, par
exemple :
package oge.gwt.chap1124.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootPanel;
/**
* Ce point d’entrée sera invoqué si Gears n’est pas
* disponible sur la plateforme.
*/
public class GwtGearsDemoNoGears implements EntryPoint {
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get("gears");
rootPanel.add(new HTML(
"<p><font color=\"red\">Google Gears n’est
pas disponible sur ce navigateur.<br> " +
"<p>Veuillez l’installer à partir de
<a href=’http://tools.google.com/gears’>" +
"cette page</a> et recharger l’application.</font>"));
}
}
Le code « magique » qui effectue l’aiguillage vers ce point d’entrée dans le fichier
module est le suivant :
<!-- Remplacer le point d’entrée standard par un point d’entrée alternatif
si Gears n’est pas installé -->
<replace-with class="oge.gwt.chap1124.client.GwtGearsDemoNoGears">
<when-type-is class="oge.gwt.chap1124.client.GwtGearsDemo"/>
<when-property-is name="gears.installed" value="false"/>
</replace-with>
Lorsqu’on essaye d’accéder à cette page alors que Gears n’est pas installé, on peut
voir le résultat présenté en figure 11.13.
Figure 11.13 — Gears, page non disponible
L’utilisateur pourra alors installer le plugin correspondant à sa configuration avant
de continuer.
12
GWT 2.0
Objectif
Ce chapitre vous permettra de vous familiariser avec les principales nouveautés
introduites par GWT 2.0 que sont :
- la possibilité de déboguer dans tout navigateur avec OOPHM (Out Of Process
Hosted Mode) ;
- la possibilité de diviser du code généré en différents blocs avec le code splitting ;
- la construction d’interfaces via un langage descriptif avec UiBinder ;
- la gestion des ressources avec ClientBundle.
Depuis ses débuts, GWT est en évolution continue, et quasiment chaque version
a apporté des améliorations significatives (voir l’historique des versions au § 1.3.3).
La dernière évolution porte le nom de 2.0 ; l’incrémentation du numéro de version
majeure traduit l’intégration de fonctionnalités de premier plan.
Sans parler de révolution, car les principes de base sont toujours les mêmes, cette
version s’attache notamment à progresser davantage sur les aspects d’efficacité et
d’optimisation des applications GWT. Heureusement, la compatibilité ascendante
est aussi un souci des concepteurs de GWT, et la quasi-totalité de ce qui a été écrit
précédemment dans cet ouvrage s’applique aussi bien aux versions 1.6/1.7 qu’à la
version 2.0.
160
Chapitre 12. GWT 2.0
Il n’est pas question ici de lister exhaustivement les différences avec la version
précédente, mais on va cependant détailler les nouveautés les plus significatives :
• Out Of Process Hosted Mode, la nouvelle architecture du hosted mode, qui permet
enfin de déboguer dans n’importe quel navigateur ;
• la notion de code splitting, qui optimise le chargement des applications volumi-
neuses en donnant des indications au compilateur pour découper le code source
en plusieurs parties ;
• l’introduction d’un langage déclaratif de description des interfaces et son
processeur, UiBinder ;
• l’amélioration du traitement des ressources avec ClientBundle.
12.1 OBTENIR LA DERNIÈRE VERSION DE GWT
Pour profiter des nouvelles fonctionnalités de GWT 2.0, il vous faudra obtenir une
version de développement récente de GWT. Vous avez le choix entre plusieurs
options :
utiliser un milestone que Google met à disposition sur
http://code.google.com/p/google-web-toolkit/downloads
(sélectionnez
Deprecated downloads pour voir les versions intermédiaires, milestones et release
candidates) ;
• soit utiliser un des snapshots que la société SFEIR met périodiquement à
disposition de tous sur http://code.google.com/p/sfeir/downloads/list ;
• soit encore, pour avoir la version la plus récente, récupérer les sources de GWT
depuis le repository Subversion public du projet et le compiler soi-même. Si vous
n’avez pas peur de « mettre les mains dans le cambouis », la dernière option
n’est pas excessivement complexe ; nous allons la détailler ci-après.
• soit
12.1.1 Prérequis
Les prérequis sont :
• un
client
SVN
(Subversion),
téléchargeable
depuis
http://subversion.tigris.org/getting.html ;
• une version d’ant supérieure à 1.7.1, qu’on peut télécharger depuis
http://ant.apache.org/bindownload.cgi.
On suppose que les commandes svn et ant sont installées et que votre PATH est
configuré correctement pour y accéder.
Note
Mac OS X inclut un client SVN en standard, mais celui-ci est trop ancien et ne
fonctionnera pas avec GWT. Vous devrez donc tout de même installer un client SVN
récent.
161
12.2 OOPHM
La compilation de GWT nécessite des outils spécifiques (notamment pour générer
le hosted mode). La compilation de ces outils eux-mêmes est possible également, mais
complexe ; heureusement le repository contient une version précompilée de ces outils.
12.1.2 Génération de GWT
1. À l’endroit où vous le souhaitez, créez un dossier dédié pour cette opération.
On supposera que ce ce dossier s’appelle « gwt ».
2. Ouvrez un terminal (invite de commande pour Windows) et allez dans ce
nouveau dossier.
3. Tapez la commande suivante pour récupérer les outils précompilés :
svn checkout http://google-web-toolkit.googlecode.com/svn/tools/
tools
4. Tapez la commande suivante pour récupérer les sources de GWT :
svn checkout http://google-web-toolkit.googlecode.com/svn/trunk/
trunk
5. Allez dans le dossier trunk qui vient d’être créé :
cd trunk
6. Lancez la construction :
– pour genérer toutes les plates-formes (Windows, Mac, Linux) :
ant
– pour genérer la plate-forme courante seulement :
ant dist-dev
Si tout se passe correctement, les JAR générés se trouvent sous build/lib, les
archives complètes sous build/dist avec une version correspondant à 0.0.0 (gwtwindows-0.0.0.zip, gwt-mac-0.0.0.tar.gz, gwt-linux-0.0.0.tar.bz2).
12.2 OOPHM
Le hosted mode est une pièce essentielle de GWT ; en effet il permet au développeur
d’utiliser un débogueur Java standard pour déboguer du code qui, au final, tournera sous
forme de JavaScript dans un navigateur. Par conséquent, le hosted mode doit émuler un
navigateur pour permettre au code Java d’interagir avec ce dernier, de la même façon
que le code JavaScript interagira avec le navigateur dans lequel il tournera.
Dans les versions 1.x de GWT, on arrive à cette fin en embarquant une version
modifiée d’un navigateur dans le hosted mode ; le navigateur embarqué cohabite donc
avec le hosted mode dans le même processus système.
162
Chapitre 12. GWT 2.0
Si cette approche s’est avérée exploitable, elle présente cependant un certain
nombre d’inconvénients :
• l’intégration d’un navigateur avec le hosted mode nécessite beaucoup de
modifications. Conséquence de ceci, seul un navigateur est supporté pour
chaque plate-forme (Windows, Mac, Linux) ;
• pour la même raison, l’intégration d’une nouvelle version d’un navigateur est
problématique ;
• la technique d’intégration utilisée empêche de nombreux plugins de fonctionner
(en particulier Google Gears comme vu au § 11.2.3) ;
• de nombreux autres effets de bord indésirables existent.
C’est pour remédier à tous ces problèmes que Google a introduit la notion de Out
Of Process Hosted Mode (mode hôte hors processus). Dans cette nouvelle architecture,
le hosted mode n’embarque pas un navigateur, mais dialogue avec un navigateur
standard au travers d’un mécanisme de type plugin. Les avantages sont évidents :
• pour peu que la compatibilité des plugins soit assurée, le support d’une nouvelle
version de navigateur ne devrait nécessiter aucun travail supplémentaire ;
• le hosted mode pourra fonctionner avec n’importe quel navigateur (supporté) sur
n’importe quelle plate-forme ;
• les outils de debogage et plugins fonctionneront puisque le navigateur ne sera
pas modifié ;
• les différentes variantes de GWT (gwt-dev-mac.jar, gwt-dev-windows.jar, gwt-
dev-linux.jar) pourront éventuellement fusionner, puisque la majorité des
différences provient des implémentations particulières du hosted mode.
Le noyau du nouveau hosted mode communique avec le plugin au moyen d’un
protocole réseau TCP (figure 12.1), ce qui offre un bénéfice supplémentaire puisqu’il
sera ainsi possible de déboguer une session Internet Explorer tournant sur une machine
Windows depuis un environnement de développement et un hosted mode hébergés sur
une machine Linux !
Les plugins existants aujourd’hui sont les suivants :
• Firefox 2, 3 et 3.5 : les plugins correspondants (respectivement
gwt-dmp-ff2.xpi, gwt-dmp-ff3.xpi et gwt-dmp-ff35.xpi) se trouvent à l’URL
http://google-web-toolkit.googlecode.com/svn/trunk/plugins/xpcom/prebuilt/
Pour l’installer il suffit de cliquer sur le fichier XPI correspondant depuis Firefox,
et confirmer l’installation du plugin.
• Internet Explorer 6, 7 et 8 : l’extension se trouve à l’URL http://google-webtoolkit.googlecode.com/svn/trunk/plugins/ie/prebuilt
Pour l’installation, téléchargez le fichier oophm.dll dans un dossier temporaire
local, puis dans une invite de commande, saisissez regsrv32 oophm.dll et
redémarrez IE.
163
12.2 OOPHM
• Safari 3 et 4 (Mac seulement) : le plugin se trouve à l’URL http://google-web-
toolkit.googlecode.com/svn/trunk/plugins/webkit/prebuilt
Téléchargez le fichier oophm.dmg (image disque), montez-le dans le Finder
(double-clic) et exécutez l’installeur.
• Chrome : il faut utiliser la version « DevChannel » de Chromium (la ver-
sion Open Source de Chrome), car la version stable courante ne supporte
pas encore les extensions. L’extension se trouve à l’URL http://google-webtoolkit.googlecode.com/svn/trunk/plugins/npapi/prebuilt
Pour l’installer, cliquez sur le fichier gwtdmp.crx depuis Chrome.
Figure 12.1 — Communication entre le hosted mode et le navigateur avec OOPHM
Terminologie
GWT 2.0 introduira un léger changement dans la terminologie officielle, destiné à
clarifier les choses. Ainsi, Hosted Mode deviendra Development Mode et Web Mode
deviendra Production Mode. OOPHM quant à lui sera appelé In-Browser Development
Mode.
164
Chapitre 12. GWT 2.0
12.2.1 Utilisation
Côté développement, il vous faudra activer l’OOPHM qui ne l’est pas par défaut. En
règle générale, il suffit d’ajouter le fichier JAR gwt-dev-oophm.jar en tête du classpath
Java. Cependant, si vous utilisez le plugin Google pour Eclipse, il vous suffira de cocher
une case dans la configuration d’exécution.
Dans le détail :
1. Décompressez l’archive générée par la compilation ou téléchargée, comme
n’importe quelle version de GWT (voir § 2.2.2).
2. Si votre archive décompressée ne comporte qu’un fichier gwt-dev.jar au lieu
d’un fichier gwt-dev-mac.jar, gwt-dev-windows.jar ou gwt-dev-linux.jar
(ce qui sera le cas dans la version finale de GWT 2.0), il vous faudra alors
le renommer correctement selon votre plate-forme pour que cette version de
GWT soit reconnue par le plugin Google. Bien sûr, le plugin sera mis à jour lors
de la sortie de GWT 2.0.
3. Ajoutez la nouvelle version de GWT à la configuration d’Eclipe :
– dans Eclipse, ouvrez Preferences > Google > Web Toolkit (figure 12.2),
– cliquez sur le bouton Add et choisissez le répertoire qu’on vient de créer
(figure 12.3),
– cliquez sur OK,
– cochez la version que vous venez d’ajouter (figure 12.4), puis cliquez sur OK
pour fermer les préférences.
Figure 12.2 — Préférences Eclipse : liste des versions de GWT
Figure 12.3 — Préférences Eclipse : Ajout d’une nouvelle version de GWT
165
12.2 OOPHM
Figure 12.4 — Préférences Eclipse : liste des versions de GWT avec la nouvelle version comme
défaut
4. Assurez-vous que la configuration d’exécution de votre projet est bien réglée
pour OOPHM :
–
–
–
–
dans Eclipse, ouvrez le menu Run > Run configuration,
sélectionnez votre projet dans la partie gauche,
dans l’onglet GWT, cochez la case Launch URL using OOPHM,
cliquez sur Apply puis Close pour valider vos modifications.
5. Pour lancer l’application, faites exactement comme d’habitude : clic droit sur
le projet, puis Run As... (ou Debug As...) > Web Application.
6. Surveillez la console. Après quelques secondes, un message similaire au suivant
apparaît :
Using a browser with the GWT Development Plugin, please browse to
the following URL:
http://localhost:64130/Sandbox.html?gwt.hosted=10.0.2.1:9997
7. Lorsque ce message s’affiche, ouvrez un navigateur équipé du plugin OOPHM,
copiez l’URL mentionnée et collez-la dans la barre d’adresse du navigateur.
8. Le plugin établit la communication avec le processus du hosted mode qui tourne
dans Eclipse; vous pouvez désormais mettre des points d’arrêt et vérifier que
tout fonctionne.
Note
Les appels entre le plugin OOPHM et le code Java sont synchrones ; par conséquent,
si vous avez mis un point d’arrêt dans une méthode Java, le navigateur apparaîtra
bloqué tant que la méthode ne retournera pas.
166
Chapitre 12. GWT 2.0
12.3 CODE SPLITTING & SOYC
Avec la complexité grandissante des applications AJAX, le volume de code JavaScript
à télécharger initialement tend à augmenter, et les applications GWT n’échappent
pas à la règle. Par défaut, la compilation GWT génère du JavaScript dit « obfuscated »
c’est-à-dire compacté au maximum, mais ce n’est pas suffisant.
Pour aller plus loin, GWT 2.0 introduit la notion de « code splitting » (séparation
de code). Grâce à ce concept, le programmeur peut indiquer au compilateur GWT
des endroits de son programme où une partie additionnelle de code peut être chargée.
Ces endroits sont appelés « split points » (points de séparation).
Avec cette information, le compilateur peut découper le code JavaScript en
plusieurs parties, qui seront chargées « à la demande » lorsque le programme atteindra
un split point.
12.3.1 Insérer un split point
Pour insérer un split point dans votre code, il suffit d’appeler la méthode statique
GWT.runAsync(). Cette méthode prend en paramètre un objet de type RunAsyncCallback, qui contient le code dont on souhaite différer le chargement.
De façon similaire aux callbacks associés aux appels RPC (AsyncCallback, voir
chapitre 5 : Communiquer avec le serveur), le RunAsyncCallback doit implémenter
deux méthodes :
• void onSuccess() sera appelé lorsque le code additionnel aura été chargé ;
• void onFailure(Throwable reason) sera appelé si le code additionnel n’a pas pu
être chargé pour une raison quelconque (réseau ou serveur devenu indisponible
par exemple).
RunAsyncCallback se présente donc comme un event handler, à ceci près que
l’événement auquel il réagit est le chargement du code nécessaire à la poursuite de
l’exécution. Le code dont le chargement doit être différé est donc le contenu de la
méthode onSuccess(), ainsi que tout ce dont il dépend.
À titre d’exemple, prenons le code d’exemple standard généré pour un nouveau
projet GWT. Ce code contient une méthode sendNameToServer() qui récupère le
contenu d’un champ texte, effectue un appel RPC et affiche la réponse du serveur :
private void sendNameToServer() {
sendButton.setEnabled(false);
String textToServer = nameField.getText();
textToServerLabel.setText(textToServer);
serverResponseLabel.setText("");
greetingService.greetServer(textToServer, new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
Window.alert("Rpc failure");
sendButton.setEnabled(true);
}
12.3 Code splitting et SOYC
167
public void onSuccess(final String result) {
serverResponseLabel.setHTML(result);
dialogBox.center();
closeButton.setFocus(true);
}
});
}
Or cette méthode n’a pas besoin d’être chargée initialement, puisqu’elle n’est
appelée que lors du clic sur un bouton ou un appui sur Enter. Si on souhaite différer
son chargement jusqu’à son appel effectif, on pourra transformer le code comme suit :
private void sendNameToServer() {
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable reason) {
GWT.log("runAsync failed", reason);
}
public void onSuccess() {
doSendNameToServer();
}
});
}
private void doSendNameToServer() {
sendButton.setEnabled(false);
String textToServer = nameField.getText();
textToServerLabel.setText(textToServer);
serverResponseLabel.setText("");
greetingService.greetServer(textToServer, new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
Window.alert("Rpc failure");
sendButton.setEnabled(true);
}
public void onSuccess(final String result) {
serverResponseLabel.setHTML(result);
dialogBox.center();
closeButton.setFocus(true);
}
});
}
Ce qu’on a fait ici tout simplement, c’est qu’on a déplacé le corps de la méthode
sendNameToServer() dans une nouvelle méthode doSendNameToServer(), et la
méthode sendNameToServer() contient désormais un appel à GWT.runAsync() qui
invoquera doSendNameToServer() lorsque le code correspondant aura été chargé.
Cette simple déclaration a pour effet d’indiquer au compilateur GWT qu’il doit, si
possible, placer le code JavaScript correspondant à la méthode doSendNameToServer()
dans un fichier séparé ; ce dernier ne sera chargé que lorsqu’on en aura besoin, c’est-àdire dans notre exemple lors de l’appel à la méthode sendNameToServer().
Si on compile et qu’on exécute l’application modifiée, on peut constater deux
choses :
• d’une part, le comportement de l’application d’un point de vue externe est
inchangé (c’est le but) ;
• d’autre part, le volume du chargement initial a été réduit d’environ un tiers.
168
Chapitre 12. GWT 2.0
Sur une application comme celle-ci, le gain est marginal, mais sur une application
moins triviale, la différence de temps de chargement initial peut être très sensible !
Ainsi, en insérant des split points à des endroits judicieux, il est assez simple de réduire
de moitié la taille du téléchargement initial pour une application GWT typique.
Attention toutefois, car le résultat n’est pas toujours comme on l’attend. Il est
possible que le compilateur décide d’inclure dans le téléchargement initial du code
qu’on pensait différé, la plupart du temps à cause d’une dépendance indirecte passée
inaperçue. Pour comprendre comment le compilateur GWT procède au découpage,
et corriger le code pour que la séparation se fasse comme on le souhaite, GWT 2.0
introduit la notion de « Story of your compile » (l’histoire de votre compilation).
12.3.2 Story Of Your Compile (SOYC)
SOYC peut être vu comme une sorte de compte-rendu détaillé de la compilation. Ce
compte-rendu intègre, entre autres, les différents morceaux qui résultent du découpage
en fonction des split points rencontrés dans le code.
La génération d’un rapport SOYC se fait en deux phases :
1. des options spécifiques sont passées au compilateur GWT pour lui indiquer de
générer les informations de découpage « brutes » ;
2. le rapport lui même est généré à partir de ces informations par l’application
SoycDashboard, inclue dans la distribution GWT.
Le processus est à ce jour très peu documenté et assez délicat à faire fonctionner;
voyons en détail comment le mettre en œuvre avec Eclipse et le plugin Google.
Compiler pour SOYC
1. Dans la fenêtre de compilation GWT (obtenue lorsqu’on clique sur l’icône
Icône compilation GWT
dans la barre d’outils Eclipse), cliquez sur Advanced
pour afficher les options avancées, et ajoutez les options -soyc -extra extra
aux options de compilation, comme sur la figure 12.5.
2. Cliquez sur Compile pour lancer la compilation. À l’issue de la compilation, si
tout s’est bien passé, le compilateur aura créé un dossier extra contenant les
informations de compilation brutes.
Explication des options
-extra demande au compilateur de générer des informations sur la compilation dans
un sous-dossier nommé extra.
-soyc lui demande en plus d’y ajouter les informations nécessaires à la génération
ultérieure du rapport SOYC.
Note : ces options sont retenues d’une invocation à l’autre de la compilation ; si vous
ne désirez plus générer de rapport SOYC, il faudra effacer les options avancées de la
fenêtre de compilation.
12.3 Code splitting et SOYC
169
Figure 12.5 — Plugin GWT : options de compilation pour SOYC
Générer le rapport SOYC
Pour générer le rapport, on va utiliser une run configuration d’Eclipse, mais toutes les
options qui permettent de lancer une classe Java sont également valides (target ant,
etc.) :
1. Le programme doit avoir accès au fichier gwt-soyc-vis.jar contenu dans
la distribution GWT. Le plus simple pour ne pas s’embarasser de chemins
absolus est de créer un sous-dossier lib dans le projet et d’y copier le fichier
gwt-soyc-vis.jar depuis le dossier gwt-xxx-0.0.0.
2. Ouvrez l’écran de configuration des run configurations (menu Run > Run
configurations..., figure 12.6).
3. Créez une nouvelle configuration de type Java application :
– faites un clic droit sur Java application puis New,
– nommez la nouvelle configuration « SoycDashboard »,
– assurez-vous qu’elle correspond bien au projet souhaité, au besoin choisissezle en cliquant sur Browse...,
– dans
le
champ
Main
class
(figure 12.7),
saisissez
« com.google.gwt.soyc.SoycDashboard » (ou bien cliquez sur Search... et
sélectionnez la classe en tapant une partie de son nom).
170
Chapitre 12. GWT 2.0
Figure 12.6 — Liste des configurations de lancement Eclipse
Figure 12.7 — Onglet principal de la configuration de lancement Eclipse pour la génération du
rapport SOYC
12.3 Code splitting et SOYC
171
4. Cliquez sur l’onglet Arguments (figure 12.8) :
le
champ
Program
arguments,
saisissez
-resources
lib/gwt-soyc-vis.jar -soycDir extra/sandbox/soycReport/
-symbolMapsDir extra/sandbox/symbolMaps/ -out soyc,
– dans
– dans le champ VM arguments, saisissez -Xmx1024M,
– dans Working Directory, assurez-vous que Default est coché.
Figure 12.8 — Onglet « Arguments » de la configuration de lancement Eclipse pour la
génération du rapport SOYC
5. Cliquez sur Apply pour sauvegarder la configuration, puis Run pour démarrer.
La console montre l’évolution de la génération :
Generating the Story of Your Compile...
Finished creating reports for permutation.
Finished creating reports for permutation.
Finished creating reports for permutation.
Finished creating reports for permutation.
Finished creating reports for permutation.
Finished creating reports for permutation.
Finished creating reports. To see the dashboard, open index.html in
your browser.
6. Une fois la génération terminée, comme indiqué ouvrez le fichier
soyc/index.html avec un navigateur.
172
Chapitre 12. GWT 2.0
Explication des options
-resources désigne le fichier gwt-soyc-vis.jar, copié précédemment dans le sousdossier lib.
-soycDir et -symbolMapsDir désignent les dossiers contenant les données générées
précédemment par le compilateur. Il convient de remplacer « sandbox » dans l’exemple
en figure 12.7 par le nom du module GWT de votre projet.
-out indique au programme de générer les fichiers résultats dans un sous-dossier
nommé soyc.
Note
Pour relancer la génération du rapport ultérieurement, il suffira d’invoquer de nouveau
la run configuration qu’on vient de créer (après avoir effectué une compilation bien sûr).
Si ça ne fonctionne pas...
Les paramètres à saisir sont fortement sujets à erreur, et le message produit n’est pas
toujours en rapport avec le problème... voici quelques erreurs qui peuvent survenir :
• Error creating html file. error in opening zip file
Cette erreur se produit si le fichier gwt-soyc-vis.jar désigné par l’option resources n’est pas accessible.
• Cannot open file extra/sandbox/soycRepor/stories0.xml (No such file
or directory)
Le dossier désigné par l’option -soycDir est probablement erroné. Vérifiez qu’il
a bien été généré lors de la compilation; ce dossier doit contenir un ensemble
de fichiers du type splitPoints0.xml.gz, stories0.xml.gz, dependencies0.xml.
N’oubliez pas que le chemin spécifié est relatif à la racine du projet Eclipse.
• Exception in thread "main" java.lang.
NullPointerException
at com.google.gwt.soyc.Settings.readPermutationInfo
(Settings.java:221)
at com.google.gwt.soyc.SoycDashboard.main
(SoycDashboard.java:73)
Cette erreur peu glorieuse se produit lorsque le dossier désigné par l’option
-symbolMapsDir est erroné...
Cette liste n’est bien sûr pas limitative.
12.3.3 Optimiser avec le code splitting
Le rapport fourni par SOYC vous sera utile pour connaître la taille des différents blocs
de téléchargement, en particulier celle du bloc initial.
12.3 Code splitting et SOYC
173
Parfois, il arrive qu’on pense avoir sorti une partie du bloc initial, mais qu’elle
y figure toujours. Pour cela, le rapport SOYC vous permettra de suivre la chaîne de
dépendances pour comprendre d’où vient la dépendance restante.
Un pattern utile en collaboration avec le code splitting est Async provider ; il permet
d’isoler un groupe de fonctionnalités dans un bloc tout en fournissant un point d’entrée
unique qui évite les références directes pouvant causer une inclusion non souhaitée
du code dans un autre bloc.
Imaginons une classe Provider qui fournit un certain nombre de méthodes, et
qu’on souhaite placer dans un bloc de code séparé qui est chargé au besoin. Pour
cela, on oblige les utilisateurs de cette classe à passer par une méthode publique qui
appellera un callback lorsque l’instance sera disponible.
L’interface que le client doit implémenter :
public interface AsyncClient<T> {
void onUnavailable();
void onAvailable(T instance);
}
Extrait du code de la classe Provider :
public class Provider {
private static Provider instance = null;
private Provider() {
//...
}
public static void getAsync(final AsyncClient<Provider> client) {
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable reason) {
client.onUnavailable();
}
public void onSuccess() {
if (instance == null) {
instance = new Provider();
}
client.onAvailable(instance);
}
});
}
}
Utilisation de la classe :
Provider.getAsync(new AsyncClient<Provider>() {
public void onAvailable(Provider instance) {
// l’instance de Provider est disponible
}
public void onUnavailable() {
// un problème est survenu...
}
});
D’autres patterns vont certainement apparaître en conjonction avec le code splitting.
174
Chapitre 12. GWT 2.0
12.4 UIBINDER
Un des aspects manquants dans GWT était la possibilité de construire des interfaces
statiquement au travers d’un langage déclaratif, comme le permettent de nombreux
toolkits graphiques. L’intérêt d’un langage de construction déclaratif est qu’il permet de
séparer clairement ce qui est purement statique (les composants UI qui ne changent
pas) du comportement dynamique (le comportement de l’interface en réaction aux
actions de l’utilisateur).
GWT 2.0 comble ce manque en introduisant UiBinder. Concrètement, il existe
une interface générique UiBinder qui se présente comme suit :
public interface UiBinder<U, O> {
/**
*Creates and returns the root object of the UI, and fills
*any fields of owner tagged with {@link UiField}.
*
*@param owner the object whose {@literal @}UiField needs will be filled
*/
U createAndBindUi(O owner);
}
Cette interface est paramétrée pas les types suivants :
• U est le type de l’élément construit par le binder ;
• O est le type d’un objet qui peut contenir des champs liés, c’est-à-dire des
références à des éléments construits par UiBinder.
La seule méthode de cette interface, createAndBindUi(), a un double rôle :
• construire l’instance de U à partir du fichier de description XML ;
• renseigner certaines variables de l’objet O passé en paramètre avec la référence
de l’élément de l’interface correspondant. Les variables à renseigner sont taggées
par l’annotation @UiField. Ceci permet à l’objet O d’accéder à ce dont il a besoin
pour mettre en place le comportement souhaité.
12.4.1 Utilisation
Pour construire une interface avec UiBinder, les étapes sont les suivantes :
1. Importez le module UiBinder dans le projet ; pour cela il faut ajouter la ligne
suivante dans le fichier module :
<inherits name="com.google.gwt.uibinder.UiBinder"/>
175
12.4 UiBinder
2. Définissez l’interface désirée au format XML dans un fichier .ui.xml. Il n’existe
pas à cette heure de spécification complète du format, mais celui-ci est assez
simple pour être compris à partir d’un exemple :
<gwt:UiBinder xmlns:ui=’urn:ui:com.google.gwt.uibinder’
xmlns:gwt=’urn:import:com.google.gwt.user.client.ui’>
<div>
<gwt:HorizontalPanel>
<gwt:Label text="Votre nom :" />
<gwt:TextBox ui:field="nomBox" />
<gwt:Button ui:field="submitButton" text="OK" />
</gwt:HorizontalPanel>
</div>
</gwt:UiBinder>
3. Choisissez un type Java (O) qui recevra les références, et marquez avec l’annotation UiField les variables à renseigner :
@UiField
TextBox nomBox;
4. Si nécessaire, dans le type O, désignez des méthodes en tant que handlers sur
certains widgets avec l’annotation UiHandler :
@UiHandler("submitButton")
void buttonClicked(ClickEvent event) {
// faire quelque chose...
}
5. Dans le code Java, déclarez une interface qui étend UiBinder en spécifiant les
types désirés pour U et O, par exemple :
@UiTemplate("MyClient.ui.xml")
interface MyUiBinder extends UiBinder<Panel, MyClient> {}
6. Obtenez une instance de ce type grâce à l’omniprésent GWT.create() :
private static final MyUiBinder binder = GWT.create(MyUiBinder.class);
7. Appelez createAndBindUi() en lui passant une instance de O :
MyClient client = new MyClient();
Panel panel = binder.createAndBindUi(client);
L’appel retourne un objet U (ici une instance de Panel) et remplit les champs
taggés dans l’objet O passé en paramètre (ici client).
176
Chapitre 12. GWT 2.0
Notes
Étape 2 :
La présence d’un élément XML du namespace « gwt » se traduit par l’instanciation
d’un widget du type spécifié. Ce widget sera ajouté comme fils au widget instancié
par l’élément XML parent :
– les attributs (sans namespace) de ces éléments spécifient des valeurs pour les
propriétés du widget ;
– l’attribut spécial ui:field désigne une variable du type O qui recevra la référence
vers le widget lors du binding.
Notez pas ailleurs qu’on peut inclure des éléments HTML littéraux (texte, <DIV>, etc.).
Étape 4 :
Le nom de la méthode n’est pas important, c’est le type du paramètre qui va déterminer
de quel type de handler il s’agit. Ici, le paramètre est de type ClickEvent : la méthode
sera enregistrée avec addClickHandler().
Étape 5 :
L’annotation UiTemplate permet de désigner le fichier de déclaration à utiliser. Cette
annotation est optionnelle : par défaut, GWT cherchera un fichier situé dans le même
package que le type O (ici MyClient), portant le même nom que celui-ci avec un suffixe
de .ui.xml, donc dans notre cas MyClient.ui.xml.
12.4.2 Un exemple
Nous allons illustrer l’utilisation d’UiBinder par un exemple simple et concret.
Souvenez-vous du widget SexeRadio qu’on avait utilisé comme exemple pour illustrer la fabrication d’un widget « maison » au § 10.2 Utiliser la classe Composite. Nous
allons recréer ce widget, mais cette fois-ci, au lieu d’instancier programmatiquement
les sous-widgets, nous allons déléguer cette opération à UiBinder.
Tout d’abord, traduisons la hiérarchie de widgets dans le format spécifique XML
UiBinder. On obtient ceci :
<gwt:UiBinder xmlns:ui=’urn:ui:com.google.gwt.uibinder’
xmlns:gwt=’urn:import:com.google.gwt.user.client.ui’>
<gwt:HorizontalPanel>
<gwt:Label text="Sexe :" />
<gwt:VerticalPanel>
<gwt:RadioButton name=’sexeradio’ text=’M’
ui:field=’maleRadio’ />
<gwt:RadioButton name=’sexeradio’ text=’F’
ui:field=’femaleRadio’ />
</gwt:VerticalPanel>
</gwt:HorizontalPanel>
</gwt:UiBinder>
La base du nouveau widget reste un Composite. Ce widget jouera le rôle du type O
pour UiBinder car on souhaite en récupérer les références vers les deux RadioButtons
(d’où la présence des attributs ui:field dans le fichier XML) :
12.4 UiBinder
177
public class SexeRadio2 extends Composite {
@UiField
RadioButton maleRadio;
@UiField
RadioButton femaleRadio;
// ...
}
Les noms des variables doivent correspondre aux valeurs des attributs ui :field du
fichier XML. Ensuite, dans cette même classe, nous allons déclarer l’interface UiBinder
et obtenir une instance grâce à GWT.create() :
@UiTemplate("SexeRadio2.ui.xml")
interface MyUiBinder extends UiBinder<Panel, SexeRadio2> {}
private static final MyUiBinder binder = GWT.create(MyUiBinder.class);
Finalement, nous allons instancier l’interface dans le constructeur de notre
nouveau widget. Comme il s’agit d’un composite, le panel sera passé à initWidget()
pour en faire le widget de base du composite :
public SexeRadio2() {
final Panel panel = binder.createAndBindUi(this);
initWidget(panel);
}
On note également que c’est this qui est passé en paramètre au binder, puisque
c’est bien dans l’objet courant qu’on souhaite récupérer les références vers les
RadioButtons. On peut alors inclure les méthodes qui permettent de savoir quel
bouton est sélectionné :
public boolean isM() {
return maleRadio.getValue();
}
public boolean isF() {
return femaleRadio.getValue();
}
Pour le plaisir (et pour vérifier que cela fonctionne bien sûr), nous allons associer
des handlers aux RadioBoutons qui affichent simplement une trace dans la fenêtre du
hosted mode :
@UiHandler("maleRadio")
void maleClicked(ClickEvent event) {
GWT.log("C’est un garçon !", null);
}
@UiHandler("femaleRadio")
void femaleClicked(ClickEvent event) {
GWT.log("C’est une fille !", null);
}
Le widget ainsi créé s’utilise exactement comme le précédent ; la seule différence,
mais de taille, c’est que la construction de l’interface s’est faite de façon totalement
déclarative dans le fichier XML, alors que la classe Java se cantonne à gérer la
partie dynamique (comportement). Le récapitulatif de la classe SexeRadio2 est
accessible en annexe 7.
178
Chapitre 12. GWT 2.0
12.5 CLIENTBUNDLE
ClientBundle permet de grouper des ressources quelconques (y compris images, feuilles
CSS et ressources binaires). L’avantage évident est de limiter le nombre de téléchargements, ce qui réduit l’overhead causé par un grand nombre de téléchargements.
Les possibilités ouvertes par ClientBundle sont nombreuses ; illustrons-les avec un
exemple.
Tout d’abord, il faut importer le module Resources dans le fichier module :
<inherits name="com.google.gwt.resources.Resources" />
Ensuite, on définit une interface qui hérite de ClientBundle ; chaque méthode
permettra d’accéder à une ressource dont la source est spécifiée par une annotation,
par exemple :
public interface MyResources extends ClientBundle {
public static final MyResources INSTANCE =
GWT.create(MyResources.class);
@Source("style.css")
CssResource style();
@Source("data.xml")
TextResource xmlData();
@Source("document.pdf")
DataResource document();
@Source("logo.png")
ImageResource logo();
}
Les ressources doivent être disponibles au moment de la compilation ; le compilateur se charge d’effectuer le groupage de façon totalement automatique et transparente.
Côté client, on obtient une référence vers un objet qui implémente l’interface
qu’on a défini en appelant GWT.create(MyResources.class) ; c’est ensuite à l’application d’utiliser une des méthodes ci-dessous pour accéder au contenu des différentes
ressources, en fonction de son type :
• TextResource fournit une méthode getText() qui retourne le contenu sous
forme de String ;
• DataResource fournit une méthode getUrl() qui retourne une URL absolue
donnant accès à la ressource ;
• CssResource permet de manipuler une ressource CSS ;
• ImageResource permet d’obtenir des informations sur l’image, et son URL.
Annexes
Dans cette partie, retrouvez :
• La liste des classes de la bibliothèque d’émulation JRE (chapitre 3)
• Un exemple de FlexTable avec cellule baladeuse (chapitre 4)
• Un exemple d’appel à un service RPC (chapitre 5)
• Un exemple de mise en œuvre du mécanisme d’historique (chapitre 8)
• L’appel à un service web JSON (chapitre 9)
• Un exemple d’utilisation de l’API Google Gears (chapitre 11)
• Un exemple d’utilisation d’UiBinder pour créer un widget composite
(chapitre 12)
181
Annexes
Liste des classes de la bibliothèque d’émulation JRE (chapitre 3)
Package
java.lang
Classes émulées
ArithmeticException
ArrayIndexOutOfBoundsException
ArrayStoreException
AssertionError
Boolean
Byte
CharSequence
Character
Class
ClassCastException
Cloneable
Comparable
Deprecated
Double
Enum
Error
Exception
Float
IllegalArgumentException
IllegalStateException
IndexOutOfBoundsException
Integer
Iterable
Long
Math
NegativeArraySizeException
NullPointerException
Number
NumberFormatException
Object
Override
Runnable
RuntimeException
Short
StackTraceElement
String
182
Aller plus loin avec GWT
Package
Classes émulées
StringBuffer
StringBuilder
StringIndexOutOfBoundsException
SuppressWarnings
System
Throwable
UnsupportedOperationException
Void
java.lang.annotation
Annotation
AnnotationFormatError
AnnotationTypeMismatchException
Documented
ElementType
IncompleteAnnotationException
Inherited
Retention
RetentionPolicy
Target
java.util
AbstractCollection
AbstractList
AbstractMap
AbstractQueue
AbstractSequentialList
AbstractSet
ArrayList
Arrays
Collection
Collections
Comparator
ConcurrentModificationException
Date
EmptyStackException
EnumMap
EnumSet
183
Annexes
Package
Classes émulées
Enumeration
EventListener
EventObject
HashMap
HashSet
IdentityHashMap
Iterator
LinkedHashMap
LinkedHashSet
LinkedList
List
ListIterator
Map
Map.Entry
MissingResourceException
NoSuchElementException
PriorityQueue
Queue
RandomAccess
Set
SortedMap
SortedSet
Stack
TooManyListenersException
TreeMap
TreeSet
Vector
java.io
FilterOutputStream
OutputStream
PrintStream
Serializable
java.sql
Date
Time
Timestamp
184
Aller plus loin avec GWT
Exemple de FlexTable avec cellule baladeuse (chapitre 4)
Code source de Gwt42Tables.java
package oge.gwt.chap42.client;
import
import
import
import
import
import
import
import
import
com.google.gwt.core.client.EntryPoint;
com.google.gwt.event.dom.client.ClickEvent;
com.google.gwt.event.dom.client.ClickHandler;
com.google.gwt.user.client.ui.Button;
com.google.gwt.user.client.ui.FlexTable;
com.google.gwt.user.client.ui.Grid;
com.google.gwt.user.client.ui.HorizontalPanel;
com.google.gwt.user.client.ui.RootPanel;
com.google.gwt.user.client.ui.VerticalPanel;
public class Gwt42Tables implements EntryPoint {
private
private
private
private
FlexTable table;
VerticalPanel buttonPanel;
int row = 0;
int col = 0;
/**
* Le point d’entrée
*/
public void onModuleLoad() {
// sous-classe de Button spécialisée qui fait appel à la méthode
// move avec les paramètres passés à son constructeur
class DirButton extends Button{
public DirButton(String label, final int deltax,
final int deltay) {
super(label);
addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
move(deltax, deltay);
}
});
}
}
// créer la FlexTable
table = new FlexTable();
table.setBorderWidth(1);
table.setCellPadding(25);
// créer le groupe de boutons qui constituera
// la "cellule baladeuse"
buttonPanel = new VerticalPanel();
buttonPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
buttonPanel.add(new DirButton("^", -1, 0));
HorizontalPanel hPanel = new HorizontalPanel();
hPanel.add(new DirButton("<", 0, -1));
hPanel.add(new DirButton(">", 0, +1));
buttonPanel.add(hPanel);
185
Annexes
buttonPanel.add(new DirButton("v", +1, 0));
//positionner initialement le groupe de boutons
table.setWidget(row, col, buttonPanel);
// ajouter la table
RootPanel.get("contenu").add(table);
}
/**
* Déplace le groupe de boutons
* @param deltax décalage horizontal
* @param deltay décalage vertical
*/
protected void move(int deltax, int deltay) {
// retirer le groupe de boutons de la table
table.remove(buttonPanel);
// calculer les nouvelles positions
row += deltax;
if (row < 0) {
row = 0;
}
col += deltay;
if (col < 0) {
col = 0;
}
// replacer le groupe de boutons
table.setWidget(row, col, buttonPanel);
}
}
186
Aller plus loin avec GWT
Exemple d’appel à un service RPC (chapitre 5)
Code source de GwtChap52.java
package oge.gwt.chap52.client;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
com.google.gwt.core.client.EntryPoint;
com.google.gwt.core.client.GWT;
com.google.gwt.event.dom.client.ClickEvent;
com.google.gwt.event.dom.client.ClickHandler;
com.google.gwt.event.dom.client.KeyCodes;
com.google.gwt.event.dom.client.KeyUpEvent;
com.google.gwt.event.dom.client.KeyUpHandler;
com.google.gwt.user.client.rpc.AsyncCallback;
com.google.gwt.user.client.rpc.ServiceDefTarget;
com.google.gwt.user.client.ui.Button;
com.google.gwt.user.client.ui.DialogBox;
com.google.gwt.user.client.ui.HTML;
com.google.gwt.user.client.ui.Label;
com.google.gwt.user.client.ui.RootPanel;
com.google.gwt.user.client.ui.TextBox;
com.google.gwt.user.client.ui.VerticalPanel;
/**
* Example: appel du service RPC d’addition
*/
public class GwtChap52 implements EntryPoint {
// Le point d’entrée
public void onModuleLoad() {
// créer les widgets
final Button sendButton = new Button("=");
final TextBox op1Field = new TextBox();
final TextBox op2Field = new TextBox();
final TextBox resultField = new TextBox();
op1Field.setText("1");
op2Field.setText("2");
// récupérer le conteneur et y ajouter les widgets
RootPanel container = RootPanel.get("container");
container.add(op1Field);
container.add(new HTML(" + "));
container.add(op2Field);
container.add(sendButton);
container.add(resultField);
// donner le focus au premier champ texte
op1Field.setFocus(true);
op1Field.selectAll();
// Handler pour événements clic-souris et appui sur une touche
class MyHandler implements ClickHandler, KeyUpHandler {
187
Annexes
// appelé lorsque l’utilisateur clique sur le bouton "="
public void onClick(ClickEvent event) {
// effectuer l’appel au serveur
sendToServer();
}
// appelé lorsque l’utilisateur appuie sur Enter
// dans un champ texte
public void onKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
// effectuer l’appel au serveur
sendToServer();
}
}
/*
* Méthode qui récupère les paramètres, effectue
*/
private void sendToServer() {
sendButton.setEnabled(false);
long x = Long.parseLong(op1Field.getText());
long y = Long.parseLong(op2Field.getText());
/**
* Create a remote service proxy to talk
* to the server-side Greeting service.
*/
final AdditionServiceAsync addService =
GWT.create(AdditionService.class);
addService.additionner(x, y, new AsyncCallback<Long>() {
public void onFailure(Throwable caught) {
// échec
resultField.setText(caught.toString());
}
public void onSuccess(Long result) {
// succès
resultField.setText("" + result);
}
});
}
}
// Add a handler to send the name to the server
MyHandler handler = new MyHandler();
sendButton.addClickHandler(handler);
op1Field.addKeyUpHandler(handler);
op2Field.addKeyUpHandler(handler);
}
}
188
Aller plus loin avec GWT
Exemple de mise en œuvre du mécanisme d’historique (chapitre 8)
Code source de GwtHistory.java
package oge.gwt.history.client;
import
import
import
import
import
import
import
import
import
com.google.gwt.core.client.EntryPoint;
com.google.gwt.event.logical.shared.SelectionEvent;
com.google.gwt.event.logical.shared.SelectionHandler;
com.google.gwt.event.logical.shared.ValueChangeEvent;
com.google.gwt.event.logical.shared.ValueChangeHandler;
com.google.gwt.user.client.History;
com.google.gwt.user.client.ui.RootPanel;
com.google.gwt.user.client.ui.Tree;
com.google.gwt.user.client.ui.TreeItem;
public class GwtHistory implements EntryPoint {
/**
* Le point d’entrée
*/
public void onModuleLoad() {
//
// construction de l’arbre
//
TreeItem racine = new TreeItem("Noeud Racine");
{
TreeItem n1 = new TreeItem("Noeud 1");
{
TreeItem n11 = new TreeItem("Noeud 1.1");
n11.addItem(new TreeItem("Noeud 1.1.1"));
n11.addItem(new TreeItem("Noeud 1.1.2"));
n1.addItem(n11);
}
{
TreeItem n12 = new TreeItem("Noeud 1.2");
n12.addItem(new TreeItem("Noeud 1.2.1"));
n12.addItem(new TreeItem("Noeud 1.2.2"));
n1.addItem(n12);
}
racine.addItem(n1);
}
{
TreeItem n2 = new TreeItem("Noeud 2");
n2.addItem(new TreeItem("Noeud 2.1"));
n2.addItem(new TreeItem("Noeud 2.2"));
racine.addItem(n2);
}
final Tree tree = new Tree();
tree.addItem(racine);
//
// ajout SelectionHandler
//
tree.addSelectionHandler(new SelectionHandler<TreeItem>() {
189
Annexes
public void onSelection(SelectionEvent<TreeItem> event) {
// construire le token sur base du hashcode du texte
// du noeud sélectionné
TreeItem selectedItem = event.getSelectedItem();
String token =
Long.toHexString(selectedItem.getText().hashCode());
// créer une entrée d’historique
History.newItem(token);
}
});
//
// ajout du handler historique
//
History.addValueChangeHandler(new ValueChangeHandler<String>() {
public void onValueChange(ValueChangeEvent<String> event) {
// récupérer le token
String token = event.getValue();
// tenter de trouver le noeud corespondant
TreeItem item = findNode(tree, token);
if (item != null) {
// noeud trouvé: le sélectionner
tree.setSelectedItem(item);
// s’assurer que sa branche est déployée
ensureExpanded(item);
}
}
});
RootPanel.get().add(tree);
}
/**
* S’assurer que le noeud spécifié est visible (c’est-à-dire
* que son parent et tous ses ascendants sont déployés)
*/
protected void ensureExpanded(TreeItem item) {
TreeItem parentItem = item.getParentItem();
if (parentItem != null) {
parentItem.setState(true);
ensureExpanded(parentItem);
}
}
/**
* Retourne le noeud de l’arbre correspondant à l’identifiant spécifié,
* ou null si pas trouvé
*/
private TreeItem findNode(Tree tree, String token) {
for (int i = 0; i < tree.getItemCount(); i++) {
TreeItem child = tree.getItem(i);
TreeItem target = findNode(child, token);
if (target != null) {
return target;
}
}
return null;
190
Aller plus loin avec GWT
}
/**
* Retourne le noeud correspondant à l’identifiant spécifié parmi le
* noeud passé en paramètre et ses descendants, null si pas trouvé
*/
private TreeItem findNode(TreeItem item, String token) {
if (match(item, token)) {
return item;
}
for (int i = 0; i < item.getChildCount(); i++) {
TreeItem child = item.getChild(i);
TreeItem target = findNode(child, token);
if (target != null) {
return target;
}
}
return null;
}
/**
* Retourne true si et seulement si le noeud spécifié correspond à
* l’identifiant. Cette implémentation utilise le hashcode
* du texte du noeud
*/
private boolean match(TreeItem item, String token) {
return Long.toHexString(item.getText().hashCode()).equals(token);
}
}
191
Annexes
Appel à un service web JSON (chapitre 9)
Code source de JSONRequestHelper.java (classe utilitaire)
package oge.gwt.chap95.client.util;
import com.google.gwt.core.client.JavaScriptObject;
public class JSONRequestHelper {
public interface JSONResponseHandler {
public void onResponseReceived(JavaScriptObject json);
}
/**
* Effectue un appel à un web service JSON. Le web service doit
* supporter les options "JSON in script" et "JavaScript callback".
*
* @param url L’URL du service à appeler, moins le nom du callback.
* Le nom du callback sera concaténé à la fin de l’URL; celle-ci
* devra donc se terminer par un paramètre du type "callback=".
* @param handler une instance de JSONResponseHandler dont
* la méthode onResponseReceived
* sera appelée après réception du résultat.
*/
public static void get(String url, JSONResponseHandler handler) {
// Construire un nom de callback propre au handler.
// Ceci permet de gérer des situations
// où plusieurs appels simultanés sont faits
// avec des handlers différents.
String callbackName = "callback" + handler.hashCode();
get(url + callbackName, callbackName, handler);
}
/**
* Méthode native JSNI qui effectue la manipulation du DOM.
* @param url L’URL complète (y compris le callback)
* @param callbackName Le nom du callback
* @param handler Le handler client à appeler
*/
private native static void get(String url, String callbackName,
JSONResponseHandler handler) /*-{
callback = function(j) {
[email protected].
JSONRequestHelper$JSONResponseHandler::onResponseReceived
(Lcom/google/gwt/core/client/JavaScriptObject;)(j);
};
$wnd[callbackName] = callback;
var script = $wnd.document.createElement("script");
script.setAttribute(‘‘type’’, ‘‘text/javascript’’);
script.setAttribute(‘‘src’’, url);
$wnd.document.getElementsByTagName(‘‘head’’)[0].appendChild(script);
}-*/;
}
192
Aller plus loin avec GWT
Contenu de la page HTML hôte
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>JSONP</title>
<script type="text/javascript" language="javascript"
src="gwtchap95/gwtchap95.nocache.js"></script>
</head>
<body>
<h1>JSON web service demo</h1>
<table align="center">
<tr>
<td colspan="2" style="font-weight:bold;">
URL complète du service web:</td>
</tr>
<tr>
<td id="nameFieldContainer"></td>
<td id="sendButtonContainer"></td>
</tr>
<tr>
<td colspan="2" style="font-weight:bold;">Résultat :</td>
</tr>
<tr>
<td colspan="2" id="result"></td>
</tr>
</table>
</body>
</html>
Code source de GwtChap95.java (client)
package oge.gwt.chap95.client;
import oge.gwt.chap95.client.util.JSONRequestHelper;
import oge.gwt.chap95.client.util.JSONRequestHelper.JSONResponseHandler;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
public class GwtChap95 implements EntryPoint {
private TextBox urlField;
private TextArea resultArea;
/**
* Le point d’entrée
*/
193
Annexes
public void onModuleLoad() {
// le champ URL
urlField = new TextBox();
urlField.setWidth("100%");
urlField.setText("http://www.google.com/calendar/feeds/
[email protected]/public/
full?alt=json-in-script&callback=");
// le bouton d’envoi
final Button sendButton = new Button("Envoyer");
sendButton.addStyleName("sendButton");
// la zone qui affichera le résultat
resultArea = new TextArea();
resultArea.setVisibleLines(20);
resultArea.setCharacterWidth(80);
// Ajoute les widgets au RootPanel
RootPanel.get("nameFieldContainer").add(urlField);
RootPanel.get("sendButtonContainer").add(sendButton);
RootPanel.get("result").add(resultArea);
// Focus dans le champ URL
urlField.setFocus(true);
urlField.selectAll();
// Handler pour le bouton et le TextField
class MyHandler implements ClickHandler, KeyUpHandler {
public void onClick(ClickEvent event) {
fetchJSON();
}
public void onKeyUp(KeyUpEvent event) {
fetchJSON();
}
}
MyHandler handler = new MyHandler();
sendButton.addClickHandler(handler);
urlField.addKeyUpHandler(handler);
}
/**
* Cette méthode effectue l’envoi de la requête JSON et affiche
* le résultat dans la TextArea.
*/
private void fetchJSON() {
String url = urlField.getText();
JSONRequestHelper.get(url, new JSONResponseHandler() {
public void onResponseReceived(JavaScriptObject json) {
// convertir l’objet en JSONObject
JSONObject jsonValue = new JSONObject(json);
// convertir en texte
String result = jsonValue.toString();
// afficher dans la TextArea
resultArea.setText(result);
}
});
}
}
194
Aller plus loin avec GWT
Exemple d’utilisation de l’API Google Gears (chapitre 11)
Code source de GwtGearsDemo.java
package oge.gwt.chap11.client;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
com.google.gwt.core.client.EntryPoint;
com.google.gwt.event.dom.client.ClickEvent;
com.google.gwt.event.dom.client.ClickHandler;
com.google.gwt.event.dom.client.KeyCodes;
com.google.gwt.event.dom.client.KeyPressEvent;
com.google.gwt.event.dom.client.KeyPressHandler;
com.google.gwt.gears.client.Factory;
com.google.gwt.gears.client.GearsException;
com.google.gwt.gears.client.database.Database;
com.google.gwt.gears.client.database.DatabaseException;
com.google.gwt.gears.client.database.ResultSet;
com.google.gwt.user.client.Window;
com.google.gwt.user.client.ui.Button;
com.google.gwt.user.client.ui.Grid;
com.google.gwt.user.client.ui.HorizontalPanel;
com.google.gwt.user.client.ui.RootPanel;
com.google.gwt.user.client.ui.TextBox;
com.google.gwt.user.client.ui.VerticalPanel;
/**
* Démonstration de Gears Database
*/
public class GwtGearsDemo implements EntryPoint {
private Database db;
private Grid grid;
private TextBox msgBox;
public void onModuleLoad() {
// Créer la base si elle n’existe pas
try {
db = Factory.getInstance().createDatabase();
db.open("gears-database-demo");
db.execute("create table if not exists demo (
Message text, Timestamp int)");
} catch (GearsException e) {
Window.alert(e.toString());
}
// créer les composants graphiques
VerticalPanel panel = new VerticalPanel();
panel.setSpacing(15);
HorizontalPanel hPanel = new HorizontalPanel();
msgBox = new TextBox();
Button sendBtn = new Button("OK");
Button clearBtn = new Button("Effacer tout");
195
Annexes
hPanel.add(msgBox);
hPanel.add(sendBtn);
hPanel.add(clearBtn);
panel.add(hPanel);
grid = new Grid();
grid.setBorderWidth(1);
panel.add(grid);
RootPanel.get("gears").add(panel);
// ajouter les handlers
msgBox.addKeyPressHandler(new KeyPressHandler() {
public void onKeyPress(KeyPressEvent event) {
if (event.getCharCode() == KeyCodes.KEY_ENTER) {
insertEntry();
}
}
});
sendBtn.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
insertEntry();
}
});
clearBtn.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
clearAll();
updateMessages();
}
});
// initialiser la liste
updateMessages();
}
// effacer tous les messages
private void clearAll() {
try {
db.execute("delete from demo");
} catch (DatabaseException e) {
Window.alert(e.toString());
}
}
// mettre à jour la liste des messages affichée
private void updateMessages() {
// Récupérer les entrées existantes
List<String> messages = new ArrayList<String>();
List<String> timestamps = new ArrayList<String>();
try {
ResultSet rs = db.execute("select * from demo order
by Timestamp");
for (int i = 0; rs.isValidRow(); ++i, rs.next()) {
messages.add(rs.getFieldAsString(0));
timestamps.add(rs.getFieldAsString(1));
196
Aller plus loin avec GWT
}
rs.close();
} catch (DatabaseException e) {
Window.alert(e.toString());
}
// mettre à jour la grille
grid.resize(timestamps.size() + 1, 2);
grid.setText(0, 0, "Date");
grid.setText(0, 1, "Message");
for (int row = 0; row < timestamps.size(); ++row) {
Date date = new Date(Long.valueOf(timestamps.get(row)));
grid.setText(row + 1, 0, date.toString());
grid.setText(row + 1, 1, messages.get(row));
}
}
// ajouter un message dans la base locale
private void insertEntry() {
String msg = msgBox.getText();
try {
db.execute("insert into demo values (?, ?)", new String[] {
msg,Long.toString(System.currentTimeMillis()) });
updateMessages();
msgBox.setText("");
} catch (DatabaseException e) {
Window.alert(e.toString());
}
}
}
197
Annexes
Exemple d’utilisation d’UiBinder
pour créer un widget composite (chapitre 12)
Fichier de description pour UiBinder (SexeRadio2.ui.xml)
<gwt:UiBinder xmlns:ui=’urn:ui:com.google.gwt.uibinder’
xmlns:gwt=’urn:import:com.google.gwt.user.client.ui’>
<gwt:HorizontalPanel>
<gwt:Label text="Sexe :" />
<gwt:VerticalPanel>
<gwt:RadioButton name=’sexeradio’ text=’M’
ui:field=’maleRadio’ />
<gwt:RadioButton name=’sexeradio’ text=’F’
ui:field=’femaleRadio’ />
</gwt:VerticalPanel>
</gwt:HorizontalPanel>
</gwt:UiBinder>
Code source de SexeRadio2.java
package oge.gwt2.uibinder.client;
import
import
import
import
import
import
import
import
import
com.google.gwt.core.client.GWT;
com.google.gwt.event.dom.client.ClickEvent;
com.google.gwt.uibinder.client.UiBinder;
com.google.gwt.uibinder.client.UiField;
com.google.gwt.uibinder.client.UiHandler;
com.google.gwt.uibinder.client.UiTemplate;
com.google.gwt.user.client.ui.Composite;
com.google.gwt.user.client.ui.Panel;
com.google.gwt.user.client.ui.RadioButton;
public class SexeRadio2 extends Composite {
@UiTemplate("SexeRadio2.ui.xml")
interface MyUiBinder extends UiBinder<Panel, SexeRadio2> {}
private static final MyUiBinder binder = GWT.create(MyUiBinder.class);
public SexeRadio2() {
final Panel panel = binder.createAndBindUi(this);
initWidget(panel);
}
@UiField
RadioButton maleRadio;
@UiField
RadioButton femaleRadio;
@UiHandler("maleRadio")
void maleClicked(ClickEvent event) {
GWT.log("C’est un garçon !", null);
}
198
Aller plus loin avec GWT
@UiHandler("femaleRadio")
void femaleClicked(ClickEvent event) {
GWT.log("C’est une fille !", null);
}
public boolean isM() {
return maleRadio.getValue();
}
public boolean isF() {
return femaleRadio.getValue();
}
}
Webographie
GWT : le site principal de Google
http://code.google.com/webtoolkit
Le plugin Google pour Eclipse
http://code.google.com/eclipse
Les groupes de discussion consacrés à GWT (Google Groups)
http://groups.google.com/group/Google-Web-Toolkit
Code source des exemples de ce livre (hébergé sur Google Code)
http://code.google.com/p/livre-gwt
Blog de l’auteur
http://blog.gerardin.info
onGWT : informations quotidiennes sur GWT (en anglais)
http://www.ongwt.com
Le blog Google sur GWT (an anglais)
http://googlewebtoolkit.blogspot.com/
Index
A
AbsolutePanel 61
AJAX 9
Ant 35
API 152
appel de procédure à distance (RPC) 71
applet 6
application
point d’entrée 20
serveur d’applications 36
arborescence 117
asynchronisme 72
Atom 126
B
bibliothèque
de composants 139
Ext JS 139
Ext-GWT 145
GWT-ext 139
MyGWT 145
SmartGWT 149
utilitaire 152
binding 152
bootstrap 38
breakpoint 32
Button 44, 136
C
callback 72, 166
CheckBox 46
checked (exception) 84
classe
Composite 134
Client-side Scripting 7
ClientBundle 178
code
compiler 36
langue 97
pays 97
code splitting 166
compilateur GWT 37
compiler 36
compte-rendu (SOYC) 168
Component Library 151
Composite 134
CSS (styles) 42
cycle de développement 39
D
data binding 145, 148
date
formatage 105
DatePicker 47
DateTimeFormat 106
202
GWT
débug 39
DeckPanel 63
Deferred Binding 110
déploiement 81
DialogBox 58
DisclosurePanel 66
Dockpanel 65
DOM (Document Object Model) 8
XML 124
dossier
war 18
dynamique
interface 41
E
Eclipse 14, 15
Google Plugin for Eclipse 14
installation 16
JEE 16
projet 22
Entity JavaBeans (EJB) 36
Environnement de développement
intégré (IDE) 10, 38
environnement de production 81
événement 43
exception 33, 83
contrôlée 84
Ext JS 139
Ext-GWT 145
F
FlexTable 66
FlowPanel 63
flux
Atom 126
formatage
date 105
nombre 105
fragment (URL) 116
G
Gears 153
gestion
exception 33
historique 51, 115
Google Plugin 14, 17
Google Web Toolkit (GWT)
compilateur 37
historique 10
installation 15
module 21
version 2.0 159
Grid 66
Gwittir 152
GWT Component Library 151
GWT Incubator 150
GWT Server Library 152
GWT Widget Library 151
GWT-ext 139
GWTiger 151
H
handler 43
historique
gestion 51, 115
jeton 116
HorizontalPanel 62
HorizontalSplitPanel 64
hosted mode 31, 35, 76
Out Of Process Hosted Mode
(OOPHM) 161
hosted mode 13
hostname 4
HTTP (module) 122
Hyperlink 51
hypertexte 3
I
i18n 89
Incubator 150
203
Index
Integrated Development Environment
(IDE) 10, 38
interface
Constants 91
ConstantsWithLookup 103
Messages 104
interface homme-machine (IHM) 41
internationalisation 89
statique 90, 112
J
Java 6
exeption 83
sérialisation 34
Java Runtime Environment (JRE) 32
classes 33
JavaScript 7
AJAX 9
mono-thread 33
XmlHttpRequest 9
JEE (Eclipse) 16
jeton d’historique 116
Jetty 36, 76
JSNI (JavaScript Native Interface) 14,
107, 129, 136
JSON 121, 126
JSP 7
L
laison différée 110
langue 89
layout 60, 144
ListBox 51
locale 89, 96
M
MenuBar 53
méthode
JSNI 14
MIME 5
mode
debug 39
hôte 13, 31, 35, 76, 161
web 36
module 21
HTTP 122
I18N 90
mono-thread 33
MVC (Model-View-Controller) 149
MyGWT 145
N
navigateur
version 110
nombre
formatage 105
norme
Servlet Java 72
NumberFormat 105
O
Out Of Process Hosted Mode (OOPHM)
161
P
panel 43, 60
AbsolutePanel 61
DeckPanel 63
DisclosurePanel 66
Dockpanel 65
FlexTable 66
FlowPanel 63
Grid 66
HorizontalPanel 62
HorizontalSplitPanel 64
layout 60
PopupPanel 61
RootPanel 60
StackPanel 62
TabPanel 66
VerticalPanel 62
204
GWT
VerticalSplitPanel 64
paramètres régionaux 97
PasswordTextBox 49
PHP 6
Plain Old Java Object (POJO) 152
plate-forme 37
plugin 15
point
d’arrêt 32
d’entrée 20
de séparation 166
PopupPanel 61
proxy serveur 131
PushButton 45
Q
query string 4
R
RadioButton 45
Remote Procedure Call (RPC) 71, 151,
152
asynchronisme 72
requête HTTP 121
RichtextArea 57
RootPanel 60
S
Same Origin Policy (SOP) 122
schema 4
sérialisation 34, 82
Server Library 152
serveur 187
appel 77
code 71
communication 71
d’applications intégré 36
déploiement 18, 76
Jetty 36
proxy 131
Tomcat 36
servlet 7
Servlet Java 72
SmartGWT 149
split point 166
StackPanel 62
Story Of Your Compile (SOYC) 168
style 42
SuggestBox 54
T
TabPanel 66
TextArea 50
TextBox 49
ToggleButton 48
token (history) 116
Tomcat 36
Tree 53, 117
type
MIME 5
U
UiBinder 174
unchecked (exception) 84
URL 4
fragment 116
hostname 4
query string 4
schema 4
V
version 110, 159
VerticalPanel 62
VerticalSplitPanel 64
W
war
(dossier) 18
Web
historique 3
hypertexte 3
205
Index
web mode 31
web service 122, 128
webAppCreator 35
widget 42, 176
Button 44, 136
CheckBox 46
créer 133
DatePicker 47
DialogBox 58
GWT Component Library 151
GWT Widget Library 151
Hyperlink 51
implémenter 135
ListBox 51
MenuBar 53
panel 60
PasswordTextBox 49
PushButton 45
RadioButton 45
RichtextArea 57
sous-classer 134
SuggestBox 54
TextArea 50
TextBox 49
ToggleButton 48
Tree 53
widget-fils 60
Widget Library 151
X
XML 124
AJAX 9
DOM 124
XmlHttpRequest 9
Téléchargement
Study collections