IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Tutoriel pour implémenter le patron de conception MVP dans une application web avec Spring, GWT et Hibernate

Projet GWT MVP Image non disponible


précédentsommairesuivant

II. Première partie : Partie cliente

L'objectif dans cette partie sera de développer l'interface client en se basant sur le modèle MVP de GWT.

II-A. Organisation d'un projet GWT

Avant de commencer les développements, voyons comment est structuré un projet GWT.

Pour rappel, voici la structure de notre projet :

packages GWT
Structure packages GWT

Un projet typiquement GWT est construit autour de trois packages principaux :

  • le package client ;
  • le package server (ou serveur) ;
  • le package shared (package partagé entre le client et le serveur).

II-A-1. Le package client

Ce package contient les vues construites à base de Widgets ou composants fournis par GWT, donc pas de code métier. Son rôle est de fournir à l'utilisateur des moyens d'interaction avec l'application à travers des composants.

Après modification du code dans cette partie, pas besoin de recompiler l'application. Un simple rafraîchissement du navigateur est suffisant.

Dans cette couche, nous allons donc créer les Vues, la ClientFactory, le Presenter, la Place, l'Activity, l'EventBus et les Mappers, dont voici quelques définitions.

II-A-1-a. Définitions

Widget : c'est un composant en GWT, exemple : une zone de texte (TextBox). Pour séparer la vue de sa présentation, GWT offre un moyen appelé Uibinder.

Uibinder : c'est un moyen offert par GWT pour dessiner les interfaces graphiques à travers un fichier xml afin de mieux séparer la vue et sa présentation. Pour fabriquer les vues, on peut utiliser une factory ou ClientFactory.

ClientFactory : c'est la classe permettant de fabriquer les vues, de fournir le bus d'événements (ou EventBus), et la PlaceController. L'utilisation d'une factory n'est cependant pas obligatoire.

EventBus : quand un événement survient, tous ceux qui sont à l'écoute doivent être informés. C'est grâce à l'EventBus que tout cela est possible, par exemple la PlaceController peut être informée de la création d'une nouvelle Place.

PlaceController : c'est la classe utilisée par la ClientFactory pour naviguer entre les Places. Son rôle c'est de diffuser les Places dans l'EventBus.

Place : une Place représente une page de l'application. Elle correspond à l'endroit où on se trouve dans l'application. Donc à chaque nouvelle page, il faudra créer une nouvelle Place. Une Place doit toujours être associée à une PlaceTokeniZer qui permet de la sérialiser, c'est-à-dire transformer une Place en URL.

PlaceTokeniZer : c'est l'interface qui permet de sérialiser une Place, c'est-à-dire de transformer une Place en URL. Une URL en GWT est constituée du nom de la Place suivi des deux-points (:) et se termine par le Token (ou jeton) retourné par le PlaceTokeniZer. La gestion de l'historique de navigation entre les pages est assurée par la PlaceHistoryMapper.

PlaceHistoryMapper : c'est le gestionnaire de l'historique de navigation. Toutes les Places ou pages de l'application doivent être référencées dans cette interface afin d'être prises en compte par le Handle qui est PlaceHistoryHandler.

PlaceHistoryHandler : son rôle est de définir un mapping bidirectionnel entre la page et l'URL associée. Chaque page dynamique de l'application doit avoir un moyen qui permet à la vue de communiquer avec sa partie métier : c'est le Presenter (ou Présenteur).

Presenter : le Présenteur est une interface contenant des méthodes qui permettent à la vue de communiquer avec l'Activity (service traiteur).

Activity : une Activity (activité) représente ce que fait l'utilisateur dans une Place. C'est la classe chargée de traiter les demandes sollicitées par la vue, par exemple le clic sur un bouton. Ces demandes lui sont transmises grâce au présenteur. Chaque activité est liée à une Place c'est-à-dire une page. C'est l'ActivityMapper qui est chargée de mapper une Place à son Activity à travers la ClientFactory.

l'Activity ne doit pas contenir de composants, ni de logique de présentation. Son rôle est d'initialiser et de restaurer la page avant son affichage.

ActivityMapper : c'est l'interface chargée de créer une instance de l'activité en fonction de la Place qui est passée en paramètre. Dans les faits, c'est le gestionnaire des activités, l'ActivityManager qui récupère les Places diffusées dans l'EnventBus et la met à disposition de l'ActivityMapper.

ActivityManager : c'est le gestionnaire des activités. Il est à l'écoute de l'eventBus pour traquer les Places diffusées par la PlaceController afin de les transmettre à l'ActivityMapper.

Tous ces termes que vous venez de voir feront l'objet d'une mise en pratique lors du développement de la partie cliente de l'application.

II-A-2. Le package server

C'est dans ce package qu'il faut coder la logique métier, les échanges avec les autres applications et les services transverses. Les services RPC seront implémentés dans cette couche.

Un service RPC est un service qui fonctionne en mode asynchrone (utilise le mode de communication en différé) comparativement à un mode de communication synchrone où les échanges d'informations sont directs.

Un exemple d'échanges asynchrones (mode non connecté) peut être l'envoi des méls, ou l'emprunt d'un livre à la bibliothèque.

Un exemple d'échanges synchrones (mode connecté) peut être l'appel téléphonique.

Toute modification du code dans le package server nécessite une recompilation du module.

II-A-3. Le package shared

Ce package contient des modules ou classes qui sont à la fois nécessaires côté client et côté serveur. C'est-à-dire des classes utilisables par le client et le serveur. Nous allons y mettre toutes les interfaces asynchrones afin d'intercepter tous les appels asynchrones sollicités par l'utilisateur. À chaque appel asynchrone correspond un service RPC. Par exemple pour l'interface IBookServiceRpcAsync, il faudra aussi une interface IBookServiceRpc. L'implémentation de IBookServiceRpc se fera dans le package server. À chaque service RPC est associé une URL qui permet de garder le lien avec différentes implémentations. Exemple : @RemoteServiceRelativePath("rpc/bookServiceRpc")

