Aller au contenu

Arbre

tree() de @dzgrid/dom/tree affiche les lignes en arbre (un treegrid WAI-ARIA) : la colonne de l’arbre est décalée d’un pas par niveau, un parent a un chevron, → l’ouvre, ← le ferme ou va au parent (inversé de droite à gauche), * ouvre ses frères. Textes -tree. Les lignes d’un arbre sont des lignes de données ordinaires avec des clés ordinaires : la sélection, l’édition et l’export marchent sur elles comme sur des lignes à plat.

Donnez la hiérarchie de l’une de trois façons :

Option Vos lignes Exemple
parentId(row) à plat, chacune avec l’identifiant de son parent (null en haut) des dossiers dans une table
children(row) imbriquées, chacune contenant ses enfants un document JSON
path(row) à plat, chacune avec son chemin de noms ['Jardin', 'Outils', 'Râteau']
import { createGrid, type GridMessages } from '@dzgrid/dom';
import { tree } from '@dzgrid/dom/tree';
import { FR } from '@dzgrid/dom/locales/fr';
import { FR_TREE } from '@dzgrid/dom/locales/fr-tree';
type Room = { id: string; parent: string | null; name: string; area: number };
const rows: Room[] = [
{ id: 'h', parent: null, name: 'Phare', area: 120 },
{ id: 'h1', parent: 'h', name: 'Chambre de la lanterne', area: 14 },
{ id: 'h2', parent: 'h', name: 'Cuisine du gardien', area: 22 },
{ id: 'h21', parent: 'h2', name: 'Garde-manger', area: 4 },
];
const messages: GridMessages = { ...FR, text: { ...FR.text, ...FR_TREE.text } };
const rooms = tree<Room>({ parentId: (r) => r.parent, initial: 1, column: 'name', filter: 'ancestors' });
const grid = createGrid<Room>(document.getElementById('rooms')!, {
rows,
columns: [{ id: 'name', header: 'Pièce', flex: 1 }, { id: 'area', header: 'Surface (m²)', type: 'number' }],
getRowId: (r) => r.id,
messages,
features: [rooms],
});
grid.kernel.dispatch({ type: 'rows.expandAll', expanded: true }, 'api');

→ ouvre une pièce, ← la ferme ; filtrez Pièce sur « garde-manger » et ses parents restent pour qu’on puisse l’atteindre.

  • initial : 'none' (tout fermé), 'all', ou une profondeur (ouvert jusqu’à ce niveau).
  • filter : 'ancestors' garde les parents d’une ligne qui correspond (pour qu’on puisse l’atteindre), 'subtree' garde aussi les enfants d’un parent qui correspond.
  • Un tri ordonne les enfants de chaque parent entre eux ; les parents restent au-dessus de leurs enfants. Ce qui est ouvert reste ouvert après un tri, un filtre ou de nouvelles lignes.
  • sticky: n garde les parents ouverts des lignes visibles figés sous les titres, jusqu’à n niveaux.
  • column est la colonne de l’arbre (la première colonne visible par défaut) ; indent son pas en pixels.
  • Les commandes rows.expand et rows.expandAll ouvrent et ferment depuis le code ; les événements rowExpanded vous le disent. En Angular, [(expanded)] tient les clés ouvertes.

Chaque option de tree() est sur sa page.

lazyChildren({ has, load }) de @dzgrid/dom/tree-lazy charge les enfants d’un parent à sa première ouverture : has(row) dit si une ligne peut avoir des enfants (elle reçoit un chevron), load(row, { signal }) les rend. Pendant le chargement, une ligne de chargement se tient sous le parent ; en cas d’échec, elle propose de réessayer.

import { createGrid } from '@dzgrid/dom';
import { tree } from '@dzgrid/dom/tree';
import { lazyChildren } from '@dzgrid/dom/tree-lazy';
import { FR } from '@dzgrid/dom/locales/fr';
import { FR_TREE } from '@dzgrid/dom/locales/fr-tree';
type Folder = { id: string; parent: string | null; name: string; folders: number };
createGrid<Folder>(document.getElementById('folders')!, {
rows: [{ id: 'root', parent: null, name: 'Archives des cartes', folders: 3 }],
columns: [{ id: 'name', header: 'Dossier' }],
getRowId: (f) => f.id,
messages: { ...FR, text: { ...FR.text, ...FR_TREE.text } },
features: [tree<Folder>({
parentId: (f) => f.parent,
lazy: lazyChildren<Folder>({
has: (f) => f.folders > 0,
load: async (f, { signal }) => (await fetch(`/api/folders/${f.id}`, { signal: signal as AbortSignal })).json(),
}),
})],
});

detail({ part, height }) de @dzgrid/dom/detail ouvre un panneau sous une ligne au lieu de lignes enfants (textes -detail). Le panneau est une partie detail ; nestedGrid({ settings }) fait de cette partie une grille entière. L’imbrication s’arrête au-delà de trois niveaux.

import { createGrid } from '@dzgrid/dom';
import { detail, nestedGrid } from '@dzgrid/dom/detail';
import { FR } from '@dzgrid/dom/locales/fr';
import { FR_DETAIL } from '@dzgrid/dom/locales/fr-detail';
type Trip = { id: string; date: string; km: number };
type Bicycle = { id: string; name: string; trips: Trip[] };
const messages = { ...FR, text: { ...FR.text, ...FR_DETAIL.text } };
const trips = nestedGrid<Trip>({
settings: (c) => ({
rows: (c.row as Bicycle).trips,
columns: [{ id: 'date', header: 'Date', type: 'day' }, { id: 'km', header: 'km', type: 'number' }],
getRowId: (t) => t.id,
messages,
}),
});
createGrid<Bicycle>(document.getElementById('bicycles')!, {
rows: [{ id: 'b1', name: 'Héron bleu', trips: [{ id: 't1', date: '2026-06-01', km: 42 }] }],
columns: [{ id: 'name', header: 'Vélo' }],
getRowId: (b) => b.id,
messages,
features: [detail<Bicycle>({ part: trips, height: 200 })],
});

Le chevron d’un vélo ouvre ses sorties : une grille à elles, dans la ligne.

En Angular, le panneau est un composant donné avec componentPart('detail', Component), et un <dz-grid [dzgNested]> à l’intérieur fait la grille imbriquée.