Accueil Nos publications Blog Développement GWT – MVP

Développement GWT – MVP

La conception et le développement des grandes applications web a sa part de difficulté, et pour GWT, il n’y a pas d’exception. Plusieurs développeurs travaillent sur le même code, d’où la nécessité de maintenir une cohérence et d’assurer la pérennité des fonctionnalités de l’application. Il existe plusieurs designs patterns à choisir : Presentation-Abstraction-Control (PAC), Model-View-Controller (MVC), Model-View-Presenter (MVP), etc. … Chacun a ses bénéfices, l’architecture du MVP fonctionne mieux avec GWT pour deux principales raisons :

  1. Le pattern permet de découpler les développements ce qui permet à plusieurs développeurs de travailler simultanément.
  2. Il est plus adapté aux tests coté IHM. Il permet de minimiser l’utilisation de GwtTestCase qui nécessite un navigateur.

Le cœur du pattern est la séparation des fonctionnalités en composants homogènes qui ont un sens, dans GWT, l’intérêt est de mettre la vue (View) la plus simple possible (passive) afin de minimiser les tests sur le navigateur et par conséquent de passer moins de temps à exécuter les tests.

Les trois concepts du MVP sont :

  •  Le modèle, se sont les données de l’application, souvent issues d’une source de données (ex : base de données), il est destiné à être manipulé par l’UI. Il n’a pas d’adhérence avec les autres classes MVP, il est isolé.
  •  La vue, la partie de la page web affichée dans l’interface utilisateur, qui se compose des widgets et des conteneurs de widgets.
  •  Le présenter représente la logique métier du modèle, et rend complètement étanche les échanges entre la vue et le modèle.

Une fois les fondements et les bases du pattern assimilés, la conception et le développement des applications GWT sont beaucoup plus faciles. Pour illustrer ces concepts, on va mettre en place une application web qui va permettre à l’utilisateur de saisir quelques informations dans un formulaire et afficher ces informations sur un autre écran lors de la validation.
Le code du projet est dans GitHub :
https://github.com/RaabKader/soat.git [Projet : gwt-mvp]

Pour commencer, on structure l’application en plusieurs composants :

  • Model
  • View
  • Presenter
  • AppController
  • ClientFactory

Par la suite, on examinera l’interaction entre ces composants :

  • Relier (Binding) les presenters et les vues
  • Évents & Event bus.
  • Gestion de l’historique et des transitions entre les vues
  • Injection de dépendances

Model

Le modèle englobe les objets métiers. Dans notre application, les coordonnées d’une personne seront modélisées de la façon suivante :

  1. Person : La représentation d’une personne. Pour être claire, cet objet contient une civilité, nom, prénom et email (Bien entendu, dans des applications plus complexes, ces objets contiendront plusieurs propriétés).

View

La vue va contenir tous les composants (Widgets) qui vont construire l’application. Elle inclut les labels, boutons, textbox, etc. La vue est responsable de l’affichage des composants et n’a aucune connaissance du modèle. La navigation entre les vues est gérée par le module de gestion de l’historique au sein de la couche de présentation (AppController). La vue est constituée de :

  1. FormPersonView : Permet d’afficher le formulaire de saisie des coordonnées d’une personne.
  2. DetailPersonView : Afficher le détail de la personne, et pouvoir revenir au formulaire.

Presenter

En général, pour chaque vue va correspondre un présenter pour gérer la vue et les événements provenant des widgets de la vue correspondante. Pour notre exemple, nous avons :

  1. FormPersonPresenter : Validation de la saisie des coordonnées de la personne.
  2. DetailPersonPresenter :  Afficher les coordonnées précédemment saisies, et pouvoir revenir au formulaire.

AppController

Pour gérer la logique qui n’est pas spécifique à n’importe quel Presenter et réside plutôt dans la couche application, nous allons introduire le composant AppController. Il contient la gestion de l’historique et la logique de transition entre les vues, voire la transition, est directement liée à la gestion de l’historique et est examinée plus en détail ci-dessous.

Voici un aperçu de l’architecture globale de l’application :

Grâce à notre structure de composants en place, nous allons jeter un coup d’œil  sur le processus de lancement de l’application. Le déroulement général est illustré dans le code suivant (GwtMvp.java) :

  1. Appel de onModuleLoad(), méthode d’entrée dans l’application GWT.
  2. Création de la ClientFactory, AppController.
  3. Passer l’instance du RootPanel à AppController qui va prendre la main sur l’application


public class GwtMvp implements EntryPoint {

 /**
* Point d'entrée de l'application
*/
public void onModuleLoad() {
   ClientFactory clientFactory = GWT.create(ClientFactory.class);
   AppController appController = new AppController(clientFactory);
   appController.go(RootPanel.get());
   }
 }

Binding presenters et views

Avant de relier un Presenter à la vue associée, on examinera en détail l’interface définie dans le Presenter.  Prenons l’exemple de FormPersonPresenter :



