Introduction à GWT - Partie 4 - Place à un peu de code

Leave a Comment
Article précédent: Anatomie d'un projet GWT.
Dans ce chapitre nous allons regarder le code de notre application HelloGWT puis nous ferons quelques petites modifications pour vous familiariser avec les différentes parties d’une application GWT.

La page hôte d'une application GWT

Une application GWT est constituée d'une unique page HTML appelée page hôte. Le contenu de cette page est en permanence effacé et redessiné selon les sollicitations de l'utilisateur et suivant une logique propre à l'application. Voici la page hôte de notre application HelloGWT.



On a là une simple page à la différence qu'elle ne contient pas grand chose: un tag h1, un message d'invitation dans une cellule de tableau, deux éléments id, l'un pour contenir dynamique un champ de texte et l'autre le bouton. Cet aspect dépouillé de la page hôte peut vous surprendre mais la raison est que le code d'une application GWT est essentiellement du Javascript. Vous écrivez votre application en Java, le compilateur de GWT le transforme en code Javascript exécuté par le navigateur. Toute l'interface graphique dans une application GWT peut être créée dynamiquement comme c'est le cas pour le champ de texte et le bouton Send dans la version actuelle de notre application Hello GWT. Vous pouvez toujours ajouter du contenu statique à cette page si vous le souhaitez.




La page hôte inclut le fichier de style CSS (situé dans le dossier war du projet) grâce à la ligne suivante: 


Le démarrage de l'application GWT et le fichier nocache.js


On arrive à la ligne d'après qui est d'une importance capitale car elle permet d'aborder l'un des aspects les plus importants de GWT à mon sens, le deferred binding. La page hôte inclut un fichier Javascript hellogwt.nocache.js.


Le navigateur charge hellogwt.nocache.js qui contient le code nécessaire à la détermination de la configuration de l'utilisateur (langue, navigateur utilisé etc.). Puis il charge le code correspondant à cette configuration. Ainsi l'utilisateur ne va charger que le code qu'il va réellement exécuter. Un utilisateur ayant Firefox ne va pas charger le code applicatif correspondant à Internet Explorer. "On ne paye que pour ce qu'on utilise"! Comme son nom l'indique le fichier nocache n'est pas en mis en cache pour éviter de manquer les changements apportés à l'application. Par contre le code réellement exécuté peut être caché.

Le point d'entrée de l'application
Nous n'allons pas détailler l'EntryPoint généré par GWT. Au lieu de cela nous allons écrire quelque chose de beaucoup plus simple, une "task list" comme sur l'image suivante. 
L'interface est constituée d'un titre, d'un champ de texte, d'un bouton et d'un conteneur pour la liste des tâches. Il est possible d'enlever une tâche grâce au lien "Remove". Pour créer une tâche on entre son nom dans le champ de texte et on clique sur le bouton "Create task".

Pour commencer voici le contenu de la page hôte dont on va partir:

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="HelloGWT.css">
    <title>Hello GWT Application</title>    
    <script type="text/javascript" language="javascript" src="hellogwt/hellogwt.nocache.js"></script>
  </head>

  <body>
    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
    
    <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>

  </body>
</html>

Commencez par ajouter les deux lignes suivantes au corps de la page hôte: 

<h2>Hello GWT Application</h2>
<div id="content"></div>

La première ligne constitue le titre de notre application et la seconde est un emplacement qui va accueillir le contenu dynamique créé.
A ce stade l'exécution de notre application donne:


Maintenant nous allons ajouter le champ de texte et le bouton de création de la tâche.

Le monde des widgets GWT

GWT dispose d'un nombre important de classes dédiées aux interfaces graphiques et appelées widgets dans le jargon de GWT. Ces classes se trouvent dans le package com.google.gwt.user.client.ui. Si vous vous demandez toujours ce que peut être un widget c'est par exemple un bouton, un label, un champ de texte, un panneau pouvant contenir d'autres widgets etc. Vous devez, quand vous faites le design de votre application, décider du choix des widgets que vous allez utiliser. Faites un tour ici si vous souhaitez voir, sans tarder, les widgets en grandeur nature.

