Mettre en place un Lazy Tree avec GWT

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.

About these ads

2 Responses to Mettre en place un Lazy Tree avec GWT

  1. Joseph says:

    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

    • jawher says:

      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 :D

      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” :P

      Merci à toi aussi !

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: