Mettre en place un Lazy Tree avec GWT
24/04/2010 2 Comments
Le but de ce billet est de présenter une technique simple pour mettre en place un arbre paresseux (lazy tree) avec GWT.
GWT offre des primitives basiques pour la création d’un arbre, qui sont :
- Le composant TreeItem : représente un noeud dans l’arbre et peut contenir de sous noeuds du même type.
- Le composant Tree : représente la racine de l’arbre et peut contenir des TreeItems
Construire un arbre revient donc à créer autant d’instances de TreeItem que nécessaire et des les assembler pour obtenir l’hiérarchie souhaitée.
Cependant, cette méthode n’est pas applicable dans le cas où :
- On dispose d’un grand nombre des noeuds (des centaines voire des milliers)
- La récupération des noeuds est coûteuse : accès à la base de données et/ou au réseau
Dans des cas pareils, mieux vaut être paresseux et ne charger des noeuds que quand nécessaire (ne charger les fils d’un noeud que quand l’utilisateur l’ouvre).
Dans le cadre de ce billet, et comme source de données pour l’arbre, on suppose disposer d’un service exposé en RPC dont voici l’interface :
package com.iptech.client;
import java.util.ArrayList;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@RemoteServiceRelativePath("treeService")
public interface TreeService extends RemoteService {
ArrayList getChildren(String parent)
throws IllegalArgumentException;
}
Dans l’implémentation de ce service, j’ai introduit (via un sleep) un délais de 2 secondes, pour simuler l’obtention d’une ressource coûteuse et pour que le comportement paresseux de l’arbre soit plus visible.
public class Application implements EntryPoint {
private static final String CHARGEMENT_EN_COURS = "Chargement en cours ...";
private final TreeServiceAsync treeService = GWT.create(TreeService.class);
public void onModuleLoad() {
TreeItem root = new TreeItem(CHARGEMENT_EN_COURS);
Tree browseTree = new Tree();
browseTree.addItem(root);
treeService.getChildren(null, new TreeRootCallback(browseTree));
browseTree.addOpenHandler(new OpenHandler() {
public void onOpen(OpenEvent event) {
if (needsLoading(event.getTarget())) {
treeService.getChildren(event.getTarget().getText(),
new TreeItemCallback(event.getTarget()));
}
}
});
RootPanel.get().add(browseTree);
}
:
:
}
Je commence donc par créer l’instance du composant du composant Tree et d’y ajouter un élément avec le message “Chargement en cours …“
J’invoque ensuite le service présenté plus haut pour récupérer la liste des noeuds racines de l’arbre. Cet appel se fait de façon asynchrone, d’où l’utilisation d’un callback dont voici le code :
public static final class TreeRootCallback implements
AsyncCallback<ArrayList<String>> {
private Tree browseTree;
public TreeRootCallback(Tree browseTree) {
super();
this.browseTree = browseTree;
}
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
public void onSuccess(ArrayList names) {
browseTree.removeItems();
for (String name : names) {
TreeItem ti = new TreeItem(name);
ti.addItem(CHARGEMENT_EN_COURS);
browseTree.addItem(ti);
}
}
}
A la réception de la réponse, le callback supprime les éléments de l’arbre (le message “Chargement en cours …” plus précisément) et pour chaque chaîne retournée :
- Crée un item avec la chaîne comme texte et l’ajoute à l’arbre
- A chaque item crée ajoute un fils avec le message “Chargement en cours …“
Retournons à la méthode onModuleLoad.
L’étape suivante consiste à attacher à l’arbre un listener qui intercepte les évènements d’ouverture d’un noeud, et si ce dernier nécessite le chargement de ses fils (dans le cas où il a un seul fils et que ce dernier a le texte “Chargement en cours …“), fait appel à treeService en lui passant un autre callback dont voici le code :
public static final class TreeItemCallback implements
AsyncCallback<ArrayList<String>> {
private TreeItem treeItem;
public TreeItemCallback(TreeItem treeItem) {
super();
this.treeItem = treeItem;
}
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
public void onSuccess(ArrayList names) {
treeItem.removeItems();
for (String name : names) {
TreeItem ti = new TreeItem(name);
ti.addItem(CHARGEMENT_EN_COURS);
treeItem.addItem(ti);
}
}
}
Ce second callback ressemble beaucoup au premier, excepté qu’il opère sur un TreeItem au lieu d’un Tree.
A ce propos, j’étais étonné du fait que Tree et TreeItem n’ont pas un ancêtre commun, bien qu’ils sont très similaires conceptuellement (tous les deux sont des conteneurs d’autres TreeItems, ce qui se fait trahir par le fait qu’ils partagent plusieurs méthodes avec le même nom et signatures).
Code source
Un projet maven avec le code source complet de ce qui a été présenté dans ce billet est disponible sous la licence MIT sur GitHub à l’adresse suivante : http://github.com/jawher/gwt-lazy-tree/
Une fois ce projet récupéré, il suffit d’exécuter la commande “mvn gwt:run” dans son dossier pour exécuter et tester cette application.
Salut Jawher
Sympa cet article, mais … Pourquoi cet intérêt soudain pour GWT ?
Marrant au fond, vu que je me disais justement que j’allais regarder GWT de plus près. Aussi, si jamais tu as le temps/la volonté/l’envie d’en faire une rapide comparaison avec Wicket, je suis intéressé…
Encore merci !
++
joseph
Salut Joseph,
Et bien GWT m’intéressait beacoup et ce depuis plus d’un an déjà, j’ai juste pas trouvé le temps (ni la volonté) de m’y mettre. Là, je l’utilise dans un projet au boulot, ce qui tombe très bien
Une comparaison avec Wicket serait intéressante en effet, quoi que les 2 ne sont pas de la même catégorie … je me pencherais plutôt sur “GWT vu des yeux d’un Wicketien”
Merci à toi aussi !