public class FormPersonPresenter implements Presenter{
 // interface passive
 public interface IFormPersonView {
void setPrenseter(FormPersonPresenter presenter);
Widget asWidget();
}
// Other code
}

L’interface IFormPersonView est passive, elle sera implémentée par la Vue FormPersonView. Cette vue contient plusieurs widgets : Label, ListBox, TextBox, Button, etc, pour afficher le formulaire de saisie des coordonnées de la personne :



public class FormPersonView extends Composite implements FormPersonPresenter.IFormPersonView {

// Other code

@UiField(provided = true)
 ListBox civilite;

@UiField
 TextBox nom;

@UiField
 TextBox prenom;

@UiField
 TextBox email;

@UiField
Button saveButton

// Other code

@UiHandler("saveBtn")
void onClick(ClickEvent e) {
if (checkPerson()) {
this.presenter.doEnregistrerPerson(getPerson());
} else {
Window.alert("Veuillez renseigner les champs obligatoires");
}
}
// other code
}

La conception avec le pattern MVP nous permet de changer assez facilement la vue concrète (Widgets) sans aucun impact, à condition de maintenir l’interface dans le Presenter.

Évents et EventBus

Une fois les Presenters mis en place pour recevoir les événements des widgets à travers les vues, nous souhaiterons mettre en place des actions pour gérer ces événements. Pour cela, on va relayer ces événements à l’EventBus qui est conçu pour l’application indépendamment des autres composants. L’EventBus est un mécanisme pour

  1.  Passer des événements
  2.  Enregistrer ces événements pour qu’ils soient notifiés.

Il est très important de comprendre que tous les événements ne doivent pas être placés dans l’Event Bus. Les événements à l’échelle de l’application sont réellement les seuls à être placés dans l’Event Bus. Les événements de type « Click sur la touche entrée, Appel RPC serveur …» sont inintéressants à relayer dans l’Event Bus, contrairement aux événements de type « Éditer une personne, Retour au formulaire ».
Voici l’événement défini dans notre application :

  • EditPersonEvent

Chacun de ces événements doit étendre GwtEvent et redéfinir dispatch() et getAssociatedType(). La méthode dispatch() prend un seul paramètre de type EventHandler, et pour notre exemple, nous avons défini une interface handler pour notre événement :

  • EditPersonEventHandler

Pour montrer la relation entre ces composants, nous allons décrire en détail l’édition de la personne. Premièrement, on va enregistrer l’événement EditPersonEvent dans AppController. Pour ce faire, on fait appel à eventBus.addHandler(..) en lui passant en paramètre le GwtEvent.TYPE et le handler approprié qui sera appelé quand l’événement sera déclenché :



public class AppController implements ValueChangeHandler {
// Other code
eventBus.addHandler(EditPersonEvent.TYPE, new EditPersonEventHandler(){
@Override
public void onEditPersonn(EditPersonEvent event) {
person = event.getPerson();
doEditPerson(person);
}
});
 // Other code
}

On a enregistré un nouveau handler EditPersonEventHandler. Ce dernier va appeler la méthode doEditPerson(Person person) à chaque fois que l’événement EditPersonEvent est déclenché. Plusieurs composants peuvent écouter un seul événement, le Event bus va regarder quel composant a ajouté un handler pour l’événement getAssociatedType() et appeler event.dispatch() en passant cette interface handler en paramètre .

Historique et transitions entre les vues

Une grande partie d’une application web consiste à gérer des événements relatifs à l’historique. Ces événements sont assimilés à des tokens (String) qui représentent un état dans l’application, c’est comme des « marqueurs » pour nous situer dans l’application. Par exemple, l’utilisateur va naviguer depuis le formulaire de saisie des coordonnées (Clic sur valider) vers la page de détail et clique par la suite sur le bouton Retour au formulaire ou sur le bouton Back du navigateur, le résultat de l’action doit être le formulaire. Lors de l’affichage du formulaire, le token “form” est empilé dans la stack History, et lors de l’édition des informations de la personne, le token “edit” est empilé dans la stack History. Lors du click sur le bouton Back, le token “edit” est dépilé, et le token “form” se trouve à la tête de la stack, la page de saisie des coordonnées de la personne est affichée dans ce cas là :



public class AppController implements Presenter, ValueChangeHandler<String> {

// Other code
private void doEditPerson(Person person) {
History.newItem("edit", false);
Presenter presenter = new DetailPersonPresenter(eventBus, clientFactory.getDetailPersonView(), person);
presenter.go(container);
}
 // Other code
 }

 

ClientFactory

Elle permet de construire et récupérer les vues (singletons), Event bus et AppController. C’est le même principe qu’un Framework d’injection de dépendances tel que GIN, qu’on aura l’occasion de voir prochainement dans un autre tutoriel.

Ressources

[1] https://developers.google.com/web-toolkit/articles/mvp-architecture