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');import { Component, signal } from '@angular/core';import { DzGrid, type DzgColumnOrGroup } from '@dzgrid/angular';import type { RowKey } from '@dzgrid/core';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 };
@Component({ selector: 'app-rooms', imports: [DzGrid], template: ` <dz-grid [rows]="rows" [columns]="columns" [getRowId]="rowId" [messages]="messages" [features]="features" [(expanded)]="open" /> <p>{{ open()?.size ?? 0 }} pièces ouvertes</p>`,})export class Rooms { readonly 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 }, ]; readonly columns: DzgColumnOrGroup<Room>[] = [{ id: 'name', header: 'Pièce', flex: 1 }, { id: 'area', header: 'Surface (m²)', type: 'number' }]; readonly rowId = (r: Room) => r.id; readonly messages = { ...FR, text: { ...FR.text, ...FR_TREE.text } }; readonly features = [tree<Room>({ parentId: (r) => r.parent, initial: 1, column: 'name', filter: 'ancestors' })]; // Les lignes ouvertes, dans les deux sens. readonly open = signal<ReadonlySet<RowKey> | undefined>(undefined);}import '@dzgrid/element/define';import type { DzGridElement } from '@dzgrid/element';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 grid = document.querySelector<DzGridElement<Room>>('dz-grid')!;grid.columns = [{ id: 'name', header: 'Pièce', flex: 1 }, { id: 'area', header: 'Surface (m²)', type: 'number' }];grid.getRowId = (r) => r.id;grid.messages = { ...FR, text: { ...FR.text, ...FR_TREE.text } };grid.features = [tree<Room>({ parentId: (r) => r.parent, initial: 1, column: 'name', filter: 'ancestors' })];grid.rows = [ { 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 },];
await grid.ready;grid.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: ngarde les parents ouverts des lignes visibles figés sous les titres, jusqu’ànniveaux.columnest la colonne de l’arbre (la première colonne visible par défaut) ;indentson pas en pixels.- Les commandes
rows.expandetrows.expandAllouvrent et ferment depuis le code ; les événementsrowExpandedvous le disent. En Angular,[(expanded)]tient les clés ouvertes.
Chaque option de tree() est sur sa page.
Enfants chargés à la demande
Section intitulée « Enfants chargés à la demande »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(), }), })],});Maître et détail
Section intitulée « Maître et détail »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.