On peut aussi mettre les interfaces asynchrones dans le module client, mais je trouve que ça peut créer des confusions.

Toute modification dans ce package doit faire l'objet d'une nouvelle compilation. Le rafraîchissement du navigateur ne suffit pas.

Dans la suite, tout module qui ne concerne pas GWT doit être créé dans un package différent (pas obligatoire, mais nécessaire pour une bonne séparation des modules du projet).

II-B. Implémentation du pattern GWT-MVP

II-B-1. Présentation du patron de conception GWT-MVP

Le patron de conception MVP (Model View Presenter ou en français : Modèle Vue Présenteur) est un modèle de développement d'applications qui dérive du modèle MVC (Model View controller). Dans le pattern MVP, le modèle (partie des traitements ou services) est complètement séparé de la présentation ou de la vue (View). Dans ce pattern, le présenteur joue le rôle de contrôleur au sens strict du terme afin d'isoler toute communication directe entre la vue et le modèle.

Voici le rôle de chaque couche :

  • Le Modèle assure la logique métier. C'est lui qui fait les traitements. Il ne contient aucun composant graphique.
  • La Vue est l'interface par laquelle l'utilisateur agit sur l'application. Aucun traitement ne devrait avoir lieu dans la vue. Elle sert uniquement d'affichage et contient donc des composants graphiques.
  • Le Présenteur assure la communication entre la vue et le modèle. Il connait toutes les méthodes de traitements et de mises à jour de la vue.

L'avantage de ce pattern est que l'implémentation de la vue peut être modifiée sans aucun impact sur la logique métier. Ce qui n'est pas possible avec le MVC, car dans ce modèle, le contrôleur et la vue manipulent le modèle, ce qui rend la séparation entre la vue et le modèle difficile.

Voici une image qui illustre le modèle MVP.

Schéma du pattern MVP
Schéma du pattern MVP

Pour illustrer l'implémentation de ce design, nous allons développer la page d'accueil WelcomeView.

Cette page contiendra :

  • un bouton qui permet de déclencher l'inscription ;
  • une zone de texte pour le login ;
  • une zone de texte pour le mot de passe ;
  • un bouton pour se connecter à son espace personnel.

Le résultat final devrait donner ceci :

image page d'accueil
Page d'accueil WelcomeView à développer

II-B-2. Implémentation de la Vue

Note : je vous propose de créer un nouveau package view dans le package client. C'est dans ce package que nous allons créer toutes les vues.

Nous allons donc commencer par créer une interface IWelcomeView qui hérite des composants IsWidget de GWT nécessaires à notre vue.

Interface IWelcomeView.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
package com.developpez.bnguimgo.gwt.client.view;
import com.google.gwt.user.client.ui.IsWidget;

public interface IWelcomeView extends IsWidget {

    void setPresenter(Presenter presenter);
    void showError(String errorMessage);
    void showSucces(String succesMessage);
        
    public interface Presenter {

    void goToCreateComptePlace(String token);
    void findUserByEmail(String email, String password); 
    

    }

}

Explications

Interface vue
Méthodes exposées par la vue et le Présenteur

Notre vue doit contenir des composants tels que les boutons et les zones de texte. Autant mieux prendre cela en compte immédiatement par héritage de l'interface IsWidget.

IsWidget est une interface qui expose les composants GWT.

Dans cette interface, il y a la méthode setPresenter et l'interface interne Presenter. La présence simultanée de la méthode setPresenter et de l'interface Presenter permet d'établir une communication bidirectionnelle entre la vue et le présenteur. La méthode setPresenter(Presenter presenter) permet à la vue de récupérer le présenteur qui lui servira de communication avec son Activity. L'interface Presenter expose des méthodes qui permettent à son Activity de traiter les demandes de la vue : c'est à l'Activity d'implémenter ces méthodes.

Pour transmettre la page de création du compte, le présenteur aura besoin de la méthode goToCreateComptePlace(String token). La vue quant à elle, va transmettre au présenteur l'email et le mot de passe et attendre que le présenteur la redirige vers une nouvelle page. Le présenteur va se servir de la méthode findUserByEmail(String email, String password) pour demander à l'Activity d'aller contrôler l'existence de cet utilisateur en base de données. Si tout se passe bien, l'Activity renvoie l'utilisateur vers la nouvelle page, sinon, on reste à la page d'accueil.

Le Presenter est implémenté par l'Activity. C'est donc à l'activity que le présenteur transmet toutes les demandes utilisateurs : c'est l'Activity qui fait le boulot. Le Presenter sert juste à la vue de communiquer avec l'Activity.

Dans une communication unidirectionnelle, la vue n'a pas besoin du présenteur, et la méthode setPresenter(Presenter presenter) n'a plus sa raison d'être, mais dans ce cas tous les événements liés à la communication avec la vue doivent être préalablement enregistrés au démarrage de l'Activity.

La méthode showError(String errorMesse) est implémentée par la vue pour afficher les messages d'erreurs. Il en est de même de la méthode showSucces(String succesMesse) en cas de succès.

Les méthodes implémentées par la vue sont dans l'interface IWelcomeView alors que celles implémentées par l'Activity sont dans l'interface Presenter. N'oubliez pas que l'interface Presenter est incluse dans l'interface IWelcomeView, ce qui permet à la vue de s'assurer qu'elle dispose d'un moyen de communication avec son Activity et que le Présenteur connait les méthodes qui vont traiter les demandes de la vue. Nous verrons plus bas l'importance de cette organisation.

Implémentation de la vue

Je vous conseille de créer un package impl dans le package view. Nous allons créer la classe WelcomeViewImpl qui sera une implémentation de la vue IWelcomeView.
L'objectif de cette implémentation, c'est de construire la vue à travers les composants GWT.

WelcomeViewImpl.java
Sélectionnez
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.
package com.developpez.bnguimgo.gwt.client.view.impl;
public class WelcomeViewImpl extends Composite implements IWelcomeView{
    