Construction de l'UI de notre application

Pour placer les éléments de notre interface graphique nous utilisons un conteneur spécial, un VerticalPanel, qui place ses fils (éléments contenus) verticalement, de haut en bas. Nous instancions pour cela la classe com.google.gwt.user.client.ui.VerticalPanel

VerticalPanel container = new VerticalPanel();

La prochaine étape consiste à créer le panneau de création d'une nouvelle tâche. La figure suivante donne un aperçu avec le nom des widgets utilisés.




Le widget GWT pour récupérer du texte court est com.google.gwt.user.client.ui.TextBox et celui pour créer un bouton est com.google.gwt.client.ui.Button. Nous plaçons ces deux widgets dans un panel, com.google.gwt.client.ui.HorizontalPanel, qui place ces éléments fils horizontalement de gauche à droite. 

TextBox taskNameBox = new TextBox();
Button createButton = new Button("Create task");
HorizontalPanel taskCreationPanel = new HorizontalPanel();  
  
taskCreationPanel.add(taskNameBox);
taskCreationPanel.add(createButton);

Comme illustré par le code précédent pour ajouter un élément au panneau horizontal (HorizontalPanel) nous invoquons la méthode add()
Ajoutons le panneau horizontal au conteneur principal créé précédemment.
container.add(taskCreationPanel);
A ce stade le code de l'Entry Point est :
package com.nouhoum.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Entry point classes define onModuleLoad().
 */
public class HelloGWT implements EntryPoint {
 private VerticalPanel container = new VerticalPanel();

 /**
  * This is the entry point method.
  */
 public void onModuleLoad() {
  TextBox taskNameBox = new TextBox();
  Button createButton = new Button("Create task");
  HorizontalPanel taskCreationPanel = new HorizontalPanel();

  taskCreationPanel.add(taskNameBox);
  taskCreationPanel.add(createButton);

  container.add(taskCreationPanel);
 }
}
Mettez à jour le code de l'application avant de l'exécuter. Voyez-vous un changement à l'interface graphique? En principe vous ne devriez observer aucun changement.
Maintenant ajoutons la ligne suivante et puis exécutons l'application. 
RootPanel.get("content").add(container);

Que s'est-il passé? Avant d'avoir ajouté cette ligne le conteneur n'était pas ajouté à la page hôte. Nous avons fait usage d'un widget particulier, le RootPanel, auquel nos widgets doivent être directement ou indirectement ajoutés pour être visibles. 


Note: Les clics sur le bouton "Create Task" ne font rien. Nous verrons dans la suite comment faire réagir le bouton aux évènements de l'utilisateur. Comme vous le verrez plus tard la programmation évènementielle occupe une place importante en GWT.

On avance, on avance! Il ne reste plus que l'affichage de la liste pour finir avec la partie purement graphique.


Cette UI (User Interface) est constituée de widgets: un label pour le texte "Task List" et un tableau pour la liste proprement dite. 
Il existe dans la librairie de GWT un widget (com.google.gwt.user.client.ui.Label) que nous allons utiliser pour afficher le texte. Voici comment ça marche:
Label title = new Label("Task List");
L'affichage de la liste ressemble à un tableau avec deux colonnes et un nombre variable de lignes. GWT met à notre disposition deux widgets pour réaliser des tableaux:

  • com.google.gwt.user.client.ui.Grid: Ce widget est un tableau rectangulaire qui peut contenir du texte, du code HTML ou d'autres widgets. Sa particularité est que ses dimensions sont fournies lors de sa création et son redimensionnement se fait de façon explicite (traduction vous le faites vous-même en invoquant une méthode de la classe Grid).
  • com.google.gwt.user.client.ui.Flextable: C'est un tableau comme le widget Grid mais plus flexible. Avec ce widget les cellules peuvent être créées à la demande, le nombre de cellules pouvant varier par ligne.

