GWT  - Créer des applications web interactives avec Google Web Toolkit (versions 1.7 et 2.0)
 2100531824, 9782100531820 [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

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



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

Titre statique

URL:
Résultat :


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 :



-->

--> -->

-->

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 , Button par un . 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 . 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() { public void onValueChange(ValueChangeEvent 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() { public void onValueChange(ValueChangeEvent 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( "" + "Google WebToolkit" + "");

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("HTML pour la mise en forme"); 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 : suggestBox.addSelectionHandler(new SelectionHandler() { public void onSelection(SelectionEvent 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)); 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, où T est le type de retour de la méthode originale. Si la méthode retournait void, on utilisera AsyncCallback seul ou AsyncCallback. 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 callback); }

Le type exact du callback est AsyncCallback, 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 :

addServlet oge.gwt.chap52.server.AdditionServiceImpl

addServlet /gwtchap52/add

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() { 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 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 avec un ID « container » qui nous permettra de le référencer dans le code GWT.





Exemple GWT-RPC





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 ).

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 :





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 :

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 :







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 :

ou :

Par exemple, pour utiliser la locale allemande on utilisera :

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 :











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 :

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() { public void onSelection(SelectionEvent 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() { public void onValueChange(ValueChangeEvent 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 :

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 :

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) :

Exemple

2009-12-13T18:30:02Z Marcel Marceau

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 :

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

Demo GWT+Gears



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( "

Google Gears n’est pas disponible sur ce navigateur.
" + "

Veuillez l’installer à partir de " + "cette page et recharger l’application.")); } }

Le code « magique » qui effectue l’aiguillage vers ce point d’entrée dans le fichier module est le suivant :



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() { 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() { 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 { 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 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() { 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 { /** *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 :

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 :







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 {}

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, , 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 :







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 {} 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 :

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)); 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() { 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() {

189

Annexes

public void onSelection(SelectionEvent 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() { public void onValueChange(ValueChangeEvent 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



JSONP

JSON web service demo

URL complète du service web:
Résultat :


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 messages = new ArrayList(); List timestamps = new ArrayList(); 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)







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 {} 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