    private Presenter presenter;
    private Button createCompteButton = new Button("CREATION COMPTE");
    private TextBox emailTextBox = new TextBox();
    private PasswordTextBox passwordTextBox = new PasswordTextBox();
    private Button connexionButton = new Button("CONNEXION");
    private Label connexionStatus = new Label();

    public WelcomeViewImpl() {
        
        VerticalPanel welcomeVPanel = initComponents();
        initWidget(welcomeVPanel); //création de la vue
        // Ajout des Handles pour la gestion des événements
        createCompteButton.addClickHandler(new ClickHandler(){

            @Override
            public void onClick(ClickEvent event) {
                presenter.goToCreateComptePlace("CREATIONCOMPTE");
                
            }
            
        });
        
        connexionButton.addClickHandler(new ClickHandler(){

            @Override
            public void onClick(ClickEvent event) {
                presenter.findUserByEmail(emailTextBox.getValue(), passwordTextBox.getValue());
                
            }
            
        });
    }

    @Override
    public void setPresenter(Presenter presenter) {
        this.presenter = presenter;        
    }

    @Override
    public void showError(String errorMessage) {
        connexionStatus.getElement().getStyle().setColor("red");
        connexionStatus.setText(errorMessage);
    }
    @Override
    public void showSucces(String succesMessage) {
        connexionStatus.getElement().getStyle().setColor("blue");
        connexionStatus.setText(succesMessage);
    }

    
    private VerticalPanel initComponents(){
        
        HorizontalPanel createCompteHPanel = new HorizontalPanel();
        createCompteHPanel.add(createCompteButton);
        
        HorizontalPanel emailHPanel = new HorizontalPanel();
        Label emailLabel = new Label("MAIL : ");
        emailHPanel.add(emailLabel);
        emailHPanel.add(emailTextBox);
        
        HorizontalPanel passwordHPanel = new HorizontalPanel();
        Label passwordLabel = new Label("PASS : ");
        passwordHPanel.add(passwordLabel);
        passwordHPanel.add(passwordTextBox);
        
        HorizontalPanel connexionHPanel = new HorizontalPanel();
        connexionHPanel.add(connexionButton);
        
        VerticalPanel welcomeVPanel = new VerticalPanel();
        welcomeVPanel.add(createCompteHPanel);
        welcomeVPanel.add(connexionStatus);
        welcomeVPanel.add(emailHPanel);
        welcomeVPanel.add(passwordHPanel);
        welcomeVPanel.add(connexionHPanel);
        return welcomeVPanel;
        
    }

}

N'oubliez pas de compléter les imports nécessaires.

Notre vue est à présent créée, dans la suite nous allons reconstruire cette même vue avec UiBinder. Pour obtenir une instance de cette vue, nous allons passer par la Factory. L'intérêt de créer la vue à travers une interface factory, c'est de fournir plusieurs implémentations. On peut par exemple construire la vue en fonction de la taille de l'écran (Smartphones, Mobiles, etc.). C'est l'interface ClientFactory qui sera chargée de fournir différentes implémentations de la vue, l'EventBus et la PlaceController.

II-B-3. Implémentation de la ClientFactory

La ClientFactory, intégrée à chaque Activity, sera chargée de fournir différentes implémentations de la vue. Elle permet de rendre les vues réutilisables. Elle fournit aussi l'EventBus pour la gestion des événements, la PlaceController afin d'initialiser l'historique de navigation entre les pages. Je vous conseille de créer un package factory dans le package client.

La ClientFactory n'est pas toujours nécessaire, mais en cas d'absence, il faut utiliser un Framework d'injection de dépendance comme GIN pour obtenir une référence des objets comme l'EventBus.

Voici notre interface ClientFactory :

Interface ClientFactory.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
package com.developpez.bnguimgo.gwt.client.factory;
public interface ClientFactory {

    EventBus getEventBus();

    PlaceController getPlaceController();

    IWelcomeView getWelcomeView();
    
    Place getWhere();

    void goTo(Place place);

}

Pour le moment, nous n'avons qu'une seule vue à afficher : WelcomeView. Dans la suite, nous ajouterons les autres vues dans cette même factory. Nous avons aussi ajouté deux méthodes : une pour indiquer où on se trouve : getWhere(), et une autre pour indiquer où on va : goTo(Place place). Ces deux méthodes sont nécessaires à la navigation.

Lors de l'import de l'EventBus, choisir le package import com.google.web.bindery.event.shared.EventBus, au lieu de : import com.google.gwt.event.shared.EventBus ; sinon, vous aurez une erreur lors de l'exécution.

Implémentation de la ClientFactory

Dans le même package factory, créer comme ci-dessous, la classe : ClientFactoryImpl

ClientFactoryImpl.java
Sélectionnez
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.
package com.developpez.bnguimgo.gwt.client.factory;
public class ClientFactoryImpl implements ClientFactory {

    protected PlaceController placeController;
    protected EventBus eventBus;
    
    public ClientFactoryImpl() {
        super();
        eventBus = new SimpleEventBus();
        placeController = new PlaceController(eventBus);
    }
    
    @Override
    public EventBus getEventBus() {
        return eventBus;
    }

    @Override
    public PlaceController getPlaceController() {
        return placeController;
    }

    @Override
    public IWelcomeView getWelcomeView() {
        return new WelcomeViewImpl();
    }

    @Override
    public Place getWhere() {
        return getPlaceController().getWhere();
    }

    @Override
    public void goTo(Place newPlace) {
        getPlaceController().goTo(newPlace);
        
    }

}

Ce qu'il faut retenir ici, c'est que grâce à la factory, on peut choisir la vue à afficher. Cette factory nous fournit la PlaceController pour faciliter la navigation entre les pages et l'EventBus pour gérer les événements. Grâce à la ClientFactory, on saura à tout moment où on se trouve et où on va.