Nous choisissons d'afficher la liste de tâches dans un Flextable. Nous en profitons pour ajouter un label qui affiche "Task List". Voici le point d'entrée après ces ajouts:
package com.nouhoum.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Entry point classes define onModuleLoad().
 */
public class HelloGWT implements EntryPoint {
 private VerticalPanel container = new VerticalPanel();
 private FlexTable taskContainer = new FlexTable();

 /**
  * This is the entry point method.
  */
 public void onModuleLoad() {
  TextBox taskNameBox = new TextBox();
  Button createButton = new Button("Create task");
  HorizontalPanel taskCreationPanel = new HorizontalPanel();

  taskCreationPanel.add(taskNameBox);
  taskCreationPanel.add(createButton);
  
  container.add(taskCreationPanel);
  Label title = new Label("Task List");
  container.add(title);
  container.add(taskContainer);
  RootPanel.get("content").add(container);
 }
}

Notre super application ressemble à ça à ce niveau:



Comment ajouter une tâche?
Il nous reste à ajouter la logique de la création et de l'affichage d'une tâche. Comme vous l'avez certainement constaté rien ne se passe lorsqu'on clique sur le bouton "Create task". Ceci est dû au fait que nous sommes indifférents à l'évènement clic sur le bouton. Nous devons détecter les clics sur ce bouton et créer si nécessaire une tâche avec le nom fourni dans le champ texte. Dans le jargon de GWT nous nous mettons à l'écoute de l'évènement "clic sur le bouton Create task". 
Brève présentation de la programmation évènementielle en GWT


La gestion des évènements (clic, clavier etc.) est très importante dans une application GWT car elle définit comment les utilisateurs interagissent avec votre application. La problématique est gérée en suivant le design pattern observer. Comment marche ce design pattern? En résumé il est constitué de deux types d'acteurs: un sujet (observé) et des observateurs. Les observateurs, intéressés par un évènement donné, s'enregistrent auprès du sujet (observé) qui les notifie dès que cet évènement arrive. Clair? Pas clair?


Passons maintenant à la pratique en appliquant ce qu'on vient d'apprendre à notre problème. Notre sujet (observé) est tout trouvé! C'est le bouton "Create task". Il doit permettre aux observateurs de s'enregistrer auprès de lui. Ceci est possible grâce à la méthode addClickHandler() du widget Button. En réalité cette méthode provient d'une interface com.google.gwt.event.dom.client.HasClicHandlers qui est implémentée par tous les widgets qui souhaitent être notificateurs d'évènements clic.  Voici son code source:

package com.google.gwt.event.dom.client;

import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;

/**
 * A widget that implements this interface provides registration for
 * {@link ClickHandler} instances.
 */
public interface HasClickHandlers extends HasHandlers {
  /**
   * Adds a {@link ClickEvent} handler.
   * 
   * @param handler the click handler
   * @return {@link HandlerRegistration} used to remove this handler
   */
  HandlerRegistration addClickHandler(ClickHandler handler);
}

La méthode addClickHandler() prend en argument un objet ClickHandler qui représente un observateur. Mettons cela en pratique en implémentant ceci:
  1. Se mettre à l'écoute du clic du bouton "Create task"
  2. Récupérer le contenu du champ de texte
  3. Créer et afficher la tâche
package com.nouhoum.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Entry point classes define onModuleLoad().
 */
public class HelloGWT implements EntryPoint {
 private TextBox taskNameBox = new TextBox();
 private FlexTable taskContainer = new FlexTable();
 private VerticalPanel container = new VerticalPanel();

 /**
  * This is the entry point method.
  */
 public void onModuleLoad() {  
  Button createButton = new Button("Create task");
  HorizontalPanel taskCreationPanel = new HorizontalPanel();

  taskCreationPanel.add(taskNameBox);
  taskCreationPanel.add(createButton);
  
  container.add(taskCreationPanel);
  Label title = new Label("Task List");
  container.add(title);
  container.add(taskContainer);
  
  createButton.addClickHandler(new ClickHandler() {   
   @Override
   public void onClick(ClickEvent event) {
    //We remove leading and trailing whitespaces from 
    //the user provided text
    String taskName = taskNameBox.getText().trim();
    if(!taskName.isEmpty()) {
     addTask(taskName); 
    }
   }
  });
  
  RootPanel.get("content").add(container);
 }

