Thème
Configuration avec JSON (CrudJsonConfigPage)
La librairie propose un composant dédié, CrudJsonConfigPage, qui permet d'initialiser une interface CRUD dynamique complète à partir d'un fichier JSON distant fourni par votre serveur backend.
Cette fonctionnalité est idéale pour construire des back-offices totalement déclaratifs pilotés par le serveur sans nécessiter de recompilation du code frontend lors d'évolutions de formulaires, de colonnes ou d'actions.
Sommaire
- 1. Utilisation
- 2. Le Schéma JSON (
CrudJsonConfig) - 3. Types d'Actions Supportées
- 4. Filtrage d'Emplacements (
locations) dans le JSON - 5. Transfert de Slots et Personnalisation
1. Utilisation
Côté Vue, le code nécessaire est minimal. Le composant télécharge la configuration JSON depuis l'URL spécifiée, la valide et la transforme de manière transparente en une instance de <CrudPage>.
vue
<template>
<CrudJsonConfigPage configUrl="https://api.example.com/crud/users.json" />
</template>
<script setup lang="ts">
import { CrudJsonConfigPage } from '@codifyo/vue-crud';
</script>2. Le Schéma JSON (CrudJsonConfig)
Le fichier JSON distant doit respecter la structure CrudJsonConfig suivante :
json
{
"title": "Gestion des Produits",
"itemName": "Produit",
"idKey": "id",
"listEndpoint": "/api/products",
"enableBulkActions": true,
"columns": [
{ "key": "id", "label": "ID", "type": "text", "sortable": true },
{ "key": "reference", "label": "Référence", "type": "badge" },
{ "key": "price", "label": "Prix (€)", "type": "text", "sortable": true },
{ "key": "status", "label": "Statut", "type": "badge" }
],
"options": {
"perPage": 20,
"perPageChoices": [10, 20, 50, 100],
"syncUrl": true,
"columnChooserStorageKey": "crud_products_cols",
"allowedActionsKey": "_allowed_actions"
},
"actions": {
"item": [
{
"name": "view",
"label": "Détails",
"icon": "fas fa-eye",
"type": "view",
"displayMode": "modal",
"viewConfig": {
"sections": [
{
"title": "Informations Produit",
"size": 6,
"properties": [
{ "key": "name", "label": "Nom", "type": "text" },
{ "key": "reference", "label": "Réf", "type": "badge" }
]
}
]
}
},
{
"name": "delete",
"label": "Supprimer",
"icon": "fas fa-trash",
"style": "danger",
"type": "confirm",
"confirmMessage": "Voulez-vous vraiment supprimer ce produit ?",
"url": "/api/products/{id}",
"method": "DELETE"
}
],
"global": [
{
"name": "create",
"label": "Nouveau Produit",
"icon": "fas fa-plus",
"style": "primary",
"type": "form",
"displayMode": "modal",
"fetchUrl": "/api/products/form-config",
"submitUrl": "/api/products",
"method": "POST"
}
]
}
}Propriétés racines du JSON
listEndpoint(Requis) : L'URL HTTP appelée enGETpour récupérer la liste. Les paramètres?page=X&limit=Y&sort=key&dir=ascy sont automatiquement ajoutés par le composable.columns: Tableau de configuration des colonnes (key,label,type,sortable,defaultSort,nl2br,iframe, etc.).options: Options globales (perPage,defaultSort,defaultSortDir,syncUrl,columnChooserStorageKey,allowedActionsKey, etc.).defaultSort/default_sort(string) : Clé de la colonne à trier par défaut (ex:"createdAt"ou"id").defaultSortDir/default_sort_dir("asc"|"desc", défaut :"asc") : Direction du tri par défaut.- Astuce : Vous pouvez aussi définir
defaultSort: true(ou"asc"/"desc") directement sur l'objet d'une colonne dans le tableaucolumns.
3. Types d'Actions Supportées
La puissance de CrudJsonConfigPage réside dans le champ type de chaque action. Le système transforme automatiquement chaque déclaration JSON en gestionnaires de requêtes réseau et de modales.
Action Confirmation (confirm)
Affiche une modale native demandant confirmation à l'utilisateur avant d'exécuter une requête HTTP (DELETE/POST).
json
{
"name": "delete",
"label": "Supprimer",
"icon": "fas fa-trash",
"style": "danger",
"type": "confirm",
"confirmMessage": "Voulez-vous vraiment supprimer cet élément ?",
"url": "/api/products/{id}",
"method": "DELETE",
"onSuccessRefreshCurrentPage": true,
"onSuccessRefreshList": false,
"onSuccessInvalidateList": true
}Note : La variable {id} dans l'URL est automatiquement interpolée avec la valeur réelle de idKey de la ligne.
Actions globales et bulk, transmission des filtres (includeFilters)
Une action confirm fonctionne aussi en global (sans ligne) et en bulk :
- bulk : les identifiants sélectionnés sont ajoutés à l'URL :
?ids[]=1&ids[]=2. - URL avec placeholders sans élément : une action dont l'
urlcontient{...}déclenchée sans ligne (ex :triggerAction('delete')) est refusée avec un message d'erreur, aucune requête n'est envoyée. includeFilters/include_filters(boolean, défaut :false) : ajoute le tri et les filtres courants de la liste à l'URL, quelle que soit la méthode HTTP (GET, POST, PUT, PATCH, DELETE), au même format quelistEndpoint:?sort=name&dir=asc&filters[status]=published. Les paramètres déjà présents dansurlsont conservés (les nouveaux sont ajoutés à la suite).
json
{
"name": "export",
"label": "Exporter",
"icon": "fas fa-file-export",
"type": "confirm",
"url": "/api/products/export",
"method": "POST",
"includeFilters": true,
"confirmMessage": "Exporter les produits filtrés ?"
}includeFilters s'applique aussi aux actions form et view : les filtres et le tri sont ajoutés à fetchUrl et submitUrl.
Options de rafraîchissement post-succès :
Supporte à la fois la convention camelCase (recommandée) et snake_case (on_success_*) :
onSuccessRefreshCurrentPage/on_success_refresh_current_page(boolean, défaut :true) : Rafraîchit automatiquement la page active (ex: la vue courante).onSuccessRefreshList/on_success_refresh_list(boolean, défaut :false) : Force un appel HTTP immédiat pour recharger la liste.onSuccessInvalidateList/on_success_invalidate_list(boolean, défaut :true) : Invalide le cache de la liste (en mode page, recharge automatiquement la liste dès que l'utilisateur y revient).
Action Formulaire Distant (form)
Ouvre une modale ou une page téléchargeant la configuration du formulaire depuis fetchUrl et le soumettant vers submitUrl.
json
{
"name": "edit",
"label": "Modifier",
"icon": "fas fa-pen",
"style": "primary",
"type": "form",
"displayMode": "modal",
"fetchUrl": "/api/products/{id}/form-schema",
"submitUrl": "/api/products/{id}",
"method": "PUT",
"onSuccessAction": "view",
"onSuccessRefreshCurrentPage": true,
"onSuccessRefreshList": false,
"onSuccessInvalidateList": true
}Astuce : onSuccessAction permet de rediriger automatiquement vers une autre action (ici view) une fois le formulaire soumis avec succès. L'objet JSON renvoyé par l'API sera passé à cette nouvelle action.
Action Vue / Détails (view)
Ouvre une vue détaillée basée sur la grille Bootstrap (sections avec size de 1 à 12).
json
{
"name": "view",
"label": "Aperçu",
"type": "view",
"displayMode": "modal",
"fetchUrl": "/api/products/{id}",
"viewConfig": {
"sections": [
{
"title": "Infos Générales",
"size": 6,
"properties": [
{ "key": "name", "label": "Nom", "type": "text" },
{ "key": "price", "label": "Prix", "type": "text" }
]
},
{
"size": 6,
"tabs": [
{
"key": "tab-details",
"label": "Détails",
"icon": "fas fa-info",
"properties": [
{ "key": "description", "label": "Description", "type": "text" }
]
},
{
"key": "tab-sub",
"label": "Sous-sections",
"sections": [
{ "title": "Stocks", "size": 12, "properties": [{ "key": "stock", "label": "Stock" }] }
]
}
]
}
]
}
}Sections épurées (
bare) : Vous pouvez renseigner"bare": true(ou"card": false) sur une section pour désactiver le conteneur carte Bootstrap (pas de bordure, pas de fond de carte, et suppression du header de section).Note sur les sections sans titre : Si vous ne spécifiez pas de
titlesur une section, l'en-tête de carte (card-header) n'est pas affiché, et seul le corps de carte (card-body) est rendu.Système d'onglets (
tabs) : Vous pouvez définirtabsdirectement à la racine deviewConfigou à l'intérieur d'une section. Chaque onglet (CrudViewTab) peut contenir lui-même desproperties, dessections, ou des sous-onglets (tabs).
Action Édition en Ligne (inline-edit)
Bascule la ligne en mode édition interactive dans le tableau :
json
{
"name": "inline-edit",
"label": "Édition Rapide",
"type": "inline-edit",
"submitUrl": "/api/products/{id}",
"method": "PATCH",
"formFields": {
"status": {
"type": "choice",
"options": {
"choices": [
{ "label": "En stock", "value": "in_stock" },
{ "label": "Rupture", "value": "out_of_stock" }
]
}
}
},
"inlineOptions": {
"saveLabel": "Valider",
"saveStyle": "success",
"cancelLabel": "Annuler",
"cancelStyle": "secondary"
}
}4. Filtrage d'Emplacements (locations) dans le JSON
Vous pouvez spécifier le tableau "locations" dans n'importe quelle action JSON pour contrôler où son bouton apparaît :
json
{
"name": "print-invoice",
"label": "Imprimer Facture",
"type": "confirm",
"locations": ["view"],
"url": "/api/invoices/{id}/print"
}"locations": ["view"]: Visible uniquement dans le header de l'actionview."locations": ["list"]: Visible uniquement dans la liste."locations": ["dropdown-xxxx"]: Visible à l'intérieur du dropdown portant le nom ou la clé"xxxx"."locations": []: Masqué de toutes les barres d'outils automatiques (déclenchable viatriggerAction).
Actions de type Dropdown en JSON
Vous pouvez configurer un menu déroulant d'actions directement dans le JSON :
json
{
"name": "more",
"type": "dropdown",
"label": "Options",
"icon": "fas fa-ellipsis-v",
"style": "outline-secondary",
"locations": ["list"]
},
{
"name": "archive",
"type": "confirm",
"label": "Archiver",
"locations": ["dropdown-more"],
"url": "/api/users/{id}/archive"
}Mode d'affichage du bouton (button_display)
Par défaut, si une action possède une icône et un label, les deux sont affichés (both). Vous pouvez configurer button_display :
json
{
"name": "edit",
"label": "Modifier",
"icon": "fas fa-pencil",
"locations": ["list", "view", "dropdown-more"],
"button_display": {
"list": "icon",
"view": "both",
"dropdown-*": "both"
}
}Valeurs possibles : "both" (défaut), "icon", "text", "auto", ou un objet par emplacement.
5. Transfert de Slots et Personnalisation
Même lorsqu'il est piloté par un JSON distant, le composant CrudJsonConfigPage transmet tous ses slots au composant sous-jacent CrudPage.
Vous pouvez donc surcharger des cellules, des filtres ou des sections spécifiques dans votre Vue :
vue
<template>
<CrudJsonConfigPage configUrl="/api/crud/products.json">
<!-- Surcharge de la cellule 'price' dans le tableau -->
<template #cell-price="{ value }">
<strong class="text-success">{{ value }} €</strong>
</template>
<!-- Surcharge du slot de filtres -->
<template #filters="{ modelValue, updateModelValue }">
<MyCustomSearchInput :modelValue="modelValue" @update:modelValue="updateModelValue" />
</template>
<!-- Surcharge du label d'une propriété dans une vue détaillée -->
<template #view-label-status="{ label }">
<span class="text-primary font-weight-bold">{{ label }}</span>
<i class="fas fa-question-circle text-muted ml-1" title="Statut du produit"></i>
</template>
<!-- Surcharge complète (label + valeur) d'une propriété dans une vue détaillée -->
<template #view-prop-stock="{ label, value, item, refreshPage, ActionButton }">
<div class="border rounded p-2 bg-light">
<small class="text-muted d-block">{{ label }}</small>
<span :class="value > 0 ? 'text-success' : 'text-danger font-weight-bold'">{{ value }} unités</span>
<ActionButton action="replenish" :item="item" class="ml-2" size="sm" />
</div>
</template>
</CrudJsonConfigPage>
</template>6. Surcharge des Handlers d'Actions (actionHandlers)
Vous pouvez intercepter et remplacer totalement le comportement de n'importe quelle action (par exemple pour effectuer une requête personnalisée) via la prop :actionHandlers.
Dans votre fichier JSON, déclarez l'action avec un type custom ou surchargez une action existante :
json
{
"name": "my-custom-confirm-action",
"label": "Action Spéciale",
"type": "custom",
"confirm": true,
"confirmMessage": "Êtes-vous sûr de vouloir déclencher cette action personnalisée ?"
}Puis dans votre composant Vue, définissez une fonction asynchrone pour l'intercepter via son name :
vue
<template>
<CrudJsonConfigPage
configUrl="/demo-config.json"
:actionHandlers="customHandlers"
/>
</template>
<script setup lang="ts">
const customHandlers = {
// context : { item?, ids?, formData?, filters?, sort?, dir? }
'my-custom-confirm-action': async (context: CrudActionHandlerContext) => {
const item = context.item;
if (!item) return;
// Requête HTTP totalement personnalisée
const response = await fetch(`/api/special-action/${item.id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason: "demande_speciale" })
});
if (!response.ok) {
throw new Error("Erreur personnalisée");
}
}
};
</script>Note : Les erreurs jetées via throw new Error() sont automatiquement attrapées par le CRUD qui affichera un toast d'erreur. Les exécutions réussies déclenchent un toast de succès et rechargent la liste.
6. Méthodes Exposées (defineExpose)
Tout comme <CrudPage>, <CrudJsonConfigPage> expose l'ensemble des méthodes de contrôle via sa ref (typée CrudExposedMethods) :
typescript
const crudRef = ref<InstanceType<typeof CrudJsonConfigPage> | null>(null);
// Recharger la liste depuis l'API
crudRef.value?.fetchList(true);
crudRef.value?.refreshList();
// Invalider la liste
crudRef.value?.invalidateList();
// Rafraîchir la vue ou page active
crudRef.value?.refreshPage();
// Déclencher une action JSON
crudRef.value?.triggerAction('edit', item);
crudRef.value?.closeAction();