La création d'une instance de la ClientFactory se fait via le deferred binding fourni par GWT. Il faut obligatoirement indiquer à GWT où se trouve la classe implémentant la factory. C'est pourquoi il faut ajouter dans le fichier BookManager.gwt.xml l'élément suivant :

configuration de la factory dans BookManager.gwt.xml
Sélectionnez
<replace-with class="com.developpez.bnguimgo.gwt.client.factory.ClientFactoryImpl">
<when-type-is class="com.developpez.bnguimgo.gwt.client.factory.ClientFactory"/>
</replace-with>

Nous verrons plus loin comment modifier l'Entry-point pour intégrer ces changements.

Intéressons-nous dès à présent à la logique métier. Rappelez-vous : toutes les demandes de la vue sont adressées au Presenter qui connait quelle méthode est capable de répondre à la demande. Toutes ces méthodes sont implémentées par l'activity (mais nous verrons encore plus loin qu'en réalité, l'activity transmet aussi la demande à un service RPC et attend en retour une réponse pour renvoyer à la vue).

II-B-4. Création de l'Activity

Pour chaque activité, il y a un début et une fin. L'objectif primaire de notre activité ici, c'est de créer et d'afficher la page WelcomeView. Nous aurons besoin de la ClienFactory pour le faire. Ensuite, on doit se préparer à répondre aux exigences d'un utilisateur qui veut créer son compte : implémenter la méthode goToCreateComptePlace(String token), ou à celui qui veut se connecter : implémenter la méthode findUserByEmail(String email, String password). Dans l'architecture GWT, le démarrage d'une activité se fait par la méthode start(AcceptsOneWidget panel, EventBus eventBus) fournie par l'extension de AbstractActivity de GWT.

Je vous conseille de créer un package activity dans le package client. Toutes les autres activities seront créées dans le même package. Nous allons y créer la classe WelcomeActivity chargée de traiter les demandes de la vue.

WelcomeActivity.java
Sélectionnez
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.
package com.developpez.bnguimgo.gwt.client.activity;
public class WelcomeActivity extends AbstractActivity implements IWelcomeView.Presenter {

    private static Logger logger = Logger.getLogger(WelcomeActivity.class.getName());
    private static IWelcomeView welcomeView;
    private ClientFactory clientFactory;
    
    public WelcomeActivity() {
        super();
    }
    
    public WelcomeActivity(ClientFactory clientFactory){
        this.clientFactory = clientFactory;//initialisation de la factory
    }
    
    @Override
    public void start(AcceptsOneWidget panel, EventBus eventBus) {
        logger.info("Démarrage de "+ WelcomeActivity.class.getName());
        welcomeView = clientFactory.getWelcomeView();
        welcomeView.setPresenter(this);
        panel.setWidget(welcomeView.asWidget());
        
    }

    @Override
    public void goToCreateComptePlace(String token) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void findUserByEmail(String email, String password) {
        // TODO Auto-generated method stub
        
    }

}

Explications :

Comme prévu, nous avons un objet factory pour fabriquer notre vue. Cet objet est initialisé dans le constructeur. Au démarrage, la vue est instanciée et un Presenter lui est associé afin de prévoir des échanges entre la vue et l'activity. Le tout est ajouté dans un Panel AcceptsOneWidget. Les autres méthodes seront implémentées ultérieurement. Rien ne nous oblige actuellement, on va attendre que l'utilisateur clique d'abord.

Question : Comment allons-nous afficher alors la page d'accueil si on n'a aucun bouton ? Nous pourrions utiliser la méthode goTo(welcomePlace) fournie par la ClientFactory, mais là, on cherche encore à afficher la page d'accueil, donc impossible.

Réponse : Au démarrage de l'application, on doit prendre soin d'indiquer vers quelle Place se diriger. Cette Place sera la page d'accueil de notre application. Vous comprenez qu'il nous faut créer une WelcomePlace pour la page d'accueil.

II-B-5. Création de la Place

Je vous conseille de créer un package place dans le package client. Nous allons y créer la Place WelcomePlace.

WelcomePlace.java
Sélectionnez
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.
package com.developpez.bnguimgo.gwt.client.place;
public class WelcomePlace extends Place {

    private String token;

    public WelcomePlace(String token) {
        this.token = token;
    }
    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
    
    //sans ce préfixe, c'est le nom de la classe qui est utilisé par défaut
    @Prefix(value = "welcomePlace") 
    public static class Tokenizer implements PlaceTokenizer<WelcomePlace>{

        @Override
        public WelcomePlace getPlace(String token) {
            return new WelcomePlace(token);
        }

        @Override
        public String getToken(WelcomePlace place) {
            return place.getToken();
        }
        
    }
}

Rappelez-vous qu'à chaque Activity correspond une Place.
Dans la Place construite, nous lui avons ajouté un moyen pour construire l'URL : c'est le rôle de la classe interne Tokenizer qui est une implémentation de PlaceTokenizer. Cette classe est chargée de construire l'URL de chaque page sous la forme : http://UrlServeur:port/page.html#nomPlace:token. Par exemple nous aurons dans notre cas : http://127.0.0.1:8888/BookManager.html#welcomePlace:token. Le token n'est pas obligatoire. Il sera remplacé par null s'il n'est pas renseigné. De même si le nom de la place n'est pas spécifié (c'est-à-dire pas de @Prefix sur Tokenizer), il sera remplacé par le nom de la classe correspondante.

Le token est un moyen de passer une variable dans l'URL lorsqu'on se dirige vers une page. Nous l'utiliserons pour gérer la variable de session afin de vérifier si une session est encore active.

On doit redéfinir deux méthodes lorsqu'on implémente PlaceTokenizer pour la construction de l'URL :

  • une méthode getPlace(String token) qui permet de retourner la Place correspondante ;
  • une méthode getToken(WelcomePlace place) qui affiche le token qui aurait été passé à l'URL.

Il nous faut maintenant établir un lien entre chaque Activity et la Place correspondante : c'est le rôle joué par ActivityMapper. Il en sera de même pour toutes Places à créer.

II-B-6. Création de ActivityMapper

