Composant Tree
Tree sert à représenter des catégories de produits, des organigrammes, des dossiers, des zones géographiques et toute autre donnée hiérarchique. Une petite arborescence stable peut être maintenue directement dans la page. Si les données appartiennent à un système métier, chargez les racines et les enfants depuis une API.
1. Choisir le mode de configuration
| Besoin | Configuration recommandée |
|---|---|
| Petite liste stable gérée par les auteurs | Tree Nodes statiques |
| Hiérarchie gérée par un système métier | Tree Data Source |
| Création, modification, suppression ou commandes contextuelles | Source dynamique + Node Actions |
| Réorganisation ou déplacement de branches | Source dynamique + onDrop |
1.1 Tree : catégories de produits statiques
L’exemple statique écrit d’abord [[treeNode.name]] dans selectedCategory avec Set Page Context, puis appelle reloadTable sur le ProTable. Dans Query Context, le paramètre category lit cette valeur.
1.2 Tree02 : chargement dynamique et édition
Tree02 charge les racines à l’ouverture, puis les enfants directs avec parentId lorsqu’un dossier est développé. Il illustre aussi la recherche serveur, les détails, l’ajout, la modification, la suppression et l’enregistrement d’un glisser-déposer. Validez ces fonctions dans cet ordre afin d’isoler plus facilement une erreur.
2. Ajouter un Tree
- Ouvrez la page dans Page Designer et sélectionnez Screen.
- Ajoutez Tree dans la zone enfant Navigation Tree.
- Pour des données statiques, ajoutez des Tree Nodes.
- Pour une API, ajoutez Tree Data Source dans Data Source.
- Placez le ProTable, le Form ou un autre contenu dans Panels.
Navigation Tree forme la zone de navigation à gauche. Panels et Grids forment automatiquement la zone de contenu.
3. Configurer le Tree
| Propriété | Rôle |
|---|---|
| Title | Titre facultatif au-dessus de l’arbre |
| Width | Largeur sur ordinateur ; la zone passe sur une ligne complète sur petit écran |
| Show Search | Affiche la recherche |
| Checkable | Affiche les cases à cocher |
| Default Expand All | Développe les nœuds déjà chargés au premier affichage |
| Node Draggable | Autorise le glisser-déposer et déclenche onDrop |
4. Nœuds statiques
Ajoutez des TreeNode dans Tree Nodes, puis renseignez Title, Value, Disabled, Selectable, Checkable, Is Leaf, Icon et Extra Info. Value doit rester unique et stable dans tout l’arbre, car les interactions l’utilisent pour identifier le nœud.
5. Source de données dynamique
Ajoutez TreeDataSource dans Data Source et commencez par la requête des racines :
| Réglage | Exemple Tree02 |
|---|---|
| Root Data URL | /spark/api/sample/navigation/tree/nodes |
| Request Method | GET |
| Response List Path | data.nodes |
| Title Field | label |
| Value Field | id |
| Children Field | children |
| Leaf Field | leaf |
| Extra Field | count |
5.1 Vérifier avec Test
Cliquez sur Test. Le test, le canevas et la page exécutée partagent la même construction de requête et la même analyse de réponse. Le résultat permet donc de distinguer un problème d’URL, de données backend, de Response List Path ou de correspondance des champs.
Spark ajoute automatiquement :
X-Spark-Page-Environment: DEV
X-Spark-Page-Render-Mode: DESIGN_PREVIEW
Ne les ajoutez pas manuellement dans Data Source. Consultez Contexte de requête de page et débogage backend pour le protocole complet.
Le backend doit analyser les en-têtes avec le contrat partagé, puis comparer les valeurs d’énumération :
SparkPageRequestContext context = SparkPageRequestHeaders.resolve(
environment, renderMode);
boolean developmentPreview = context != null
&& context.getEnvironment() == SparkPageEnvironment.DEV
&& context.getRenderMode() == SparkPageRenderMode.DESIGN_PREVIEW;
Les données de prévisualisation doivent être déterministes et ne jamais contenir de secrets, de données personnelles ou de données de production sensibles.
5.2 Chargement complet ou différé
Une petite arborescence peut être renvoyée entièrement dans children. Pour un grand volume, configurez Child Nodes URL et Parent Parameter. Lors du développement d’un nœud non terminal, Tree transmet sa Value, par exemple dans parentId. Un dossier doit renvoyer leaf: false, même si children est encore vide.
6. Recherche
La recherche locale convient aux données statiques ou déjà chargées. Avec le chargement différé, configurez Search URL et son paramètre. Le serveur doit renvoyer les ancêtres des nœuds trouvés afin de préserver leur contexte.
7. Nœud courant et contexte d’événement
| Événement | Données usuelles |
|---|---|
onClick | treeNode, selectedTreeNode |
onCheck | treeCheck.checkedKeys, treeCheck.checkedNodes |
onExpand | treeExpand.expandedKeys, treeExpand.node |
onLoad | treeLoad.node, treeLoad.children |
onDrop | treeDrop.dragNode, treeDrop.dropNode, treeDrop.position |
Les chemins les plus utiles sont treeNode.value, treeNode.title, treeNode.isLeaf et treeNode.data.category. Une Node Action met le nœud courant à jour sans déclencher simultanément onClick.
8. Configurer les actions de nœud
Ajoutez un TreeNodeAction sous Node Actions. Définissez le titre, l’icône au moyen du sélecteur partagé, le déclencheur, le ton, la condition d’affichage et la condition de désactivation. Le bouton More et le menu contextuel utilisent la même liste ; sur écran tactile, utilisez More plutôt qu’un appui long.
PUT /spark/api/sample/navigation/tree/nodes/[[treeNode.value]]
POST /spark/api/sample/navigation/tree/nodes
DELETE /spark/api/sample/navigation/tree/nodes/[[treeNode.value]]
Configurez l’interaction sur la Tree Node Action et appelez reloadTree uniquement après une requête réussie. Masquer une commande ne remplace jamais le contrôle d’autorisation du backend.
9. Glisser-déposer et persistance
Avec Node Draggable, Tree déplace d’abord le nœud à l’écran, puis déclenche onDrop. treeDrop.position vaut BEFORE, INSIDE ou AFTER. Pour enregistrer le résultat, ajoutez Send Request :
POST /spark/api/sample/navigation/tree/reorder
Envoyez dragId = [[treeDrop.dragNode.key]], dropId = [[treeDrop.dropNode.key]] et position = [[treeDrop.position]]. En cas d’échec ou d’interruption, Tree restaure la structure précédente. Le serveur doit vérifier les nœuds, le parent, les cycles, les droits et les modifications concurrentes.
10. Actualiser le contenu après une sélection
Dans le Tree statique, onClick écrit d’abord [[treeNode.name]] dans selectedCategory, puis appelle reloadTable. Dans Tree02, écrivez [[treeNode.value]] dans selectedNodeId et [[treeNode.data.category]] dans selectedCategory, puis rechargez le ProTable. Cet ordre est indispensable : sinon le tableau réutilise la sélection précédente.
11. Fonctions du composant
| Fonction | Effet |
|---|---|
reloadTree | Recharge les racines et conserve une sélection encore valide |
expandAll | Développe tous les dossiers déjà chargés |
collapseAll | Réduit tous les dossiers |
expandAll ne charge pas automatiquement toutes les branches absentes d’un grand arbre différé.
12. Exemple de backend Java
Le contrat Tree02 cible Java 17 et Spring Boot 4. Le DTO de nœud expose au minimum id, label, parentId, leaf et children. Les requêtes d’écriture utilisent parentId, label, category ; la réorganisation utilise dragId, dropId, position.
| Usage | Méthode et chemin |
|---|---|
| Racines, enfants et recherche | GET /spark/api/sample/navigation/tree/nodes |
| Ajouter un enfant | POST /spark/api/sample/navigation/tree/nodes |
| Modifier un nœud | PUT /spark/api/sample/navigation/tree/nodes/{nodeId} |
| Supprimer un nœud | DELETE /spark/api/sample/navigation/tree/nodes/{nodeId} |
| Enregistrer un déplacement | POST /spark/api/sample/navigation/tree/reorder |
Le service peut choisir des données de prévisualisation reproductibles ou le dépôt de l’environnement demandé :
Map<String, NodeRecord> source = isDevelopmentPreview(context)
? designPreviewNodes : nodes;
String keyword = StringUtils.trimToNull(query);
if (keyword != null) {
return children(source, null).stream()
.map(root -> filterTree(source, root, keyword))
.filter(Objects::nonNull)
.toList();
}
return children(source, StringUtils.trimToNull(parentId)).stream()
.map(node -> toDto(source, node))
.toList();
Le backend doit revalider les nœuds, les parents, les cycles et les droits pour chaque écriture. Les erreurs visibles utilisent LatticeRuntimeException(Message.code(...)) et les ressources i18n du serveur, jamais une chaîne assemblée dans le Service. La mise à jour du parent et de l’ordre des frères doit être atomique.
Le ProTable de droite peut ajouter nodeId à sa requête. Les catégories continuent d’utiliser category, tandis que le backend traduit les nœuds métier en filtres adaptés.
13. Validation et dépannage
Validez successivement le chargement, la sélection, les commandes de nœud et la persistance du glisser-déposer. Commencez par Test, vérifiez l’ouverture des enfants et la recherche, puis sélectionnez plusieurs nœuds et confirmez que le tableau change avant de tester les écritures.
| Symptôme | Premier contrôle |
|---|---|
| Échec de Test | URL, méthode, connexion, authentification et CORS |
| HTTP 200 mais aucun nœud | Response List Path doit pointer vers un tableau |
| Nombre visible, titre absent | Title Field, Value Field et noms JSON réels |
| Dossier impossible à développer | Leaf Field renvoie peut-être true par erreur |
| Même liste pour plusieurs nœuds | Ordre du Page Context et chemins du Query Context |
| Ordre perdu après actualisation | Paramètres onDrop et persistance backend |