Surcharger les méthodes à la volée

Quesaco ?

Lorsqu’un projet intègre plusieurs librairies Open Source, il est parfois nécessaire de modifier le comportement d’une méthode d’un composant. Pour cela on a recours, en général, à la création de classes d’extension.

En pratique, c’est très facile : on crée une classe dans un joli package qui regroupe nos composants personnalisés, on la fait hériter du composant qui nous intéresse et on surcharge une ou plusieurs de ses méthodes. Finalement, sur une application métier avec plein de fonctionnalités très spécifiques, on se retrouve alors avec plein de petites classes à maintenir alors qu’elles ne servent qu’une fois. Parfois même, dans la durée, ces classes sont dépréciées et inutilisées, car le composant a changé : on garde alors des fichiers inutiles pour ne pas détruire le travail d’un développeur tiers !

On  pourrait les intégrer au code en créant des classes internes. Mais si on veut sortir une partie du code, il faut penser à déplacer également les classes internes impactées… ou à les rendre publiques…Bref, ça revient presque au même, c’est juste plus facile de repérer le code mort.

La solution que je propose donc ici, c’est de n’écrire que le morceau de code à modifier directement au moment où l’on instancie le composant qui nous intéresse. Nous allons surcharger les méthodes à la volée ! C’est facile à maintenir et rapide à écrire. Techniquement, il s’agit de classes internes anonymes (inner anonymous class). C’est pas nouveau, mais on les voit pas souvent.

Pour l’exemple, nous allons surcharger la méthode toString() de la classe ArrayList de l’API java.util. Nous allons donc créer un simple projet console avec une méthode main :

public class InnerAnonymousClass {
    public static void main(String[] args) {
        
        //Création d'une instance de la classe interne anonyme qui étend la classe standard "ArrayList"
        List<String> developpeurs = new ArrayList<String>(){
            @Override
            public String toString(){
                //Si la liste est vide, on affiche "liste vide"
                if(size() == 0) return "Liste vide.\n";
                //Sinon, on concatène une liste des valeurs
                StringBuilder sb = new StringBuilder("Liste des développeurs : \n");
                for (int i = 0; i < size(); i++) {
                    sb.append("\t- ").append(get(i)).append("\n");
                }
                return sb.toString();
            }
        };
        
        //On affiche la liste vide
        System.out.println(developpeurs.toString());
        
        //On remplit la liste
        developpeurs.add("Joshua J.Bloch");
        developpeurs.add("James Gosling");
        developpeurs.add("Doug Lea");
        //On affiche la liste pleine
        System.out.println(developpeurs.toString());
        
        //On clone la liste avec la classe ArrayList standard
        List<String> listeTemoin = new ArrayList<String>(developpeurs);
        //On appelle sa méthode toString pour comparer les résultats
        System.out.println(listeTemoin.toString());
    }
}

En console on peut voir une belle implémentation de la méthode toString(), adaptée à notre projet console :

Liste vide.

Liste des développeurs : 
    - Joshua J.Bloch
    - James Gosling
    - Doug Lea

Et l’implémentation standard adaptée à l’affichage dans le debugger d’Eclipse :

[Joshua J.Bloch, James Gosling, Doug Lea]

Restrictions : on ne peut pas faire hériter une classe (anonyme ou non) d’une classe marquée par le mot clé « final« .

La validation des données avec GWT 2.5

GWT 2.5 intègre désormais la validation des données selon la JSR303. Je propose donc de s’intéresser à cette nouvelle fonctionnalité.

L’objectif d’un framework de validation des données est de réduire les contrôles que l’on peut effectuer sur les données, soit côté client, soit côté serveur. Pour des raisons de sécurité on pourrait refaire certains contrôle côté serveur pour s’assurer qu’il ont bien été fait côté client, mais ce n’est pas le sujet du jour.

Nous allons donc commencer par un simple contrôle de la saisie utilisateur dans la partie client-side d’une application GWT. Puis, nous délèguerons une partie de la validation des données au serveur via un appel RPC.

Pour réaliser cette petite POC, je vais démarrer depuis le module MyModule du post précédent.

Configuration de Maven

Le framework de validation de GWT 2.5 est basé sur Hibernate-Validator. Il va donc falloir ajouter cette librairie et ses sources à notre projet. Pourquoi les sources ? Parce que GWT a besoin des sources pour convertir du code Java en Javascript.