L'ActivityMapper a pour rôle d'établir le lien entre chaque Place et son Activity.
Je vous conseille de créer un package mapper dans le sous-package client.activity et d'y mettre la classe ActivityLibraryMapper.

ActivityLibraryMapper.java
Sélectionnez
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.
package com.developpez.bnguimgo.gwt.client.activity.mapper;
public class ActivityLibraryMapper implements ActivityMapper {

    private ClientFactory clientFactory;

    public ActivityLibraryMapper() {
        super();
    }
    
    public ActivityLibraryMapper(ClientFactory clientFactory) {
        super();
        this.clientFactory = clientFactory;
    }

    @Override
    public Activity getActivity(Place place) {

        if (place instanceof WelcomePlace) {
            return new WelcomeActivity(clientFactory);
        } else {
            return null;
        }
    }

    public ClientFactory getClientFactory() {
        return this.clientFactory;
    }

}

Pour chaque nouvelle Activity, nous devons l'ajouter dans la méthode : getActivity(Place place). C'est cette méthode qui fait le lien entre l'activité et la Place. Car elle retourne une activité en fonction de la Place passée en paramètre.

Il nous reste à établir aussi un lien entre l'URL et la Place : c'est le rôle de PlaceHistoryMapper. Il en sera de même pour toutes Places créées.

II-B-7. Création de PlaceHistoryMapper

La PlaceHistoryMapper permet d'établir un lien bidirectionnel entre une Place et l'URL associée. Nous allons créer une interface PlaceHistoryLibraryMapper qui étend la classe PlaceHistoryMapper. Toutes les pages de l'application doivent être mappées dans cette interface.

Je vous conseille de créer cette interface dans le même package ci-dessus où se trouve ActivityLibraryMapper.

Interface PlaceHistoryLibraryMapper.java
Sélectionnez
1.
2.
3.
4.
5.
package com.developpez.bnguimgo.gwt.client.activity.mapper;
@WithTokenizers(value = { WelcomePlace.Tokenizer.class})
public interface PlaceHistoryLibraryMapper extends PlaceHistoryMapper {

}

Grâce à l'annotation @WithTokenizers(value= { WelcomePlace.Tokenizer.class}), le lien est établi. Rappelez-vous que lors de la construction de la welcomePlace, nous lui avons associé une classe Tokenizer lui permettant de construire une URL avec un token associé. C'est cette classe qui est récupérée par annotation. Pour toute nouvelle Place, il faut juste l'ajouter dans cette annotation après une virgule.

Il nous faut maintenant modifier l'entry-point BookManager.java pour l'adapter à notre modèle MVP.

II-B-8. Modification de l'EntryPoint

Notre entry-point avait été créée lors de la génération du projet. Il se trouve dans le package client et s'appelle : BookManager. Nous voulons désormais qu'il affiche notre propre page d'accueil et non celle par défaut offerte par GWT. Je vous conseille de modifier cette classe comme ci-dessous :

L'Entry-Point est la classe de démarrage d'une application GWT et contient obligatoirement la méthode onModuleLoad() chargée d'initialiser l'application. Dans notre cas, c'est la classe BookManager.java

Entry-Point BookManager.java
Sélectionnez
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.
package com.developpez.bnguimgo.gwt.client;
public class BookManager implements EntryPoint {

    private static final Logger logger = Logger.getLogger(BookManager.class.getName());
    private Place welcomePlace = new WelcomePlace("");
    // Le widget principal d'affichage
    private SimplePanel display = new SimplePanel();
    
    @Override
    public void onModuleLoad() {
        logger.info("The EntryPoint module is loading ..............");        
        ClientFactory clientFactory = GWT.create(ClientFactory.class);
        EventBus eventBus = clientFactory.getEventBus();
//initialise la navigation vers une nouvelle Place
        PlaceController placeController = clientFactory.getPlaceController();
//On crée l'ActivityManager pour gérer l'Activity associée à la Place demandée
        ActivityMapper activityMapper = new ActivityLibraryMapper(clientFactory);
        ActivityManager activityManager = new ActivityManager(activityMapper, eventBus);
        activityManager.setDisplay(display);        
//Création de PlaceHistoryHandler afin de gérer l'historique de navigation
        PlaceHistoryLibraryMapper historyMapper = GWT.create(PlaceHistoryLibraryMapper.class);
//PlaceHistoryHandler assure le mapping bi-directionnel entre l'URL et la Place
        PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);
        historyHandler.register(placeController, eventBus, welcomePlace);

        RootPanel.get().add(display);
//Premier chargement de l'historique de navigation
        historyHandler.handleCurrentHistory();
        logger.info("The application is ready ..............");
    }  
}

Expliquons tout ça

Au démarrage de l'application, la méthode onModuleLoad() est exécutée en premier. La clientFactory est créée via le deferred binding de GWT. Cette factory met à notre disposition l'eventBus pour se préparer à la gestion des événements. La même factory nous fournit la placeController pour initialiser éventuellement la navigation vers d'autres pages. Une instance d'activityMapper est alors créée en association avec la clientFactory et transmise à l'activityManager. L'ActivityManager enregistre alors l'ActivityMapper et l'eventBus.
Une instance de placeHistoryHandler est créée pour assurer le mapping bidirectionnel entre l'URL et la Place en se basant sur l'historique de navigation fourni par PlaceHistoryMapper. L'historyHandler enregistre enfin notre page d'accueil (welcomePlace), un moyen de navigation vers d'autres pages (placeController) et de gestion des événements (eventBus). Le tout est transmis au RootPanel pour affichage et un premier chargement de l'historique est enclenché.

II-B-9. Modification de BookManager.gwt.xml

Dans ce fichier, il faut ajouter les extensions nécessaires à l'initialisation et au bon fonctionnement de l'application. Par exemple l'Activity, la Place, le Bootstrap pour profiter d'une feuille de style par défaut.

Voici les extensions nécessaires :

