GWT 2.5 avec les plugin gwt-maven-plugin 2.5.0 et GXT3

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

mvn eclipse:eclipse

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 :

mvn verify

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

mvn gwt:run

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 :

mvn eclipse:eclipse

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
public interface ClientProperties extends PropertyAccess<Client> {
    ModelKeyProvider<Client> id();
    ValueProvider<Client, String> nom();
    ValueProvider<Client, Date> dateNaissance();
    ValueProvider<Client, String> email();
}

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 :

mvn eclipse:eclipse

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

1
<div id="main"></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 :

mvn gwt:run

3 réflexions au sujet de « GWT 2.5 avec les plugin gwt-maven-plugin 2.5.0 et GXT3 »

  1. kamel

    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

  2. Ping : Blog de la rubrique java

  3. Ping : Le blog de Karbos

Les commentaires sont fermés.