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.
// 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 :
<?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 :
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 :
// 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 :
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 :
// 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 :
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
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
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.