Déclarations à ajouter dans BookManager.gwt.xml
Sélectionnez
<inherits name="com.github.gwtbootstrap.Bootstrap"/>
    <inherits name="com.google.gwt.activity.Activity" />
    <inherits name="com.google.gwt.place.Place" />

Voici le contenu complet du fichier BookManager.gwt.xml

BookManager.gwt.xml
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='BookManager'>
  <!-- Inherit the core Web Toolkit stuff. -->
  <inherits name='com.google.gwt.user.User' />

  <!-- Inherit the default GWT style sheet.  You can change -->
  <inherits name='com.google.gwt.user.theme.standard.Standard' />
  
  <inherits name="com.github.gwtbootstrap.Bootstrap"/>
  <inherits name="com.google.gwt.activity.Activity" />
  <inherits name="com.google.gwt.place.Place" />  
  <!-- Specify the app entry point class. -->
  <entry-point class='com.developpez.bnguimgo.gwt.client.BookManager' />
  <!-- Factory nécessaire à la construction des vues -->
  <replace-with class="com.developpez.bnguimgo.gwt.client.factory.ClientFactoryImpl">
      <when-type-is class="com.developpez.bnguimgo.gwt.client.factory.ClientFactory"/>
  </replace-with>

  <!-- Specify the paths for translatable code -->
  <source path='client' />
  <source path='shared' />

</module>

II-B-10. Ajout de maven-war-plugin

Pour indiquer au serveur où se trouve le war qui sera généré, il faut ajouter et configurer le plugin maven-war-plugin dans le pom.xml.

Modifier le pom.xml et ajouter le plugin maven-war-plugin
Sélectionnez
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
        <version>2.1.1</version>
    <configuration>
        <webappDirectory>${webappDirectory}</webappDirectory>
    </configuration>
</plugin>

Dans l'élément properties ajouter la variable webappDirectory comme ci-dessous :
<webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>

Voici le contenu complet du fichier pom.xml :

Contenu du pom.xml
Sélectionnez
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.
91.
92.
93.
94.
95.
96.
<?xml version="1.0" encoding="UTF-8"?>
<project
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>com.developpez.bnguimgo</groupId>
  <artifactId>gwtSpringHibernate5</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>GWT Maven Archetype</name>

  <properties>
    <!-- Convenience property to set the GWT version -->
    <gwtVersion>2.7.0</gwtVersion>

    <!-- GWT needs at least java 1.6 -->
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.gwt</groupId>
        <artifactId>gwt</artifactId>
        <version>${gwtVersion}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-servlet</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-user</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-dev</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
    <groupId>com.github.gwtbootstrap</groupId>
    <artifactId>gwt-bootstrap</artifactId>
    <version>2.3.2.0</version>        
    </dependency>    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <!-- Output classes directly into the webapp, so that IDEs and "mvn process-classes" update them in DevMode -->
    <outputDirectory>${webappDirectory}/WEB-INF/classes</outputDirectory>
    
    <plugins>

      <!-- GWT Maven Plugin -->
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>gwt-maven-plugin</artifactId>
        <version>2.7.0</version>
        <configuration>
          <runTarget>BookManager.html</runTarget>
          <modules>
            <module>com.developpez.bnguimgo.gwt.BookManager</module>
          </modules>
        </configuration>
      </plugin>      
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
            <version>2.1.1</version>
        <configuration>
            <webappDirectory>${webappDirectory}</webappDirectory>
        </configuration>
    </plugin>                  
    </plugins>
  </build>

</project>

II-B-11. Modification du build

Nous devons modifier le build pour indiquer au superDevMode où se trouve désormais le war après le build. Je vous propose d'ajouter ceci comme Arguments dans votre configuration du Run d'Eclipse :

-war C:\workspace\gwtSpringHibernate\target\gwtSpringHibernate-1.0.0-SNAPSHOT
-remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -startupUrl BookManager.html
-logLevel INFO -codeServerPort 9997 -port 8888 com.developpez.bnguimgo.gwt.BookManager

Configuration du build
Configuration du build

II-B-12. Exécution

Après avoir configuré les arguments comme indiqué ci-dessus, lancer le build de l'application :
Clic droit sur le projet => Run As => Maven Install (vérifier que le build est OK).

Exécution

Clic droit sur le projet => Run As => Web Application

Vous obtenez après le build cette URL : http://127.0.0.1:8888/BookManager.html

Le résultat sur un navigateur est le suivant :

WelcomeView
Page d'accueil sans feuille de style

Nous pouvons dire que notre design MVP est complet. Mais avant d'aller plus loin, nous pouvons encore faire mieux en séparant la vue de sa présentation. Cela nous évitera de construire directement les composants sur la vue comme nous venons de faire. C'est dans ce contexte que Uibinder est nécessaire.

II-B-13. Intégration UiBinder et style CSS

II-B-13-a. Intégration de l'UiBinder

UiBinder est un procédé fourni par GWT pour dessiner les interfaces graphiques à travers un fichier xml. L'intérêt est de séparer la vue de sa présentation. Le nommage du fichier Uibinder en GWT pour une vue suit une certaine logique qui est la suivante : nomDeLaVueImpl.ui.xml. Le nom du fichier Uibinder associé à la vue WelcomeViewImpl sera donc : WelcomeViewImpl.ui.xml. Un fichier ui.xml doit respecter la charte W3CWorld Wide Web Consortium pour la validation du CSS. Voici la structure de la page WelcomeViewImpl.ui.xml.

