@synapxlab/jstree

Composant arbre (tree view) réécrit en TypeScript — zéro dépendance runtime.
Inspiré de l'écosystème jsTree éprouvé, repensé en ES2020 avec une API par classe propre.

  • ✓Modèle de données JSON plat ou imbriqué
  • ✓Chargement paresseux (lazy) via AJAX
  • ✓Multi-sélection, rename inline, create / delete / move
  • ✓Événements natifs (CustomEvent) sur le conteneur
  • ✓Plugins : wholerow, contextmenu, dnd, search, state, types
TypeScript ESM UMD Zéro dépendance SCSS
jstree.ts
import { JsTree } from '@synapxlab/jstree'; import '@synapxlab/jstree/style.css'; const tree = new JsTree('#tree', { plugins: ['dnd', 'contextmenu', 'search'], core: { multiple: true, data: [ { id: '1', text: 'Racine', parent: '#' }, { id: '2', text: 'Enfant', parent: '1' }, ], }, });

🌳 Démo interactive

Double-clic pour renommer, clic droit pour le menu contextuel, glisser-déposer pour déplacer, Ctrl+clic pour multi-sélectionner. À droite, un second arbre alimenté par des données système en direct : le composant affiche ce qu'on lui câble.

Arborescence de fichiers, ou toute donnée à afficher en arbre

Déplacement virtuel
  1. Glissez un nœud sur un dossier, ou entre deux lignes.

Chargement de l'arbre…

Données système

En direct

Navigateur, écran, réseau, et l'état de l'arbre de gauche : chaque valeur est un nœud mis à jour par set_text().

L'arbre en liste déroulante

Le même composant tient dans un champ de formulaire : un <select> qui déroule une hiérarchie au lieu d'une liste plate, avec filtre. Le nœud choisi remplit un champ caché, envoyé avec le formulaire.

Valeur envoyée : famille_id = (vide)

🗄️ Côté serveur

Le composant affiche et manipule l'arbre dans le navigateur, mais n'enregistre rien. Pour qu'un déplacement, un renommage ou une création survive au rechargement, il faut un code serveur : il sert l'arbre en JSON (en entier, ou branche par branche pour le chargement paresseux), reçoit les événements move_node, rename_node, create_node, delete_node et dnd_drop, vérifie les droits et met la base à jour.

Sur devis : ce code dépend de votre base de données et de vos règles métier. Nous l'écrivons pour votre projet. Demander un devis →

Un arbre stocké en représentation intervallaire

En base, chaque nœud garde son parent_id et reçoit deux bornes, gauche (bg) et droite (bd), numérotées par un parcours en profondeur. Un nœud contient tous ceux dont les bornes tombent entre les siennes : la hiérarchie se lit par une simple comparaison de nombres, sans requête récursive.

BesoinAvec les bornes
Une branche entière (dossier et tous ses sous-dossiers)WHERE bg BETWEEN :bg AND :bd : une requête, quelle que soit la profondeur.
Le chemin d'un résultat de recherche (fil d'Ariane)WHERE bg < :bg AND bd > :bd ORDER BY bg : tous les ancêtres d'un coup.
Le nombre de descendants(bd - bg - 1) / 2, sans aucune requête.
Feuille ou dossier ?bd = bg + 1 : c'est une feuille.
Refuser un déplacement en boucleLa cible tombe-t-elle entre les bornes du nœud déplacé ? Une comparaison suffit.
Des totaux par branche (articles d'une famille et de ses sous-familles)Une jointure sur l'intervalle, en une seule requête.
L'arbre dans l'ordre d'affichageORDER BY bg, servi par un index sur la borne gauche.

La contrepartie porte sur l'écriture : déplacer un nœud renumérote des bornes. Sur des arbres de quelques centaines de nœuds, on recalcule tout l'arbre dans une transaction, ce qui reste simple et sûr. La lecture, de loin l'opération la plus fréquente, reste rapide.

Demander un devis

Décrivez votre arbre (quelles données, combien de nœuds, quelle base) et ce qu'il doit enregistrer. Un code de validation vous est envoyé par e-mail pour confirmer votre adresse.

📦 Installation

npm install @synapxlab/jstree

Ou via CDN (ESM, aucune installation) :

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@synapxlab/jstree/dist/style.css">
<script type="module">
  import { JsTree } from 'https://cdn.jsdelivr.net/npm/@synapxlab/jstree/dist/jstree.es.js';
</script>

⚡ Démarrage rapide

import { JsTree } from '@synapxlab/jstree';
import '@synapxlab/jstree/style.css';

const tree = new JsTree('#tree', {
  plugins: ['wholerow', 'contextmenu', 'dnd', 'search', 'state', 'types'],
  core: {
    multiple: true,
    check_callback: true,
    data: [
      { id: '1', text: 'Racine', parent: '#', state: { opened: true } },
      { id: '2', text: 'Documents', parent: '1' },
      { id: '3', text: 'Rapport.pdf', parent: '2' },
    ],
  },
});
const tree = new JsTree('#tree', {
  core: {
    // Chargement paresseux : appelé à l'ouverture d'un nœud
    data: {
      url: (node) => `/api/nodes?parent=${node.id}`,
    },
  },
});

📖 API & méthodes

Le constructeur prend un sélecteur (ou un élément) et un objet d'options. L'instance expose des méthodes de manipulation des nœuds.

MéthodeDescription
edit(id)Renommage inline du nœud.
create_node(parentId, opts)Crée un nœud enfant, renvoie son id.
delete_node(id)Supprime un nœud (et ses enfants).
move_node(id, parentId)Déplace un nœud sous un autre parent.
open_node(id) / close_node(id)Ouvre / referme un nœud.
open_all() / close_all()Déplie / replie tout l'arbre (ou la branche passée en argument).
set_text(id, texte)Change le libellé sans redessiner l'arbre — utile pour des valeurs en direct.
select_node(id)Sélectionne un nœud (multi-sélection si activée).
search(terme)Filtre l'arbre (plugin search).
Modèle de données : chaque nœud est { id, text, parent, icon?, state? }. parent: '#' désigne la racine. Format plat (liste) ou imbriqué (children).

🧩 Plugins

wholerow

Surlignage de la ligne entière au survol et à la sélection.

contextmenu

Menu clic droit personnalisable (renommer, créer, supprimer…).

dnd

Glisser-déposer pour réorganiser et déplacer les nœuds.

search

Filtrage et mise en évidence des nœuds correspondants.

state

Persistance de l'état (ouverture, sélection) en localStorage.

types

Types de nœuds avec icônes et règles (dossier, fichier…).

Déposer des données venues d'ailleurs : le plugin dnd ne déplace pas seulement les nœuds de l'arbre. N'importe quel élément de la page (ligne de tableau, carte, fichier d'une liste de documents) peut être glissé dans un dossier : la source émet dnd_start.vakata sur l'arbre avec les données de son choix, l'arbre indique le dossier visé pendant le geste, puis émet dnd_drop avec external: true, le dossier cible et ces données. À votre code de les ranger, en mémoire ou côté serveur.

📡 Événements

Les événements sont émis comme CustomEvent natifs sur l'élément conteneur, suffixés .jstree.

tree.element.addEventListener('select_node.jstree', (e) => {
  console.log('Sélectionné :', e.detail.node.id, e.detail.node.text);
});

Événements disponibles : select_node, deselect_node, create_node, rename_node, delete_node, move_node, open_node, close_node. Le plugin dnd ajoute dnd_start, dnd_stop, dnd_cancel et dnd_refused (dépôt refusé par move_node).