API du provider d'actions
Connecter les actions CRUD à la table via des **callbacks du provider**
Ligne d’origine et modifications concurrentes
React et Vue appellent update(id, patch, context) et delete(id, context) avec
un TableMutationContext optionnel. Sa propriété row contient l’enregistrement
affiché à l’origine, y compris les champs de version de l’application. Le patch
contient uniquement les modifications soumises. Les actions existantes à deux
arguments pour une mise à jour ou un argument pour une suppression restent valides.
update: async (id, patch, context) => {
const expectedVersion = context?.row.dataVersion;
if (typeof expectedVersion !== "number") {
return { success: false, error: "Rechargez la fiche avant de la modifier." };
}
return updateRecord({ id, patch, expectedVersion });
},Transmettez la version attendue au serveur et comparez-la atomiquement à la version stockée avant d’appliquer les changements. Vérifiez les permissions et les champs autorisés sur le serveur : le contexte provient du client et ne donne aucun droit. En cas de conflit, renvoyez un échec pour conserver le brouillon dans le formulaire ou l’éditeur de cellule. Ne cherchez pas la version dans une autre page préchargée.
Les formulaires du catalogue, l’édition en cellule, les déplacements Kanban, les
suppressions individuelles et les suppressions groupées exécutées ligne par ligne
reçoivent ce contexte. Les actions personnalisées bulkUpdate et bulkDelete
conservent leur contrat : capturez les versions des lignes sélectionnées dans
l’adaptateur de votre application. Le contexte reste séparé des champs éditables
et ne doit pas être enregistré comme donnée métier.
Un adaptateur d’actions typé
Cet exemple complet utilise le démarrage et les fichiers communs des recettes. La référence ci-dessous détaille les options et les fragments d’intégration.
import type { TableActions } from "@/components/ui/yayaw-table/providers/table-provider";
import { productStore } from "../shared/store";
import { listRecords } from "./list-records";
import { viewActions } from "./view-actions";
export const editableActions: TableActions = {
views: viewActions,
list: (params) => listRecords(productStore.rows, params),
update: (id, patch) => Promise.resolve(productStore.update(id, patch)),
bulkUpdate: (ids, patch) =>
Promise.resolve(productStore.bulkUpdate(ids, patch)),
};
export const getEditableActions = (tableType: string) =>
tableType === "products" ? editableActions : undefined;import type { TableActions } from "@/components/ui/yayaw-table-vue/types";
import { productStore } from "../shared/store";
import { listRecords } from "./list-records";
import { viewActions } from "./view-actions";
export const editableActions: TableActions = {
views: viewActions,
list: (params) => listRecords(productStore.rows, params),
update: (id, patch) => Promise.resolve(productStore.update(id, patch)),
bulkUpdate: (ids, patch) =>
Promise.resolve(productStore.bulkUpdate(ids, patch)),
};
export const getEditableActions = (tableType: string) =>
tableType === "products" ? editableActions : undefined;API des actions
Fournissez les actions par tableType via getTableActions (passé à DataTable). Ces actions connectent la table à votre couche de données. Dans Next.js, vous pouvez passer des Server Actions pour que les opérations de liste, création, mise à jour, suppression et bulk s'exécutent côté serveur. Voir Server-side & Server Actions pour un exemple complet.
Structure
getTableActions: (tableType: string) => ({
list: async (params) => {
// params: { filters, advancedFilters, limit, orderBy, page (1-based), search }
return {
data: [],
meta: { pageCount: 1, totalCount: 0 },
};
},
aggregate: async (params) => {
// params: { filters, advancedFilters, search, calculations, locale }
// Vue Graphique : aussi groupBy, metrics, timeZone, weekStartsOn ; répondre { groups }
return {
results: {
price: { raw: 820.64, label: "820,64" },
},
meta: { totalCount: 50 },
};
},
create: async (data) => ({ success: true, data }),
update: async (id, data) => ({ success: true, data }),
delete: async (id) => ({ success: true }),
duplicate: async (id) => ({ success: true }),
bulkDelete: async (ids) => ({ success: true }),
bulkCopy: async (ids) => ({ success: true, data: ids }),
bulkUpdate: async (ids, updateData) => ({ success: true }),
destinations: [],
})destinations
Déclarez des destinations de connexion et de partage personnalisées (webhooks, n8n, connecteurs) aux côtés des actions CRUD : { id, label, kind: "connect" | "share", icon?, hidden?, requiresSelection?, run(context) } ("sync" et "export" restent acceptés comme alias de "connect"). Elles apparaissent dans le menu Données : les destinations de connexion sous une ligne Connecter › (masquée s'il n'y en a aucune) et les destinations de partage sous Partager ›, après le « Copier le lien » intégré. Une destination de connexion peut aussi déclarer schedule (frequencies?, load, save, status?) pour s'exécuter selon une planification propre à chaque vue ; voir Planifier une destination de connexion. Elle peut déclarer connector (targets, allowTargetInput?, describe, modes?, load?, save?, push, labels?, help?) pour ouvrir l'écran d'envoi de la table au lieu d'exécuter run, qui devient alors facultatif ; voir Envoyer vers un connecteur. Avec directions, conflictRules?, preview? et sync, le même écran importe aussi depuis la cible ou garde les deux côtés synchronisés ; voir Synchroniser depuis l'écran du connecteur. Voir Destinations sur mesure pour la forme complète de run(context), les exemples n8n et partage, et la remarque de sécurité sur la conservation des identifiants côté backend.
import
Déclarez import pour configurer Données › Importer : { csv?, sources?, importRows?, lookup?, allowNewOptions?, batchSize? }, tous facultatifs. Sans lui, les imports CSV recherchent les clés via list et écrivent chaque ligne via create ou update. importRows(batch, context) écrit un lot { creates, updates } sur votre serveur et retourne { created?, updated?, failures? } ; lookup({ columnId, keys }) retourne les identifiants de fiche par valeur de clé ; sources ajoute des sources après le CSV ({ id, label, description?, load(context) }) ; allowNewOptions conserve les options de liste inconnues ; batchSize fixe le nombre de lignes par écriture (50 par défaut) ; csv: false masque le CSV. Voir Actions d'import et l'exemple côté serveur.
formLinks
Déclarez formLinks pour partager des vues Formulaire sur des liens publics servis par votre application : { status(viewId), publish(viewId, snapshot), unpublish(viewId), setAcceptingResponses?(viewId, accepting) }. Avec lui, une vue Formulaire enregistrée affiche Partager le formulaire (publier sur le web, copier ou ouvrir le lien, accepter les réponses, mettre à jour le formulaire public). publish reçoit un PublicFormSnapshot et retourne { url } ; status retourne { published, url?, acceptsResponses? } ou null. La table ne sert jamais le formulaire elle-même : votre serveur stocke l'instantané, affiche YayawTableForm sur une route publique et revalide chaque réponse avec acceptPublicFormResponse. Voir Partager sur un lien public et les responsabilités de votre application.
exportFile
Déclarez exportFile(request) aux côtés des actions CRUD pour générer côté serveur le fichier de l'écran d'export, en priorité serveur :
exportFile: async (request) => {
const response = await fetch("/api/exports", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
});
if (!response.ok) {
throw new Error("Export failed");
}
return response.json(); // { url: "https://.../export-2026-09-23.xlsx" }
},request porte le format choisi ("csv" | "xlsx" | "pdf"), le scope ("view" | "selection"), formatted (le réglage Valeurs), fileName (extension incluse), viewId, la query de la vue au format de l'action list, les columns choisies dans l'ordre, et selectedRowIds lorsque scope vaut "selection". Retournez { url } (un lien de téléchargement, par exemple une URL signée) ou { blob } ; lorsqu'il est fourni, exportFile traite tous les formats, et le serveur charge lui-même les enregistrements sans limite de lignes côté navigateur. Sans lui, le navigateur écrit le CSV ou imprime la page PDF. Voir Écran d'export pour la forme complète de la requête et le comportement de repli.
tree
Déclarez tree pour la vue Arborescence : { path?, move?, createFolder? }, tous optionnels. path(id) renvoie les ancêtres d’un nœud, racine d’abord, pour le fil d’Ariane et les liens vers un dossier. move({ ids, parentId }) déplace des fiches en un seul lot (parentId: null désigne la racine) et renvoie { moved?, failed? }, où failed vaut [{ id, error? }] : les éléments en échec reviennent à leur place et la première erreur est affichée. createFolder({ parentId, name }) crée un dossier et renvoie sa fiche. Sans elles, l’arborescence parcourt la colonne parent sur les lignes chargées, déplace avec update(id, { [parentColumn]: parentId }) et crée les dossiers avec create. Votre serveur revérifie les permissions, les cycles et les noms déjà pris. Voir Charger depuis votre serveur.
views
Déclarez views pour garder les vues enregistrées sur votre serveur : { list, create, update, delete, getFavorite, setFavorite, setOrder? }. list répond { data, order? } (TableViewListResult), où order est l’ordre des vues de la personne ; setOrder({ tableId, tableType, viewIds }) l’enregistre et répond { success, data: { viewIds } }. Sans views, les vues restent dans le navigateur. Voir Vues enregistrées et Ordre des vues.
tags
Déclarez tags pour les colonnes de tags : { list, create?, update?, merge?, remove? }, chacune appelée avec { tableId, tableType, columnId }. list répond le catalogue [{ id, name, color? }] ; create, update, merge et remove le modifient, et les deux dernières réécrivent sur votre serveur les enregistrements qui utilisent ces tags. Sans list, les colonnes de tags gardent leurs options statiques.
geocode
Déclarez geocode(query, { locale, signal }) pour suggérer des adresses dans l’éditeur des colonnes de lieu et convertir les adresses lors des imports CSV. Renvoyez des lieux, le meilleur en premier : [{ lat, lng, label, address? }]. La table espace les appels, annule le précédent via signal et garde au plus huit résultats. Sans elle, l’éditeur de lieu est un simple formulaire d’adresse et de coordonnées, et les adresses importées qui ne sont pas des coordonnées sont en erreur. Appelez votre fournisseur de géocodage depuis votre serveur. Voir Suggestions d’adresses.
Réponse de list
La méthode list doit retourner :
{ data: T[]; meta?: { pageCount?: number; totalCount?: number } }La vue Arborescence envoie params.scope avec kind: "children", "subtree" ou "tree-matches". Un serveur qui l’applique répond meta.scope: "applied", avec les champs optionnels meta.childCounts et meta.sizes ({ [folderId]: number }), meta.ancestors (des lignes) pour tree-matches, et meta.truncated quand il a plafonné la réponse.
La vue Carte envoie params.scope avec kind: "bbox" : { kind: "bbox", field, west, south, east, north }, la colonne de lieu et le rectangle affiché, en degrés (west plus grand que east traverse l’antiméridien). Un serveur qui ne renvoie que les lignes dont le lieu s’y trouve répond meta.scope: "applied" ; sinon, la table filtre les lignes chargées dans le navigateur, jusqu’à table.map.maxRows (2 000). Voir Rechercher dans cette zone.
Le panneau de facettes compte les valeurs d’une colonne avec aggregate quand il le peut (groupBy: [{ columnId }] et metrics: [{ fn: "count" }], comme un graphique), sinon à partir des lignes que renvoie list ; les dossiers des autres vues se chargent avec list et scope: { kind: "subtree", parentId: null }.
Réponse de aggregate (optionnelle)
aggregate est utilisée par les calculs de footer pour calculer sur l'ensemble global filtré (pas uniquement la page courante).
{
results: Record<
string,
{
raw: number | string | null;
label: string;
}
>;
meta?: { totalCount?: number };
}Notes :
calculationsdans les params est une mapcolumnId -> CalculationType.Si
aggregaten'est pas fournie, la table bascule en fallback sur des appelslistpaginés.Vous pouvez retourner un libellé custom depuis l'API (
label) tout en gardant la valeur exploitable dansraw.
Groupes des graphiques
La vue Graphique appelle le même aggregate avec la requête de la vue, une map calculations vide et quatre paramètres de plus : groupBy: [{ columnId, bucket? }] (au plus deux niveaux, l'axe X puis la série ; bucket vaut "day", "week", "month", "quarter" ou "year" pour les colonnes date), metrics: [{ columnId?, fn }] (fn vaut "count", "sum", "avg", "min", "max" ou "countDistinct"), timeZone et weekStartsOn. Répondez { groups: [{ keys, values }], truncated? }, où keys suit groupBy (null pour les valeurs vides ; YYYY-MM-DD pour les jours, le premier jour de la semaine en YYYY-MM-DD, YYYY-MM pour les mois, YYYY-Qn pour les trimestres, YYYY pour les années) et values suit metrics. results est facultatif dans le type de la réponse. Sans groups, ou si aggregate échoue, le graphique regroupe dans le navigateur les lignes de list (avec une limite et un avertissement) : les hôtes existants continuent de fonctionner. Voir Regroupement côté serveur.
Actions bulk
DataTable utilise ces actions par défaut si vous ne fournissez pas de callbacks explicites :
onBulkEdit→bulkUpdatewhen available, otherwise individualupdateactionsonBulkDelete→bulkDeletewhen available, otherwise individualdeleteactionsonBulkCopy→bulkCopywhen available, otherwise individualduplicateactionsonBulkExport→ export CSV interne des lignes sélectionnées (côté client)
Contrat recommandé pour les callbacks bulk
Pour éviter les branches ambiguës, retournez un objet de résultat explicite depuis vos callbacks bulk :
type BulkActionResult = {
success: boolean;
closeMenu: boolean;
clearSelection: boolean;
message?: string;
};Exemple :
onBulkDelete: async (rows) => {
const ids = rows.map((row) => String((row.original as { id: string }).id));
const response = await deleteManyProducts(ids);
return {
success: response.success,
closeMenu: response.success,
clearSelection: response.success,
message: response.success
? `Deleted ${ids.length} products`
: response.error ?? "Delete failed",
};
};Voir aussi :