---
title: "Extrafields et formulaires dynamiques"
weight: 990
---

# 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.

```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 :

```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 :

```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 :

```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 :

```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 :

```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 :

```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

```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

```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 :

1. **Configurer** via l'admin Dolibarr quels champs exposer
2. **Mapper** automatiquement avec le constructeur dmGenericObject
3. **Exposer** les métadonnées via objectDesc()
4. **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.

[← Mappers](/training/module8-backend-api/mappers) | [Module 9 : Intégration ->](/training/module9-integration)