Dans le fichier pom.xml, on commence donc par ajouter les dépendances ci-dessous :

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>4.1.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>4.1.0.Final</version>
        <classifier>sources</classifier>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.16</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.6.1</version>
    </dependency>

Remarque :

Si le fichier pom.xml a été généré automatiquement via l’archetype « gwt-maven-plugin« , on obtient parfois, au moment de la compilation, un message contenant « package javax.validation.constraints does not exist« . Cela est dû au fait que le projet de démo importe une version antérieure de l’API de validation avec une portée de test. Donc Eclipse importe les bons fichiers, mais Maven plante lors de la phase des tests, même si on les a supprimé… Il faudra, par exemple, supprimer les lignes ci-dessous :

    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>1.0.0.GA</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>1.0.0.GA</version>
      <classifier>sources</classifier>
      <scope>test</scope>
    </dependency>

Validation simple avec GWT 2.5

Pour utiliser le framework de validation fourni par GWT, nous allons procéder en trois étapes :

  1. définir les contraintes à vérifier sur les Java Beans
  2. implémenter l’usine abstraite AbstractGwtValidatorFactory
  3. lancer la validation lorsque l’utilisateur soumet un nouveau client

Dans la  classe Client, commençons donc par rajouter une contrainte simple à l’attribut « nom » afin que celui-ci contienne au moins 4 caractères :

public class Client implements Serializable  {
    //////////// ATTRIBUTES ////////////
    private Integer id;
    @Size(min = 4)
    private String nom;
    ...
}

Voilà le première étape terminée. Passons maintenant à cette implémentation qui pourrait effrayer par son intitulé. On crée la classe ci-dessous dans la partie client et je la commente ensuite :

public final class SimpleValidatorFactory extends AbstractGwtValidatorFactory {

  @GwtValidation(Client.class)
  public interface GwtValidator extends Validator {
  }

  @Override
  public AbstractGwtValidator createValidator() {
    return GWT.create(GwtValidator.class);
  }
}

C’est toujours un peu le même mécanisme que l’on retrouve dans GWT : on crée une interface, les annotations vont permettre à GWT de déterminer certaines options pour générer les implémentations automatiques de ces interfaces, puis la méthode GWT.create va lier le bon morceau de code Javascript au moment adéquat.

On n’oublie pas de rajouter les lignes ci-dessous dans MyModule.gwt.xml :

    <inherits name="org.hibernate.validator.HibernateValidator" />
    <replace-with
        class="com.acme.gwt.demo.client.validator.SimpleValidatorFactory">
        <when-type-is class="javax.validation.ValidatorFactory" />
    </replace-with>

Puis on rajoute un petit algorithme de validation des données et d’affichage d’erreurs dans la classe ListClient :

    @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());
            //validation des données
            Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
            Set<ConstraintViolation<Client>> violations = validator.validate(client);
            if(!violations.isEmpty()){
                clientPopup.show(); //empeche la popup de se fermer
                StringBuilder builder = new StringBuilder();
                for (ConstraintViolation<Client> violation : violations) {
                    builder.append(violation.getMessage());
                    builder.append(" : <i>(");
                    builder.append(violation.getPropertyPath().toString());
                    builder.append(" = ");
                    builder.append("" + violation.getInvalidValue());
                    builder.append(")</i>");
                    builder.append("<br/>");
                }
                //Affiche un message d'erreur avec les contraintes non respectées
                Info.display("Contraintes non respectées !", builder.toString());
            } else {
                //persistance du nouveau client
                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();
                    }
                });
            }
        }
    }

Le message d’erreur est généré automatiquement par GWT, et en français, s’il vous plait :

Personnalisation des contraintes

Comme on peut le constater dans la capture ci-dessus, le message n’est pas approprié puisqu’il affiche une valeur maximum que l’on n’a pas définit. On doit donc le modifier :

@Size(min = 4, message="Le nom doit contenir au moins {min} lettres !")
private String nom;

Ça marche (faites le test)… mais c’est moche ! Une chaîne de texte passée en dur comme ça c’est bien pour tester le framework de validation, mais en production, il va falloir pouvoir manipuler ça avec un fichier properties afin d’obtenir un joli code comme ça :

@Size(min = 4, message="{custom.name.size.message}")
private String nom;

