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 :
- 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 des 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-1. 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 de 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 prise 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 : Un 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és 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ée) comparativement à un mode de communication synchrone où les échanges d'informations sont directes.
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. A 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. A 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ée 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 design pattern GWT-MVP▲
Le design pattern 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.
- 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 lequel 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.
Pour illustrer l'implémentation de ce design, nous allons développer la page d'accueil WelcomeView.
- 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 :
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
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
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 que 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 inclue dans l'interface IWelcomeView, ce qui permet à la vue de s'assurer qu'il 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.
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.
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 reutilisables. 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 dependance comme GIN pour obtenir une référence des objets comme l'EventBus.
Voici notre interface ClientFactory :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
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éhodes 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
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.
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 indiguer à 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 :
<
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'activiy (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) fournit 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
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.
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 clic d'abord.
Question : Comment allons-nous afficher alors la page d'accueil si on a aucun bouton ? Nous pourrions utiliser la méthode goTo(welcomePlace)
fournit 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
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.
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é le nom de la classe correspondante
Le token est 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.
- 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
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.
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
2.
3.
4.
5.
6.
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éé 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 offert 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
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;
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 transmis à l'activityManager.
L'ActivityManager enregistre alors l'ActivityMapper et l'eventBus.
Une instance de placeHistoryHandler est créée pour assurer le mapping bi-directionnel entre l'URL et la Place en se basant
sur l'historique de navigation fournit 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 :
<
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
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
<?
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.
<
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
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.
<?
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
II-B-12. Exécution▲
Après avoir configuré les arguments comme indiqués 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:
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-1. 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ée à 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.
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.
<!
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
>
- 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-2. Modification de la vue▲
Voici notre nouvelle vue en association avec sa présentation fournie par l'interface interne WelcomeViewImplUiBinder :
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.
146.
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
2.
3.
4.
5.
6.
7.
8.
9.
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 encours
Les différents composants de la vue qui se trouvent désormais dans le fichier WelcomeViewImpl.ui.xml sont injectés dans le vue grâce à l'annotation @UiField.
GWT fourni é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-3. Intégration feuille de style CSS▲
- 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: <link type="text/css" rel="stylesheet" href="BookManager.css">
- Dans le fichier de module .gwt.xml. Exemple : <stylesheet src='bootstrap.css'/>
- En utilisant un conteneur CssResource dans un ClientBundle (Nous verrons un cas pratique)
- En utilisant une feuille de style interne grâce à l'élément <ui:style>. Nous avons un exemple de style interne ci-dessous dans WelcomeViewImpl.ui.xml:
<
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
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)):
//
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.
II-B-14. Résumé des étapes du design MVP▲
II-B-14-1. Etapes▲
- Création et implémentation de la Vue
- Création et implémentation de la ClientFactory
- Création des Activities
- Création des Places
- Création de l'ActivityMapper
- Création de la PlaceHistoryMapper
- Déclaration de la ClientFactory dans le fichier .gwt.xml
- Création ou mise à jour de l'EntryPoint
II-B-14-2. Structure du projet crée▲
Voici l'organisation des packages que nous avons crées :
Cette première partie nous a appris qu'un projet GWT est organisé autour de 3 packages : le package client, server et shared. Nous avons aussi appris à mettre en place le design pattern 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.