Un arbre asynchrone avec GXT

de | 3 janvier 2011

Le principal avantage de GXT est de proposer des Widget permettant des interactions complexes avec l’utilisateur, mais permettant de simplifier le travail des développeurs. Voici un nouvel exemple permettant de réaliser un arbre asynchrone avec GXT de manière relativement simple, avec la classe TreeGrid:

Qu’est-ce qu’un arbre asynchrone ?

Certaines données nécessitent d’être représentées sous forme d’arbre que tout le monde connait et dont voici un exemple:

(capture d’écran de : http://www.jmdoudoux.fr)

Dans cet exemple simple, un arbre synchrone est amplement suffisant, la structure de l’arbre étant connue et finie.

Les choses se compliquent lorsque l’on souhaite mettre en place un arbre dont le chargement des enfants de la branche se font lorsque l’on clique sur celle-ci (une nouvelle requête au serveur est alors effectuée). En GXT cette opération est relativement simple, le code suivant permettant de le faire :

Dans l’entry point:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package fr.yenoussa.example.gwt.gxt.desktop.client;
 
import java.util.Arrays;
import java.util.List;
 
import com.extjs.gxt.ui.client.data.BaseTreeLoader;
import com.extjs.gxt.ui.client.data.BaseTreeModel;
import com.extjs.gxt.ui.client.data.RpcProxy;
import com.extjs.gxt.ui.client.store.TreeStore;
import com.extjs.gxt.ui.client.widget.Window;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.treegrid.TreeGrid;
import com.extjs.gxt.ui.client.widget.treegrid.TreeGridCellRenderer;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
 
/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class AsyncTreeGridExample implements EntryPoint {
	/**
	 * Create a remote service proxy to talk to the server-side Greeting
	 * service.
	 */
	private final GreetingServiceAsync greetingService = GWT
			.create(GreetingService.class);
 
	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad() {
		// Une petite fenetre pour mettre notre arbre
		Window win = new Window();
		win.setHeading("Exemple d'arbre asynchrone");
		win.setLayout(new FitLayout());
		win.setSize(500, 500);
 
		// On crée le proxy qui sera responsable du chargement des données ...
		RpcProxy<List<BaseTreeModel>> proxy = new RpcProxy<List<BaseTreeModel>>() {
 
			@Override
			protected void load(Object loadConfig,
					AsyncCallback<List<BaseTreeModel>> callback) {
				// C'est le loader qui fera tout seul l'appel asynchrone pour
				// le chargement des enfants
				greetingService.getChildren((BaseTreeModel) loadConfig,
						callback);
			}
		};
		// on crée notre loader qui sera l'objet qui appelera le proxy ...
		BaseTreeLoader<BaseTreeModel> loader = new BaseTreeLoader<BaseTreeModel>(
				proxy) {
			@Override
			public boolean hasChildren(BaseTreeModel parent) {
				// c'est ici que l'on définit si des enfants seront chargés lors
				// d"un appel asynchrone, si false, il n'y aura pas de petite
				// fleche qui permet de déplier la branche
				return true;
			}
		};
		// On crée notre tree store qui sera l'objet qui contiendra la liste des
		// objets à afficher
		TreeStore<BaseTreeModel> store = new TreeStore<BaseTreeModel>(loader);
 
		// On crée l'arbre
		ColumnConfig name = new ColumnConfig("_Name", "Name", 100);
		// on n'oublie pas le treegrid cell renderer qui permet de savoir sur
		// quelle colonne on place la petite fleche pour déplier l arbre
		name.setRenderer(new TreeGridCellRenderer<BaseTreeModel>());
 
		ColumnModel cm = new ColumnModel(Arrays.asList(name));
 
		// on crée notre arbre
		TreeGrid<BaseTreeModel> tree = new TreeGrid<BaseTreeModel>(store, cm);
		// on etire la colonne ...
		tree.getView().setForceFit(true);
 
		// on rajoute l arbre dans la fenetre
		win.add(tree);
		// que l'on affiche ...
		win.show();
	}
}

La servlet (j’ai gardé la même servlet que celle de l’example créé en même temps que le projet):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package fr.yenoussa.example.gwt.gxt.desktop.server;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
import com.extjs.gxt.ui.client.data.BaseTreeModel;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
 
import fr.yenoussa.example.gwt.gxt.desktop.client.GreetingService;
 
/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements
		GreetingService {
 
	@Override
	public List<BaseTreeModel> getChildren(BaseTreeModel father) {
		if (father == null) {
 
			BaseTreeModel root1 = new BaseTreeModel();
			root1.set("_Name", "Root 1");
			root1.set("_Profondeur", new Integer(0));
 
			BaseTreeModel root2 = new BaseTreeModel();
			root2.set("_Name", "Root 2");
			root2.set("_Profondeur", new Integer(0));
 
			List<BaseTreeModel> listToReturn = new ArrayList<BaseTreeModel>();
			listToReturn.add(root1);
			listToReturn.add(root2);
			return listToReturn;
		}
		BaseTreeModel child = new BaseTreeModel();
		Integer profondeur = father.<Integer> get("_Profondeur") + 1;
		child.set("_Profondeur", profondeur);
		child.set("_Name", "Enfant, profondeur: " + profondeur);
		return Arrays.asList(child);
	}
 
}

Je donne l’ interface asynchrone et l’interface de la servlet, même si les deviner est relativement trivial:

L’interface de la servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package fr.yenoussa.example.gwt.gxt.desktop.client;
 
import java.util.List;
 
import com.extjs.gxt.ui.client.data.BaseTreeModel;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
 
/**
 * The client side stub for the RPC service.
 */
@RemoteServiceRelativePath("greet")
public interface GreetingService extends RemoteService {
 
	List<BaseTreeModel> getChildren(BaseTreeModel father);
 
}

Interface asynchrone:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package fr.yenoussa.example.gwt.gxt.desktop.client;
 
import java.util.List;
 
import com.extjs.gxt.ui.client.data.BaseTreeModel;
import com.google.gwt.user.client.rpc.AsyncCallback;
 
/**
 * The async counterpart of <code>GreetingService</code>.
 */
public interface GreetingServiceAsync {
 
	void getChildren(BaseTreeModel father,
			AsyncCallback<List<BaseTreeModel>> callback);
 
}

Fonctionnement:
Il est important de bien comprendre le fonctionnement du petit exemple :

  • Le store contient les éléments à afficher
  • Le store se sert du loader passé en paramètre du constructeur pour charger les éléments
  • Le loader appelle le RPC proxy pour faire le chargement asynchrone de la liste des enfants d’un élément passé en paramètre
  • Lors de l’affichage de l’arbre la première fois, celui-ci passe appelle automatiquement la méthode asynchrone disponible dans le proxy. Vu que l’arbre cherche à charger les éléments racine, le loadConfig vaut null la première fois. Lorsque l’on souhaitera déplier une branche de l’arbre, ce loadConfig sera l’objet représentatif de la branche que l’on souhaite déplier.
  • Coté serveur, on doit retourner une liste d’éléments en fonction de l’élément parent que l’on reçoit en paramètre. Si cet élément est null, alors il faut renvoyer les racines de l’abre, et sinon renvoyer les enfants de l’élément racine reçu en paramètre.
  • La méthode asynchrone est faite de manière à ce que l’on puisse passer en paramètre l’élément parent afin de pouvoir retrouver ses enfants.

Le résultat du code proposé est le suivant:

2 réflexions au sujet de « Un arbre asynchrone avec GXT »

  1. Sylvain Yenoussa Auteur de l’article

    De rien !
    Cela fait longtemps que je n’ai pas fait de GXT, il semblerait que Sencha aie bien évolué depuis, mais je pense que certains principes restent les mêmes ;)

    Répondre
  2. KWSimon

    Merci pour cet exemple. Quelques années plus tard cette article est toujours d’actualité (et utile).

    Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.

Time limit is exhausted. Please reload the CAPTCHA.