jeudi 17 février 2011

Première appli GWT avec UiBinder et GWT Designer

Dans ce tutoriel nous allons voir comment développer facilement une petite application de gestion de taches à l’aide de GWT, UiBinder et GWT Designer.

GWT Designer est un outil de création d’interfaces graphiques qui fonctionne en drag and drop, à la manière du designer SWING Matisse de Netbeans.

UI Binder est une nouvelle façon de décrire les interfaces graphiques GWT. Avec ce framework on déclare les IHM en XML et non en  Java comme on le faisait dans les versions anterieures de GWT. Le code est ainsi plus concis et beaucoup plus lisible.
Dans ses dernières versions, GWT Designer est compatible avec UiBinder.


Installation de l’outillage

Google fournit un plugin Eclipse plutôt bien fait qui apporte le support de GWT et de Google APP engine. Depuis la version 2.2 qui vient de sortir cette semaine, le plugin contient également GWT Designer.

Pour installer ce plugin sous Eclipse Helios (3.6), ajoutez ce repository : http://dl.google.com/eclipse/plugin/3.6

Création du projet
Après avoir installé le plugin, créez un nouveau projet en cliquant sur le bouton


Une première page d’exemple sera générée. Vous constaterez au passage que le générateur de projets n’utilise pas encore UI Binder et que le code java ressemble grossièrement à du SWING.
Nous allons voir comment faire quelque chose de plus élégant avec cette version de GWT.

Création d’une IHM
Dans le menu du projet, cliquez sur New-> UiBinder
Puis créez une classe nommée TodoList.

GWT nous génère alors une classe Java qui permettra de traiter les évènements et un fichier XML pour décrire l'interface graphique. Une fois compilé, tout ce code sera transformé en HTML et en JavaScript pour être exécuté sur un navigateur.

Nous allons dessiner notre panel comme ceci :


On a maintenant un petit formulaire (2 champs texte et “datebox”) pour ajouter des taches et un tableau pour les afficher.

Le code généré côté XML est le suivant :
<g:HTMLPanel>
 <g:FlowPanel>
   <g:Label text="Add a new task" width="100%" height="100%" styleName="{style.big}"/>
   <g:Label text="Name"/>
   <g:TextBox ui:field="nameField"/>
   <g:Label text="Description"/>
   <g:TextBox ui:field="descriptionField"/>
   <g:Label text="Due Date"/>
   <p2:DateBox ui:field="dateField"/>
  </g:FlowPanel>
  
  
  <g:SimplePanel>
  <g:Button ui:field="button">Save</g:Button>
  </g:SimplePanel>
  
  <div id="todolist">
  <g:FlowPanel>
   <g:Label text="What you have to do" width="100%" height="100%" styleName="{style.big}"/>
   <p1:CellTable ui:field="todoTable"/>
  </g:FlowPanel>
  </div>
  
 </g:HTMLPanel>

C’est un mélange de HTML et de balises spécifiques à GWT.

Revenons à notre designer. Si on selectionne un champ, on peut renseigner la variable “UiField” dans la zone “Properties”. Lorsqu’on renseigne cette variable, un champ est créé dans la classe Java correspondant à notre composant graphique. On peut ensuite manipuler ce champ depuis le code, par exemple pour changer la valeur d’un label lors d’un évenement de clic.

Interception d’un évenement

Lors d’un clic sur le bouton Save, on peut déclencer l’enregistrement d’une tache :

@UiHandler("button")
 void onButtonClick(ClickEvent event) {
  //evenement ici 
 }

Il est possible de générer ce bout de code depuis GWT Designer en effectuant un clic droit sur le bouton Save puis en sélectionnant Add event handler -> onClick
Nous verrons dans la suite de l'article le détail de cette méthode.

Création d’un service asynchrone
Nous allons créer un service côté serveur qui sera appelé de manière asynchrone par le client GWT.
Dans le menu du projet, cliquez sur New-> GWT RemoteService
Créez ensuite une classe nommée TodoService.

Le plugin va générer une interface TodoService et une classe TodoServiceImpl.

Rajoutons ces méthodes dans l’interface :

List<Todo> getTodos();
 List<Todo> addTodo(Todo todo);

Nous avons donc une méthode pour récupérer les taches existantes et une autre pour en créer de nouvelles.
Le plugin propose de générer une interface jumelle de TodoService, TodoServiceAsync qui prend en compte l’aspect asynchrone des appels. C’est cette interface que nous appellerons depuis la gestion d'évènements des widgets GWT.