 private void addTask(String taskName) {
  // TODO Auto-generated method stub 
 }
}

Implémentons maintenant la méthode addTask().  Au passage on accélère un peu le rythme en ajoutant l'implémentation de la suppression d'une tâche. Voici le code final de HelloGWT.java:


package com.nouhoum.gwt.client;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Entry point classes define onModuleLoad().
 */
public class HelloGWT implements EntryPoint {
 private TextBox taskNameBox = new TextBox();
 private FlexTable taskContainer = new FlexTable();
 private List<Label> tasks = new ArrayList<Label>();
 private VerticalPanel container = new VerticalPanel();

 /**
  * This is the entry point method.
  */
 public void onModuleLoad() {  
  Button createButton = new Button("Create task");
  HorizontalPanel taskCreationPanel = new HorizontalPanel();

  taskCreationPanel.add(taskNameBox);
  taskCreationPanel.add(createButton);
  
  container.add(taskCreationPanel);
  Label title = new Label("Task List");
  container.add(title);
  container.add(taskContainer);
  
  createButton.addClickHandler(new ClickHandler() {   
   @Override
   public void onClick(ClickEvent event) {
    //We remove leading and trailing whitespaces from 
    //the user provided text
    String taskName = taskNameBox.getText().trim();
    if(!taskName.isEmpty()) {
     addTask(taskName); 
    }
    taskNameBox.setText("");
   }
  });
  
  RootPanel.get("content").add(container);
  taskNameBox.setFocus(true);
 }

 private void addTask(String taskName) {
  final int row = taskContainer.getRowCount();
  final Label taskLabel = new Label(taskName);
  taskContainer.setWidget(row, 0, taskLabel);
  final Label removeLink = new Label("Remove");
  removeLink.addStyleName("remove");
  taskContainer.setWidget(row, 1, removeLink);  
  tasks.add(taskLabel);
  
  removeLink.addClickHandler(new ClickHandler() {     
   @Override
   public void onClick(ClickEvent event) {
    int index = tasks.indexOf(taskLabel);
    removeTask(tasks.indexOf(taskLabel)); 
    tasks.remove(index);
   }
  });
 }

 private void removeTask(int row) {
  taskContainer.removeRow(row);
 }
}

Le résultat en image!




Une fois la tâche créée nous l'insérons dans la première colonne du Flextable  et le lien pour la supprimer dans la deuxième colonne avec le code suivant:    
final Label taskLabel = new Label(taskName);
taskContainer.setWidget(row, 0, taskLabel);
final Label removeLink = new Label("Remove");
removeLink.addStyleName("remove");
taskContainer.setWidget(row, 1, removeLink);
Puis nous écoutons l'évènement de clic sur le label Remove. Dès qu'il survient nous récupérons la ligne correspondante grâce à la liste qui stocke l'ensemble des tâches en cours. Avec le numéro de la ligne nous supprimons la tâche de liste tasks et du tableau.


removeLink.addClickHandler(new ClickHandler() {     
  @Override
  public void onClick(ClickEvent event) {
    int index = tasks.indexOf(taskLabel);
    removeTask(tasks.indexOf(taskLabel)); 
    tasks.remove(index);
 }
});
Nous avons ajouté une classe CSS au label "Remove" pour l'afficher en rouge et faire apparaître le curseur sous forme de pointeur.
.remove {
  color: #c00;
  cursor: pointer;
}
Voici la capture finale de notre super application HelloGWT.

C'est la fin de cet article. Dans le prochain de la série nous verrons comment faire communiquer une application GWT avec un serveur.
© Nouhoum TRAORE.. Fourni par Blogger.