Pour une raison que j’expliquerai plus bas, nous allons commencer par ajouter les messages personnalisés dans les fichiers ValidationMessages.properties

custom.name.size.message=The property must be between {min}

et ValidationMessages_fr.properties

custom.name.size.message=Le nom doit contenir au moins {min} caracteres

On place ces deux fichiers dans le package com.acme.gwt.demo.client.validator, puis on va laisser Maven générer le bundle i18n en rajoutant les lignes ci-dessous dans le fichier pom.xml :

<i18nConstantsWithLookupBundle>
  com.acme.gwt.demo.client.validator.ValidationMessages
</i18nConstantsWithLookupBundle>

et en lançant la commande :

mvn gwt:i18n

L’interface est bien générée dans le package com.acme.gwt.demo.client.validator. Au passage, je rappelle la différence entre l’interface com.google.gwt.i18n.client.ConstantsWithLookup et l’interface com.google.gwt.i18n.client.Messages : la première génère des constantes javascript de manière dynamique, c’est-à-dire que les paramètres vont être lus une fois pour toute à la compilation, tandis que la seconde génère des méthodes javascript qui vont pouvoir recevoir des paramètres au moment de leur appel.

Toujours dans le même package, nous allons créer la classe CustomValidationMessagesResolver afin d’implémenter l’interface com.google.gwt.validation.client.UserValidationMessagesResolver.

public class CustomValidationMessagesResolver extends
        AbstractValidationMessageResolver implements
        UserValidationMessagesResolver {
    protected CustomValidationMessagesResolver() {
        super((ConstantsWithLookup) GWT.create(ValidationMessages.class));
    }
}

Le code de la classe que l’on étend ici (AbstractValidationMessageResolver). Son code source est ici. Attention à ce que la classe ValidationMessages soit bien celle que l’on vient de créer. C’est pour cela que j’ai souhaité démarré par cette étape. Il n’y aura pas de message d’erreur si, par exemple, on a importé la classe org.hibernate.validator.ValidationMessages

Pour que notre implémentation de l’interface soit utilisée à la place de l’implémentation par défaut, on doit le préciser dans le fichier MyModule.gwt.xml :

    <!-- Validation -->
    <inherits name="org.hibernate.validator.HibernateValidator" />
    <replace-with
        class="com.acme.gwt.demo.client.validator.SimpleValidatorFactory">
        <when-type-is />
    </replace-with>
    <replace-with
        class="com.google.gwt.sample.validation.client.CustomValidationMessagesResolver">
        <when-type-is
            class="com.google.gwt.validation.client.UserValidationMessagesResolver" />
    </replace-with>

On lance la compilation, et on obtient notre message personnalisé :

mvn gwt:run

Liste des annotations commentées dans l’excellent article de J.M. Doudoux : http://jmdoudoux.developpez.com/cours/developpons/java/chap-validation_donnees.php#validation_donnees-2-4.

Configuration de GXT 3, GWT 2.5 et Maven

Ça y est, gwt-maven-plugin 2.5.0 est passé en release ! C’est l’occasion de tester GWT 2.5 avec GXT 3… Théoriquement, c’est compatible… 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 :

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

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

<inherits name="com.sencha.gxt.ui.GXT"/>

et supprimer celle-ci car GXT la surcharge :

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

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

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 :

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 :

@RemoteServiceRelativePath("clientService")
public interface ClientService extends RemoteService {
  List<Client> listerClients();
  Client ajouterClient(Client client);
}

Déclaration de la servlet dans web.xml :

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

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

title = Clients list
addButton = Add a new client

On reporte les deux lignes dans le fichier Messages_fr.properties :

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 :

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

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

<div id="main"></div>

Cette balise sera remplie par le point d’entrée de l’application MyModule.java :

/** 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

Bonjour tout le monde !

Bienvenue sur mon blog !

J’ai parfois l’occasion de consacrer un peu de temps à tester les nouveautés autour de JavaEE. Je note ça sur un cahier et puis ça disparait. Je me suis donc dit que j’allais mettre ça sur un support facile à mettre à jour et facile à diffuser.

Au début, ce blog va donc paraître un peu vide. Mais, petit à petit, il va se remplir d’informations utiles et datées. De temps en temps, je passerai également un peu de temps à le rendre moins moche.

J’espère qu’il apportera un peu de connaissances aux développeurs Java francophones !