Extrafields et formulaires dynamiques
Les extrafields Dolibarr permettent d'ajouter des champs personnalisés aux objets métier. SmartMaker offre un système complet pour les exposer à l'application React avec génération automatique de formulaires.
Vue d'ensemble
Le système extrafields SmartMaker fonctionne en trois étapes :
1. Admin Dolibarr : Configure quels extrafields exposer (RO/RW)
↓
2. Mapper PHP : Lit la configuration, expose les métadonnées
↓
3. React : Génère dynamiquement les formulaires
1. Configuration admin
Page de configuration
SmartBoot génère une page d'administration admin/smartmaker.php qui permet de configurer les extrafields exposés à l'application.
- snippet.php
// Définir les objets Dolibarr à configurer $elementsToConfig = array( 'fichinter' => 'Interventions', 'societe' => 'Tiers', 'projet_task' => 'Tâches', );
Cette page affiche tous les extrafields de chaque objet avec deux colonnes :
| Option | Description |
|---|---|
| RO (Read-Only) | Le champ est visible dans l'application mais non modifiable |
| RW (Read-Write) | Le champ est visible ET modifiable |
Stockage de la configuration
La configuration est stockée dans les constantes Dolibarr :
MONMODULE_SMARTMAKER_EXTRAFIELDS_RO = "champ1,champ2,champ3" MONMODULE_SMARTMAKER_EXTRAFIELDS_RW = "champ4,champ5"
2. Mapper PHP (dmGenericObject)
Structure de base
Le mapper hérite de dmBase et utilise dmTrait pour la gestion des extrafields :
- snippet.php
<?php namespace MonModule\Api; use SmartAuth\DolibarrMapping\dmBase; use SmartAuth\DolibarrMapping\dmTrait; class dmItem extends dmBase { use dmTrait; // Classe Dolibarr source protected $parentClassName = 'Fichinter'; // Element pour les extrafields (table llx_extrafields) protected $parentElementToUseForExtraFields = 'fichinter'; // Mapping des champs standards protected $listOfPublishedFields = [ 'rowid' => 'id', 'ref' => 'ref', 'description' => 'description', 'fk_statut' => 'status', ]; // Champs modifiables via API protected $writableFields = []; }
Chargement automatique des extrafields
Le constructeur charge la configuration depuis Dolibarr :
- snippet.php
public function __construct() { global $db; $this->db = $db; // Extrafields en lecture seule $extRO = getDolGlobalString('MONMODULE_SMARTMAKER_EXTRAFIELDS_RO'); if (!empty($extRO)) { foreach (explode(',', $extRO) as $field) { $field = trim($field); if (!empty($field)) { $key = 'options_' . $field; $this->listOfPublishedFields[$key] = $key; } } } // Extrafields en lecture-écriture $extRW = getDolGlobalString('MONMODULE_SMARTMAKER_EXTRAFIELDS_RW'); if (!empty($extRW)) { foreach (explode(',', $extRW) as $field) { $field = trim($field); if (!empty($field)) { $key = 'options_' . $field; $this->listOfPublishedFields[$key] = $key; $this->writableFields[] = $key; } } } $this->boot(); }
Méthode objectDesc()
La méthode objectDesc() (fournie par dmTrait) retourne les métadonnées des champs pour le frontend :
- snippet.php
// Dans le controller $mapping = new dmItem(); $config = $mapping->objectDesc(); // Retourne un tableau avec les infos de chaque champ : // [ // 'options_niveau' => [ // 'type' => 'select', // 'label' => 'Niveau', // 'required' => true, // 'options' => ['1' => 'Facile', '2' => 'Moyen', '3' => 'Difficile'], // 'writable' => true, // ], // ... // ]
3. Controller : Exposer la configuration
Le controller doit retourner la configuration au frontend :
- snippet.php
class ItemController { private $mapping; public function __construct() { $this->mapping = new dmItem(); } // GET /items (liste + config) public function index($arr = null) { // ... charger les items ... return [[ 'statusCode' => 200, 'items' => $items, 'config' => $this->mapping->objectDesc(), // Métadonnées des champs ], 200]; } // GET /items/123 (détail + config) public function show($arr = null) { $id = $arr['id'] ?? 0; // ... charger l'item ... return [[ 'statusCode' => 200, 'item' => $this->mapping->map($dolibarrObject), 'config' => $this->mapping->objectDesc(), ], 200]; } }
4. Frontend : Formulaires dynamiques
Stockage de la configuration
Stocker la configuration reçue de l'API dans Redux ou le state local :
- snippet.jsx
// Dans le composant ou via Redux const [config, setConfig] = useState({}); useEffect(() => { api.private.get('items') .then(response => { setConfig(response.config || {}); // ... traiter les items ... }); }, []);
FormComponentsMap
SmartCommon fournit FormComponentsMap qui mappe les types Dolibarr vers les composants React :
- snippet.jsx
import { FormComponentsMap } from "@cap-rel/smartcommon"; // FormComponentsMap retourne le composant approprié selon le type : // 'varchar' → Input // 'text' → Textarea // 'int' → Input type="number" // 'double' → Input type="number" step="0.01" // 'date' → DatePicker // 'datetime' → DateTimePicker // 'boolean' → Checkbox // 'select' → Select // 'sellist' → Select (avec options depuis la DB) // 'radio' → RadioGroup // 'checkbox' → CheckboxGroup // 'link' → SearchSelect (lien vers autre objet)
Génération dynamique du formulaire
- snippet.jsx
const DynamicForm = ({ config, values, onChange }) => { return ( <div className="flex flex-col gap-4"> {Object.entries(config).map(([fieldName, fieldConfig]) => { // Récupérer le composant selon le type const Component = FormComponentsMap(fieldConfig.type); if (!Component) return null; return ( <Component key={fieldName} label={fieldConfig.label} value={values[fieldName] || ''} onChange={(val) => onChange(fieldName, val)} required={fieldConfig.required} disabled={!fieldConfig.writable} options={fieldConfig.options} // Pour select/radio /> ); })} </div> ); };
Exemple complet avec useApi
- snippet.jsx
import { useStates, useApi, Page, Button, Block } from "@cap-rel/smartcommon"; import { FormComponentsMap } from "@cap-rel/smartcommon"; export const ItemEditPage = ({ itemId }) => { const { states, set } = useStates({ item: null, config: {}, isLoading: true, isSaving: false, }); const { item, config, isLoading, isSaving } = states; const api = useApi(); // Charger l'item et sa configuration useEffect(() => { api.private.get(`items/${itemId}`) .then(response => { set("item", response.item); set("config", response.config); }) .finally(() => set("isLoading", false)); }, [itemId]); // Modifier une valeur const handleFieldChange = (fieldName, value) => { set("item", { ...item, [fieldName]: value }); }; // Sauvegarder const handleSave = () => { set("isSaving", true); api.private.put(`items/${itemId}`, item) .then(() => { // Succès }) .finally(() => set("isSaving", false)); }; if (isLoading) return <div>Chargement...</div>; return ( <Page> <Block title="Modifier l'item"> {/* Champs standards */} <Input label="Référence" value={item?.ref || ''} disabled /> {/* Extrafields dynamiques */} {Object.entries(config).map(([fieldName, fieldConfig]) => { // Ne montrer que les extrafields (commencent par options_) if (!fieldName.startsWith('options_')) return null; const Component = FormComponentsMap(fieldConfig.type); if (!Component) return null; return ( <Component key={fieldName} label={fieldConfig.label} value={item?.[fieldName] || ''} onChange={(val) => handleFieldChange(fieldName, val)} disabled={!fieldConfig.writable} required={fieldConfig.required} options={fieldConfig.options} /> ); })} <Button onClick={handleSave} loading={isSaving} > Enregistrer </Button> </Block> </Page> ); };
Types d'extrafields supportés
| Type Dolibarr | Composant React | Notes |
|---|---|---|
| varchar | Input | Texte court |
| text | Textarea | Texte long |
| int | Input (number) | Entier |
| double | Input (number) | Décimal avec step |
| date | DatePicker | Date seule |
| datetime | DateTimePicker | Date et heure |
| boolean | Checkbox | Oui/Non |
| select | Select | Liste déroulante |
| sellist | Select | Liste depuis SQL |
| radio | RadioGroup | Boutons radio |
| checkbox | CheckboxGroup | Cases à cocher multiples |
| link | SearchSelect | Lien vers objet Dolibarr |
| price | Input (number) | Prix avec formatage |
Bonnes pratiques
Sécurité
- Ne jamais exposer d'extrafields sensibles (mots de passe, tokens)
- Vérifier les permissions côté backend avant modification
- Valider les données reçues du frontend
Performance
- Mettre en cache la configuration si elle ne change pas souvent
- Ne charger que les extrafields nécessaires
UX
- Grouper les extrafields par catégorie si nombreux
- Utiliser des labels clairs et traduits
- Afficher les champs obligatoires distinctement
Résumé
Le système extrafields SmartMaker permet de :
- Configurer via l'admin Dolibarr quels champs exposer
- Mapper automatiquement avec le constructeur dmGenericObject
- Exposer les métadonnées via objectDesc()
- Générer dynamiquement les formulaires React avec FormComponentsMap
Ce système évite de modifier le code à chaque ajout d'extrafield : la configuration admin suffit.