Ça y est, gwt-maven-plugin 2.5.0 est passé en release ! C’est l’occasion de tester GWT 2.5 sur de vraies applications avec la bibliothèque de composant GXT 3. J’utilise déjà cette version avec GWT 2.4 : pas de problème. Et sur leur site, ils annoncent que c’est compatible avec GWT 2.5… eh bien nous allons voir ça !
Téléchargements
Si Eclipse et Maven sont installés et configurés sur le poste de dev, il n’y a plus qu’à télécharger le SDK de GWT 2.5 (http://google-web-toolkit.googlecode.com/files/gwt-2.5.0.zip) et à configurer le GPE (http://developers.google.com/eclipse/docs/getting_started).
Tous les autres téléchargements se feront via Maven.
Création du projet Maven
Dans Eclipse, créer un nouveau projet Maven (« Maven Project »).
La structure des fichiers dans un projet GWT est différente de la structure des fichiers imposée par Maven. Heureusement, il existe un archetype spécifique fournit par le plugin gwt-maven. La première fois que l’on utilise ce plugin, on doit créer l’archetype correspondant :
Ensuite on continue la création de notre projet avec le nouvel archetype. Maven va se débrouiller comme un grand et nous créer un projet imbriquant les packages de GWT (client, server et shared) dans une structure Maven (src/main/java, src/main/resources, etc.).
Si on passe en revue rapidement les fichiers ici présent, on retrouve :
- Le point d’entrée de l’application GWT : MyModule
- L’interface et l’implémentation « serveur » du portail de services RPC : GreetingService et GreetingServiceImpl (l’interface asynchrone GreetingServiceAsync sera générée automatiquement par Maven plus tard).
- Un validateur par défaut au sens de la JSR 303 : FieldVerifier
- Des fichiers d’internationalisation qui permettront à Maven de générer automatiquement l’interface Message dans le package client : Messages.properties et Messages_fr.properties
- Un fichier de configuration GWT par défaut : clients.gwt.xml
- Les fichiers .launch sont des fichiers permettant à Eclipse de lancer les tests gwtUnit depuis la classe GwtTestMyModule dans le package com.acme.gwt.client
- Une configuration par défaut pour Maven : pom.xml
- Le dossier target qui contiendra les fichiers de notre futur webapp (=futur fichier .war) : demo-0.0.1-SNAPSHOT
Pour enlever les messages d’erreur et d’avertissement, on tape la commande Maven :
Cela va copier les dépendances dans Eclipse, générer les sources pour l’internationalisation et le proxy asynchrone (interfaces GreetingServiceAsync et Message dans le package client). Ensuite on tape :
Cela va compiler une première fois le module GWT et mettre en place la servlet pour lancer GWT en mode Hosted…
Après ça, il peut rester quelques avertissements et deux erreurs liées au fichier pom.xml relatives aux options generatedAsync et i18n. Il me semble que c’est juste un problème de compatibilité avec Eclipse. Pour l’instant j’ignorerai donc ces erreurs.
Lancer GWT en mode hosted (ça peut être très long, la première fois) :
L’application démo se lance. Non ? Alors laissez-moi un commentaire !
Une fois le test terminé, fermer la fenêtre Jetty pour arrêter le DevMode.
Ajouter la librairie graphique Sencha GXT 3
Dans le fichier pom.xml, trouver ou créer la balise <repositories> et y ajouter le repository de Sencha contenant la release de GXT 3 :
1 2 3 4 5 | <repository> <id>sencha-commercial-release</id> <name>Sencha commercial releases</name> <url>https://maven.sencha.com/repo/commercial-release/</url> </repository> |
Ajouter la dépendance de GXT après les autres dépendances à l’interieur de la balise <dependencies> :
1 2 3 4 5 | <dependency> <groupId>com.sencha.gxt</groupId> <artifactId>gxt</artifactId> <version>3.0.0</version> </dependency> |
Rafraîchir les dépendances dans Eclipse pour vérifier que tout marche bien :
Dans le fichier MyModule.gwt.xml, ajouter la ligne :
1 | <inherits name="com.sencha.gxt.ui.GXT"/> |
et supprimer celle-ci car GXT la surcharge :
1 | <inherits name="com.google.gwt.user.theme.standard.Standard"/> |
Pour que les styles CSS de GXT soient pris en compte, il faut ajouter cette ligne dans l’en-tête du fichier MyModule.html qui se trouve dans le dossier src/main/webapp :
1 | <link rel="stylesheet" type="text/css" href="MyModule/reset.css"/> |
Ajouter des composants graphiques GXT
On va faire une application très simple : on charge une liste statique de clients et on fournit un formulaire pour saisir un nouveau client.
On commence par le code « métier ».
Création du Bean Client :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package com.acme.gwt.demo.server.beans; import java.util.Date; public class Client { Â Â Â //////////// ATTRIBUTES //////////// Â Â Â private Integer id; Â Â Â private String nom; Â Â Â private Date dateNaissance; Â Â Â private String email; Â Â Â /////////// CONSTRUCTORS /////////// Â Â Â public Client() { Â Â Â Â Â Â this(null, "noName", null, null); Â Â Â } Â Â Â public Client(Integer id, String nom, Date dateNaissance, String email) { Â Â Â Â Â Â this.id = id; Â Â Â Â Â Â this.nom = nom; Â Â Â Â Â Â this.dateNaissance = dateNaissance; Â Â Â Â Â Â this.email = email; Â Â Â } Â Â Â /////// GETTERS AND SETTERS //////// Â Â Â public Integer getId() { Â Â Â Â Â Â return id; Â Â Â } Â Â Â public void setId(Integer id) { Â Â Â Â Â Â this.id = id; Â Â Â } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public Date getDateNaissance() { return dateNaissance; } public void setDateNaissance(Date dateNaissance) { this.dateNaissance = dateNaissance; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } |
On utilisera GXT, donc on crée une interface ClientProperties pour accéder aux propriétés du client :
1 2 3 4 5 6 |
Création de la façade de services RPC ClientService :
1 2 3 4 5 | @RemoteServiceRelativePath("clientService") public interface ClientService extends RemoteService { List<Client> listerClients(); Client ajouterClient(Client client); } |
Déclaration de la servlet dans web.xml :
1 2 3 4 5 6 7 8 9 | <servlet> <servlet-name>clientService</servlet-name> <servlet-class>com.acme.gwt.demo.server.ClientServiceImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>clientService</servlet-name> <url-pattern>/MyModule/clientService</url-pattern> </servlet-mapping> |
Implémentation de la façade côté serveur dans la classe ClientServiceImpl :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @SuppressWarnings("serial") public class ClientServiceImpl extends RemoteServiceServlet implements ClientService { private static List<Client> clients; private static int nbClients = 0; static { clients = new ArrayList<Client>(); clients.add(new Client(nbClients++, "toto", new Date(), "toto@acme.com")); clients.add(new Client(nbClients++, "tata", new Date(), "tata@acme.com")); clients.add(new Client(nbClients++, "titi", new Date(), "titi@acme.com")); } public List<Client> listerClients() { return clients; } public Client ajouterClient(Client client) { client.setId(nbClients++); clients.add(client); return client; } } |
L’interface asynchrone sera générée par Maven. Et histoire de tester également que l’internationalisation fonctionne, on va internationaliser le titre de l’IHM et le texte du bouton « Ajouter un client ». On rajoute les deux lignes suivantes au fichier Messages.properties :
1 2 | title = Clients list addButton = Add a new client |
On reporte les deux lignes dans le fichier Messages_fr.properties :
1 2 | title = Liste des clients addButton = Ajouter un nouveau client |
On met à jour Eclipse via la commande Maven :
Cela va notamment mettre à jour le fichier d’internationalisation Message.java et créer le fichier ClientServiceAsync.java dans le répertoire « /demo/target/generated-sources/gwt/com/acme/gwt/demo/client/« .
Pour voir les modifications dans Eclipse, il faut rafraîchir les fichiers en appuyant sur « F5″. Si les modifications ne sont toujours pas visibles, ouvrir le menu « Projet » d’Eclipse et sélectionner « Clean… ». Sélectionner ensuite le projet « demo » dans la liste des projets disponibles. Cette deuxième méthode, avec le plugin gwt-maven, résout souvent quelques petits problèmes où les messages d’erreurs ne sont pas clairs… à noter…
On développe ensuite la partie graphique. Je propose d’utiliser UIBinder avec GXT… Pas d’objection ? Si vous avez déjà utilisé UIBinder avec GXT dans les versions précédentes, vous allez me dire qu’il faut ajouter une dépendance Maven dans le fichier pom.xml et qu’il faut la déclarer dans le fichier MyModule.gwt.xml…
Non, rien de tout ça : la version 2.5 de GWT intègre les correctifs soumis par les développeurs de Sencha pour UIBinder, alors pourquoi s’en passer ?
On commence donc par la création du composant graphique ListeClients qui affiche une grille chargeant la liste des clients et le bouton « ajouter clients ». On peut utiliser le plugin GPE pour construire les deux fichiers (xml et java) :
Contenu du fichier ListClient.gwt.xml :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:row="urn:import:com.sencha.gxt.widget.core.client.container" xmlns:grid="urn:import:com.sencha.gxt.widget.core.client.grid" xmlns:form="urn:import:com.sencha.gxt.widget.core.client.form" xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button" xmlns:c="urn:import:com.sencha.gxt.widget.core.client"> <!-- I18N --> <ui:with field="message" type="com.acme.gwt.demo.client.Messages" /> <!-- CONFIGURATION DE LA GRILLE --> <ui:with type="com.sencha.gxt.widget.core.client.grid.ColumnModel" field="cm"></ui:with> <ui:with type="com.sencha.gxt.data.shared.ListStore" field="store"/> <ui:with type="com.sencha.gxt.widget.core.client.grid.GridView" field="view"/> <!-- DESCRIPTION DU COMPOSANT --> <c:ContentPanel headingText="{message.title}"> <!-- LA BOITE DE DIALOGUE --> <c:Dialog ui:field="clientPopup" modal="true" hideOnButtonClick="true"> <row:VerticalLayoutContainer> <form:FieldLabel text="Name"> <form:widget> <form:TextField ui:field="name" allowBlank="false" /> </form:widget> </form:FieldLabel> <form:FieldLabel text="Birthday"> <form:widget> <form:DateField ui:field="date" allowBlank="false"/> </form:widget> </form:FieldLabel> <form:FieldLabel text="Email"> <form:widget> <form:TextField ui:field="email" allowBlank="false" /> </form:widget> </form:FieldLabel> </row:VerticalLayoutContainer> </c:Dialog> <!-- LA GRILLE --> <row:VerticalLayoutContainer borders="true" > <row:child> <grid:Grid ui:field="grid" cm="{cm}" store="{store}" view="{view}"/> </row:child> <row:child> <button:TextButton ui:field="button" text="{message.addButton}" /> </row:child> </row:VerticalLayoutContainer> </c:ContentPanel> </ui:UiBinder> |
Contenu du fichier ListClient.java :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | public class ListClient extends Composite { //UIBINDER private static ListClientUiBinder uiBinder = GWT.create(ListClientUiBinder.class); interface ListClientUiBinder extends UiBinder<Widget, ListClient> { } //PORTAIL RPC private static ClientServiceAsync service = GWT.create(ClientService.class); //CONSRUCTEUR public ListClient() { ClientProperties props = GWT.create(ClientProperties.class); store = new ListStore<Client>(props.id()); //Paramétrage des colonnes ColumnConfig<Client, String> nameColumn = new ColumnConfig<Client, String>(props.nom()); ColumnConfig<Client, Date> dateColumn = new ColumnConfig<Client, Date>(props.dateNaissance()); ColumnConfig<Client, String> emailColumn = new ColumnConfig<Client, String>(props.email()); List<ColumnConfig<Client, ?>> colonnes = new ArrayList<ColumnConfig<Client,?>>(); colonnes.add(nameColumn); colonnes.add(dateColumn); colonnes.add(emailColumn); cm = new ColumnModel<Client>(colonnes); //initialisation du composant graphique initWidget(uiBinder.createAndBindUi(this)); //chargement des données service.listerClients(new AsyncCallback<List<Client>>() { public void onSuccess(List<Client> clients) { store.addAll(clients); } public void onFailure(Throwable e) { e.printStackTrace(); } }); } //EVENEMENTS @UiHandler("button") void onButtonClick(SelectEvent event) { //affichage de la popup clientPopup.show(); } @UiHandler("clientPopup") void onHide(HideEvent event) { //Si on a cliqué sur OK : on sauvegarde le nouveau client if (clientPopup.getHideButton() == clientPopup.getButtonById(PredefinedButton.OK.name())){ Client client = new Client(null, name.getCurrentValue(), date.getCurrentValue(), email.getCurrentValue()); service.ajouterClient(client, new AsyncCallback<Client>() { public void onSuccess(Client clientPersistant) { //Ajout du nouveau client dans la grille store.add(clientPersistant); //réinitialisation de la popup name.reset(); date.reset(); email.reset(); } public void onFailure(Throwable e) { e.printStackTrace(); } }); } } //ATTRIBUTS DU COMPOSANT GRAPHIQUE @UiField TextButton button; @UiField Dialog clientPopup; @UiField TextField name; @UiField DateField date; @UiField TextField email; @UiField ColumnModel<Client> cm; @UiField ListStore<Client> store; @UiField Grid<Client> grid; @UiField GridView<Client> view; //AUTRE //Evite une errreur style "com.sencha.gxt.widget.core.client.grid.ColumnModel has no default (zero args) constructor" @UiFactory ColumnModel<Client> createColumnModel() { return cm; } //Evite une errreur style "com.sencha.gxt.data.shared.ListStore<Client> has no default (zero args) constructor" @UiFactory ListStore<Client> createListStore() { return store; } } |
Concernant la page MyModule.html, je propose de supprimer tout ce qu’il y a dans la balise <body> sauf la balise <iframe> et d’y rajouter un balise <div> :
Cette balise sera remplie par le point d’entrée de l’application MyModule.java :
1 2 3 4 5 6 7 8 | /** Point d'entrée : définit la méthode <code>onModuleLoad()</code>. */ public class MyModule implements EntryPoint { /** Méthode d'entrée, équivalent du 'main' dans une application classique */ public void onModuleLoad() { ListClient listClient = new ListClient(); RootPanel.get("main").add(listClient ); } } |
On compile et on obtient un résultat semblable à celui-ci :
Bonjour,
Un petite erreur s’est glissée dans le web.xml lors de la déclaration de la servlet clientService
OLD:
clientServans web.xmlice
com.acme.gwt.demo.server.ClientServiceImpl
NEW:
clientService
com.acme.gwt.demo.server.ClientServiceImpl
Ping : Blog de la rubrique java
Ping : Le blog de Karbos