Fichier de présentation de la vue WelcomeViewImpl.ui.xml
Sélectionnez
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.
<!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:b='urn:import:com.github.gwtbootstrap.client.ui'>

    <ui:style>
    .gwt-PasswordTextBox {
        color: black;
        background-color: yellow;
    }
    </ui:style>

    <g:HTMLPanel>
        <div class="container">
            <div class="content">
                <h3>Créer votre compte ou connectez-vous</h3>
                <p>
                    Votre compte utilisateur vous permettra d'accéder à vos services
                    en
                    ligne.
                </p>
                <br />
                <g:Anchor ui:field="createCompte" addStyleNames="btn btn-primary btn-left">
                    Création
                    compte
                </g:Anchor>

            </div>
            <div class="content">
                <h3>Connexion à votre compte</h3>
                <g:VerticalPanel>
                    <b:Label ui:field="connexionStatus" />
                    <b:Label ui:field="emailError" />
                    <b:Label ui:field="passwordError" />
                    <g:HorizontalPanel ui:field="emailHorizontalPanel"
                        title="click to enter email" verticalAlignment="middle" height="40px">
                        <b:Label text="Mail :" width="60px" height="20px"
                            styleName="control-label colorH3" />
                        <b:TextBox ui:field="emailTextBox" height="20px"></b:TextBox>
                    </g:HorizontalPanel>
                    <g:HorizontalPanel ui:field="passwordHorizontalPanel"
                        title="click to enter password" verticalAlignment="middle" height="40px">
                        <b:Label text="Pass :" width="60px" height="20px"
                            styleName="control-label colorH3" />
                        <b:PasswordTextBox ui:field="passwordTextBox"
                            height="20px" styleName="{style.gwt-PasswordTextBox}"></b:PasswordTextBox>
                    </g:HorizontalPanel>
                    <g:HorizontalPanel ui:field="buttonHorizontalPanel">
                        <b:Button ui:field="connexionButton" text="Connexion"
                            styleName="btn btn-primary btn-right" />
                    </g:HorizontalPanel>

                </g:VerticalPanel>
            </div>
            <div class="content">
                <p>
                    Tous droits réservés, copyright bnguimgo @ yahoo fr.
                </p>
            </div>
        </div>
    </g:HTMLPanel>

</ui:UiBinder>

Quelques explications :

  • La déclaration xmlns:ui='urn:ui:com.google.gwt.uibinder' permet dans la suite d'intégrer une feuille de style interne ou externe, des images, des messages, etc.
  • La déclaration de xmlns:g='urn:import:com.google.gwt.user.client.ui' permet d'intégrer les composants propres à GWT et sa feuille de style.
  • La déclaration de xmlns:b='urn:import:com.github.gwtbootstrap.client.ui' permet d'intégrer les composants du Bootstrap et sa feuille de style.
  • Les styles tels que container, content, btn, btn-primary, btn-left, etc. nous renvoient à la feuille de style BookManager.css ou au style bootstrap.css. Voir leurs intégrations un peu plus bas.

L'attribut ui:field n'est pas obligatoire. Mais tout composant susceptible d'être manipulé dans la vue doit avoir un attribut ui:field="nomDuChamp".

Pour intégrer un composant personnalisé, il faut suivre la même déclaration que ci-dessus. Nous verrons des exemples un peu plus loin.

Il faut maintenant modifier la vue pour prendre en compte le Template UiBinder correspondant à sa présentation. On va donc retirer tous les composants précédemment intégrés à la vue.

II-B-13-b. Modification de la vue

Voici notre nouvelle vue en association avec sa présentation fournie par l'interface interne WelcomeViewImplUiBinder :

Nouvelle page d'accueil séparée de sa présentation WelcomeViewImpl.java
Sélectionnez
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.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
package com.developpez.bnguimgo.gwt.client.view.impl;

public class WelcomeViewImpl extends Composite implements IWelcomeView {

    interface WelcomeViewImplUiBinder extends UiBinder<Widget, WelcomeViewImpl> {
    }
    private static WelcomeViewImplUiBinder welcomeViewUiBinder = GWT.create(WelcomeViewImplUiBinder.class);
    
    private Presenter presenter;
    @UiField
    TextBox emailTextBox;
    @UiField
    Label emailError;
    @UiField
    PasswordTextBox passwordTextBox;
    @UiField
    Label passwordError;
    final DialogBox dialogBox = new DialogBox();
    @UiField
    Button connexionButton;
    @UiField
    Label connexionStatus;
    
    public WelcomeViewImpl() {        
        initWidget(welcomeViewUiBinder.createAndBindUi(this));
        initConnexionButtonClickhandle();
    }

    private void initConnexionButtonClickhandle(){
        passwordTextBox.addKeyDownHandler(new KeyDownHandler(){

            @Override
            public void onKeyDown(KeyDownEvent event) {
                if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
                    connexionButton.click();
                }                
            }            
        });
    }
    
    @Override
    public void setPresenter(Presenter presenter) {
        this.presenter = presenter;
    }
    
    @UiHandler(value = { "connexionButton" })
    public void onConnexionButtonClick(ClickEvent event){
        findUserByEmail();
    }

    private void findUserByEmail() {
        if(validateField()){
                presenter.findUserByEmail(emailTextBox.getValue(), passwordTextBox.getValue());
        }
    }
    /**
     * Valide instantanément la saisie de emailTextBox 
     * @param event
     */
    @UiHandler("emailTextBox")
       void handleTitleChange(ValueChangeEvent<String> event) {

            connexionStatus.setText("");
            connexionStatus.setVisible(false);
        
          if (Validator.isBlank(event.getValue())) {
              emailError.setVisible(true);
              emailError.setText("L'email est obligatoire");
              emailError.setStyleName("errorMessage");
          } else {
              emailError.setText("");
              emailError.setVisible(false);
          }
       }

    /**
     * Valide instantanément la saisie du champ passwordTextBox 
     * @param event
     */
       @UiHandler("passwordTextBox")
       void handlePasswordChangeEvent(ValueChangeEvent<String> event) {

            connexionStatus.setText("");
            connexionStatus.setVisible(false);
                
          if (Validator.isBlank(event.getValue())) {
              passwordError.setVisible(true);
              passwordError.setText("Le mot de passe est obligatoire");
              passwordError.setStyleName("errorMessage");
          } else {
              passwordError.setText("");
              passwordError.setVisible(false);
          }
       }
       /**
        * valide la saisie emailTextBox et passwordTextBox
        * @return
        */
       private boolean validateField(){
           boolean invalideFields = true;
           initField();
              if (Validator.isBlank(emailTextBox.getValue())) {
                  invalideFields = false;
                  emailError.setVisible(true);
                  emailError.setText("L'email est obligatoire");
                  emailError.setStyleName("errorMessage");
              } 
              if (Validator.isBlank(passwordTextBox.getValue())) {
                  invalideFields = false;
                  passwordError.setVisible(true);
                  passwordError.setText("Le mot de passe est obligatoire");
                  passwordError.setStyleName("errorMessage");
              } 
            return invalideFields;
       }
       
       private void initField(){
              emailError.setText("");
              emailError.setVisible(false);           
              passwordError.setText("");
              passwordError.setVisible(false);
              connexionStatus.setText("");
              connexionStatus.setVisible(false);
                          
       }
       
        @UiHandler("createCompte")
        public void onCreateCompteClick(ClickEvent event) {
            presenter.goToCreateComptePlace("CREATECOMPTE");
        }

        @Override
        public void showError(String textError) {
            connexionStatus.setVisible(true);
            connexionStatus.setText(textError);
            connexionStatus.setStyleName("errorMessage");
        }
        
        @Override
        public void showSucces(String message) {
            connexionStatus.setVisible(true);
            connexionStatus.setText(message);
            connexionStatus.setStyleName("succesMessage");            
        }       
}