Nous pouvons à présent coder la partie implémentation du service :
On y ajoute le code suivant :

//This is a mock for the Database
private List<Todo> todos = Arrays.asList(new Todo("truc1", "un truc", new Date()),new Todo("truc2", "un autre truc", new Date()));
 
 @Override
 public List<Todo> getTodos() {
  return todos;
 }

 @Override
 public List<Todo> addTodo(Todo todo) {
  todos = new ArrayList<Todo>(todos);
  todos.add(todo);
  Collections.sort(todos);
  return todos;
 }
La liste “todos” permet simplement de simuler une base de données pour simplifier cet exemple.

Paramétrage du tableau de données

Retournons maintenant côté client, pour paramétrer la manière dont le tableau va afficher les données en appelant le service.
On ajoute ce code dans le constructeur de la classe TodoList :

provider = new AsyncDataProvider<Todo>() {

   @Override
   protected void onRangeChanged(HasData<Todo> display) {
    todoService.getTodos(new AsyncCallback<List<Todo>>() {

     @Override
     public void onFailure(Throwable caught) {
      caught.printStackTrace();
     }

     @Override
     public void onSuccess(List<Todo> result) {
      updateRowData(0, result);
      
     }
    });
    
   }
  };
  provider.addDataDisplay(todoTable);
  
  TextColumn<Todo> todoNameColumn = new TextColumn<Todo>() {

   @Override
   public String getValue(Todo todo) {
    return todo.getName();
   }
  };
  
  TextColumn<Todo> todoDescriptionColumn = new TextColumn<Todo>() {

   @Override
   public String getValue(Todo todo) {
    return todo.getDescription();
   }
  };
  
  TextColumn<Todo> todoDateColumn = new TextColumn<Todo>() {

   @Override
   public String getValue(Todo todo) {
    DateTimeFormat fmt = DateTimeFormat.getFormat("dd/MM/yyyy");
    return fmt.format(todo.getDate());
   }
   
  };
  
  todoTable.addColumn(todoNameColumn, "Name");
  todoTable.addColumn(todoDescriptionColumn, "Description");
  todoTable.addColumn(todoDateColumn, "Due date");

On a ajouté 3 colonnes qui correspondent aux informations que l'on peut rentrer dans le formulaire.

L'objet provider permet de charger la liste des taches en appelant notre service asynchrone.
En cas de succès on charge tous les objets à partir du rang 0 (on ne gère pas la pagination dans cet exemple).

Appeler le service depuis un clic bouton

Nous pouvons maintenant compléter l'évènement que nous avons associé à notre bouton :
@UiHandler("button")
 void onButtonClick(ClickEvent event) {
  if (!nameField.getText().equals("")){
  Todo todo = new Todo(nameField.getText(), descriptionField.getText(),dateField.getValue());
  todoService.addTodo(todo, new AsyncCallback<List<Todo>>() {
   
   @Override
   public void onSuccess(List<Todo> result) {
    provider.updateRowData(0, result);
   }
   
   @Override
   public void onFailure(Throwable caught) {
    caught.printStackTrace();
   }
  });
  }
 }
Un clic sur le bouton appelle le service pour ajouter une tache et recharge le tableau à partir du résultat obtenu, en appelant la méthode updateRowData.

Pour terminer, nous allons modifier la page principale de l'application GWT pour y ajouter la liste de taches:
/**
  * This is the entry point method.
  */
 public void onModuleLoad() {

  RootPanel.get().add(new TodoList());
  
 }

Et voilà, notre mini appli est fonctionnelle! En conclusion on peut dire qu'avec cet outillage on obtient quelque chose de comparable à l'environnement de développement Flex, mais avec pour cible des applications HTML 5 au lieu de reposer sur la plateforme Flash. Et tout ça en gardant les outils de développement que l'on a dans le monde Java. A ce propos je vous conseille de tester les capacités de debug de GWT, c'est assez bluffant : le framework permet d'intercepter dans le code Java les évènements qui sont en fait exécutés côté client en JavaScript! On peut alors analyser les variables comme si on était entrain de debugger du pur Java. C'est aussi là qu'on voit la puissance de GWT.

Pour télécharger le code source cliquez ici
Pour savoir comment déployer cette application dans le cloud sur Google App Engine vous pouvez regarder ce post