Aller au contenu

Guides

Specs de formulaire

Tout dans FormKrafter commence ici. Un formulaire est un spec : un simple arbre JSON que vous pouvez stocker en base, comparer dans une pull request, envoyer sur le réseau et confier à n’importe lequel des renderers, web ou natif. Rien n’y est spécifique à un framework, et rien n’y exige FormKrafter pour être lisible.

Chaque nœud de l’arbre — un champ, un panel, une étape de wizard — est un BrickSpec. Seules trois propriétés sont obligatoires :

interface BrickSpec {
type: 'panel' | 'input' | 'collection' | 'output' | 'action' // obligatoire
id: string // quelle brick enregistrée rend ce nœud
name: string // libellé humain, affiché dans le builder
dataType?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null' | 'void'
configs?: { key?: string } & Record<string, unknown>
styles?: Record<string, unknown>
validations?: Validation[]
rules?: Rule[]
children?: BrickSpec[]
category?: string
editable?: boolean
isPrivate?: boolean
}

La propriété que vous manipulez le plus vit dans configs : key, le nom du champ dans les données du formulaire. C’est ce qui se retrouve dans votre payload, et une brick sans key ne porte aucune donnée.

Un formulaire avec un champ texte et un select, tous deux obligatoires :

contact-form.json
{
"type": "panel",
"id": "column",
"name": "Contact",
"configs": { "key": "contact" },
"children": [
{
"type": "input",
"dataType": "string",
"id": "text",
"name": "Text",
"configs": { "key": "fullName", "label": "Nom complet" },
"validations": [{ "validator": "required" }]
},
{
"type": "input",
"dataType": "string",
"id": "select",
"name": "Select",
"configs": {
"key": "role",
"label": "Rôle",
"optionsSource": "static",
"options": "Développeur\nDesigner\nManager"
},
"validations": [{ "validator": "required" }]
}
]
}

Ce spec produit ces données :

{ "fullName": "Ada Lovelace", "role": "Développeur" }

Les données du formulaire sont plates — une entrée par key, quelle que soit la profondeur d’imbrication de la brick dans des panels, des étapes de wizard ou des onglets. La seule exception est une brick collection (comme data-grid), où chaque ligne constitue son propre enregistrement scopé :

{ "fullName": "Ada", "contacts": [{ "email": "a@b.c" }, { "email": "d@e.f" }] }

Ne mutez jamais un spec sur place. Chaque opération renvoie un nouveau spec plus les patches pour y aller et en revenir :

import { moveBrick, addBrick, SpecHistory } from '@streamline-pulse/formkrafter-core'
const update = moveBrick(spec, '0.0', '0.2.0')
// update.spec → le nouveau spec
// update.patches → les opérations RFC 6902 qui ont appliqué le changement
// update.inverse → les opérations RFC 6902 qui l'annulent

Les chemins sont des chaînes pointées enracinées en "0", exprimées en coordonnées d’avant suppression : "0.2.0" est le premier enfant du troisième enfant de la racine.

Opération Signature Comportement de fusion
addBrick (spec, brick, parentPath, index?)
removeBrick (spec, path) lève une erreur sur le chemin racine "0"
moveBrick (spec, from, to) lève une erreur si l’on déplace une brick dans son propre sous-arbre
duplicateBrick (spec, path)
updateBrickConfigs (spec, path, configs) fusionné dans les configs existantes
updateBrickStyles (spec, path, styles) fusionné
updateBrickValidations (spec, path, validations) remplacé intégralement
updateBrickRules (spec, path, rules) remplacé intégralement

Toutes les opérations adressent les bricks de la même façon, par chemin. Un chemin qui ne résout vers aucune brick lève une erreur au lieu de retomber sur la racine : une faute de frappe ne peut donc jamais modifier le mauvais nœud.

SpecHistory enregistre les mises à jour et rejoue leurs inverses :

  1. Enregistrez chaque mise à jour au fil de l’eau.

    const history = new SpecHistory()
    history.record(update)
  2. Vérifiez si un pas est possible — pratique pour désactiver les boutons de la barre d’outils.

    history.canUndo // boolean
    history.canRedo // boolean
  3. Reculez ou avancez d’un pas, en passant le spec courant.

    spec = history.undo(spec) ?? spec
    spec = history.redo(spec) ?? spec

Le builder fait exactement cela en interne, et c’est pourquoi son événement specChange transporte { spec, patches, inverse } — vous pouvez persister les patches, les rejouer sur un serveur, ou piloter votre propre pile d’historique.

import { iterateBricks, getBrickAt, pointerFromPath } from '@streamline-pulse/formkrafter-core'
for (const { brick, path } of iterateBricks(spec)) {
console.log(path, brick.configs?.key)
}
getBrickAt(spec, '0.1') // la brick au chemin pointé, ou undefined
pointerFromPath('0.1') // le JSON Pointer correspondant

Un projet de Streamline Pulse