Nous avons ajouté quelques contrôles sur les champs de saisies.

Voici une classe de validation des champs vides à jouter dans le package utils de client :

Validateur des champs de saisies Validator.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
package com.developpez.bnguimgo.gwt.client.utils;

public class Validator {
    
public static boolean isBlank(String value) {
 return (value == null || "".equals(value.trim()));
 }
}

Toute annotation @UiField ou @UiHandler("nomDuComposant") oblige le compilateur à vérifier la présence du composant concerné dans le fichier .ui.xml correspondant à l'interface en cours.

Les différents composants de la vue qui se trouvent désormais dans le fichier WelcomeViewImpl.ui.xml sont injectés dans la vue grâce à l'annotation @UiField. GWT fournit également un moyen de gestion des événements grâce à l'annotation @UiHandler("nomDuComposant"). Chaque vue récupère sa présentation à travers une interface interne qui étend UiBinder.

UiBinder nous a permis de séparer la vue de sa présentation. Mais UiBinder ne remplace pas le style CSS. Cependant, Uibinder intègre parfaitement les styles associés à chaque composant de la vue.

II-B-13-c. Intégration feuille de style CSS
 

Il existe quatre possibilités d'intégrer une feuille de style dans une application GWT :

  1. Dans la page hôte ou page d'accueil html grâce à l'élément link. Exemple : dans notre cas c'est dans le fichier BookManager.html.
  2. Dans le fichier de module .gwt.xml. Exemple : <stylesheet src='stylesheet.css' />
  3. En utilisant un conteneur CssResource dans un ClientBundle (Nous verrons un cas pratique).
  4. En utilisant une feuille de style interne. Nous avons un exemple de feuille de style interne ci-dessous dans WelcomeViewImpl.ui.xml :
Feuille de style interne
Sélectionnez
<ui:style>
    .gwt-PasswordTextBox {
        color: black;
        background-color: yellow;
    }
</ui:style>

Intégration d'une feuille de style en utilisant la CssResource.

Nous allons surcharger une feuille de style bootstrap téléchargée sur le web. Nous allons la modifier et l'intégrer à l'application comme une ressource CSS. Voici le fichier CSS (bootstrap.css) déjà modifié à téléchargerFichier bootstrap.

Je vous propose de créer un package css dans le package client de gwt. Ajouter l'interface CssInjector.java comme ci-dessous :

Interface d'intégration du CSS
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
package com.developpez.bnguimgo.gwt.client.css;

public interface CssInjector extends ClientBundle {

      public static final CssInjector INSTANCE = GWT.create(CssInjector.class); 

      @Source("bootstrap.css")
      @CssResource.NotStrict
      CssResource css();
}

Ajouter le fichier bootstrap.css précédemment téléchargé dans le même package que l'interface CssInjector.java.

L'intégration du fichier bootstrap.css se fait lors du démarrage de l'application dans l'entry-point de la classe BookManager.java avec cette ligne de code (à intégrer avant RootPanel.get().add(display)) :

Intégration d'une ressource CSS
Sélectionnez
//Préchargement des ressources CSS (optionnel)
CssInjector.INSTANCE.css().ensureInjected();

Lancer le build de l'application puis exécuter, et vous obtenez la page d'accueil avec le nouveau look.

Page d'accueil avec style
Page d'accueil Welcomview avec feuille de style

II-B-14. Résumé des étapes du design MVP

II-B-14-a. Étapes

Étapes de mise en place du patron de conception MVP

  1. Création et implémentation de la Vue
  2. Création et implémentation de la ClientFactory
  3. Création des Activities
  4. Création des Places
  5. Création de l'ActivityMapper
  6. Création de la PlaceHistoryMapper
  7. Déclaration de la ClientFactory dans le fichier .gwt.xml
  8. Création ou mise à jour de l'EntryPoint
II-B-14-b. Structure du projet créé

Voici l'organisation des packages que nous avons créés :

Structure packages avec pattern MVP
Structure packages avec pattern MVP intégré

Cette première partie nous a appris qu'un projet GWT est organisé autour de trois packages : le package client, server et shared. Nous avons aussi appris à mettre en place le patron de conception MVP et séparer la vue de sa présentation grâce à l'UiBinder.

Cependant, notre application est encore statique, elle ne fait rien pour le moment, sauf l'affichage de la page d'accueil. Pour la rendre un peu dynamique, nous allons lui ajouter des services pour faire quelques traitements, par exemple afficher la page d'administration en cas de clic sur le bouton CONNEXION après saisie de login et mot de passe. C'est l'objectif de la deuxième partie du tutoriel.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2017 Nguimgo Bertrand. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.