Skip to content

Les layouts

Un layout sert à composer des FormField pour construire un formulaire plus riche.

Un input crée déjà un FormField, mais un layout crée lui aussi un FormField. La différence est la suivante :

  • un input gère une unité de saisie ;
  • un layout organise, transforme ou conditionne un ou plusieurs FormField ;
  • le formulaire final reste toujours piloté par un unique FormField racine.

Autrement dit, un layout est une brique de composition. Il permet de décrire la structure d'un formulaire sans sortir du modèle principal de la librairie.

Ce qu'apporte un layout

Un layout peut servir à :

  • grouper plusieurs champs dans un objet ;
  • répéter un même champ ou un même bloc ;
  • alterner entre plusieurs sous-formulaires ;
  • découper un formulaire en étapes ;
  • injecter du rendu personnalisé avec un slot ;
  • encapsuler un bloc avec un titre ou une présentation ;
  • ajouter une validation transverse ;
  • désactiver conditionnellement une partie du formulaire.

L'idée clé est la suivante : la structure du formulaire reste lisible et stable dans le code, mais le comportement peut devenir très dynamique.

Les layouts fournis

  • useMultiLayout compose plusieurs champs sous des clés connues.
  • useRepeatLayout répète un champ ou un sous-formulaire dans un tableau.
  • useUnionLayout permet de choisir une variante active parmi plusieurs branches.
  • useStepLayout découpe un formulaire en plusieurs étapes successives.
  • useSectionLayout encapsule un bloc dans une section avec titre.
  • useSlotLayout délègue une partie du rendu à un slot Vue.
  • useCheckLayout ajoute une validation supplémentaire autour d'un champ existant.
  • useDisabledLayout masque et neutralise conditionnellement un champ.

Pour la référence détaillée de chaque layout, consultez aussi l'index API des layouts.

Structure fixe, comportement dynamique

useMultiLayout est souvent le point d'entrée le plus utile, parce qu'il permet de définir une structure stable avec des clés explicites.

Dans l'exemple suivant, les clés identity, contacts, preferredChannel et message existent toujours. Ensuite, chaque clé peut contenir un input simple ou un autre layout.

ts
ts
import {
	
useMultiLayout
,
useRepeatLayout
,
useSectionLayout
,
useSlotLayout
,
useUnionLayout
,
} from "@duplojs/form/vue"; import {
useForm
} from "./init";
import {
useTextInput
,
useTextareaInput
,
} from "@duplojs/form/vueDesignSystem"; export function
useProfileLayoutForm
() {
const {
component
,
check
} =
useForm
(
useSectionLayout
(
useMultiLayout
({
identity
:
useMultiLayout
({
firstName
:
useTextInput
({
label
: "First Name" }),
lastName
:
useTextInput
({
label
: "Last Name" }),
}),
contacts
:
useRepeatLayout
(
useTextInput
({
label
: "Email" }),
{
min
: 1,
max
: 3,
}, ),
preferredChannel
:
useUnionLayout
(
[ ["email",
useTextInput
({
label
: "Email" })],
["phone",
useTextInput
({
label
: "Phone" })],
], {
defaultKind
: "email" },
),
message
:
useSlotLayout
(
"customMessage",
useTextareaInput
({
label
: "Message" }),
), }), {
title
: "Profile" },
), ); return {
TheForm
:
component
,
checkForm
:
check
,
}; }

Dans ce même exemple :

  • useSectionLayout encapsule tout le formulaire dans une section Profile ;
  • useMultiLayout structure les données par blocs ;
  • useRepeatLayout rend contacts dynamique ;
  • useUnionLayout fait varier le champ actif entre email et phone ;
  • useSlotLayout laisse reprendre précisément le rendu de message.

Ce modèle est utile pour les formulaires évolués : on garde une architecture prévisible, mais chaque bloc peut devenir adaptatif.

Implémentation Vue

vue
vue
<script setup lang="ts">
import * as EE from "@duplojs/utils/either";
import { PrimaryButton } from "@duplojs/form/vueDesignSystem";
import { useProfileLayoutForm } from "./profileLayoutForm";

const { TheForm, checkForm } = useProfileLayoutForm();

function onSubmit() {
	void EE.whenIsRight(
		checkForm(),
		(result) => {
			alert(JSON.stringify(result));
		},
	);
}
</script>

<template>
	<TheForm @submit="onSubmit">
		<PrimaryButton
			type="submit"
			label="Submit"
		/>

		<template #customMessage="{ formField, value, update }">
			<div class="DFV-grid-element">
				<p>Custom render with slot</p>

				<button
					type="button"
					@click="update('')"
				>
					reset
				</button>

				{{ value }}

				<component :is="formField" />
			</div>
		</template>
	</TheForm>
</template>

Le slot #customMessage montre bien le rôle de useSlotLayout :

  • la valeur courante est disponible dans value ;
  • vous pouvez la modifier avec update(...) ;
  • vous pouvez réinjecter le champ d'origine via formField.

Résultat

Custom render with slot

Layouts de contrôle

Tous les layouts ne servent pas seulement à la structure. Certains servent plutôt à contrôler le comportement du formulaire.

L'exemple suivant montre trois cas :

  • useStepLayout impose une progression par étapes ;
  • useCheckLayout ajoute une validation supplémentaire sur un champ existant ;
  • useDisabledLayout neutralise un champ tant qu'une condition externe n'est pas remplie.
ts
ts
import * as 
EE
from "@duplojs/utils/either";
import {
ref
} from "vue";
import {
useCheckLayout
,
useDisabledLayout
,
useMultiLayout
,
useStepLayout
,
} from "@duplojs/form/vue"; import {
useTextInput
,
useTextareaInput
,
} from "@duplojs/form/vueDesignSystem"; import {
useForm
} from "./init";
export function
useFlowLayoutForm
() {
const
hasCompany
=
ref
(false);
const {
component
,
check
,
currentValue
} =
useForm
(
useStepLayout
(
[
useMultiLayout
({
fullName
:
useTextInput
({
label
: "Full name" }),
email
:
useCheckLayout
(
useTextInput
({
label
: "Email" }),
{
refine
: (
value
) =>
value
.
includes
("@")
?
EE
.
ok
()
:
EE
.
error
("Email must contain @."),
}, ), }),
useMultiLayout
({
company
:
useDisabledLayout
(
useTextInput
({
label
: "Company" }),
{
isDisabled
: () => !
hasCompany
.
value
,
}, ),
notes
:
useTextareaInput
({
label
: "Notes" }),
}), ], {
errorMessageNotAtLastStep
: "Complete all steps before submitting.",
}, ), ); return {
FlowForm
:
component
,
checkFlowForm
:
check
,
currentFlowValue
:
currentValue
,
hasCompany
,
}; }

Ici :

  • email reste un simple input texte, mais useCheckLayout lui ajoute une règle métier locale ;
  • company reste dans la structure du formulaire, mais useDisabledLayout le masque et le retire du flux tant que hasCompany vaut false ;
  • useStepLayout transforme deux blocs en parcours séquentiel avec contrôle du changement d'étape.

Comment choisir un layout

  • Utilisez useMultiLayout quand la forme de l'objet final est connue à l'avance.
  • Utilisez useRepeatLayout quand vous avez une liste d'éléments homogènes.
  • Utilisez useUnionLayout quand une seule branche doit être active à la fois.
  • Utilisez useStepLayout quand l'ordre de saisie fait partie de l'expérience.
  • Utilisez useSectionLayout quand vous voulez clarifier visuellement un bloc.
  • Utilisez useSlotLayout quand le rendu standard ne suffit plus.
  • Utilisez useCheckLayout quand la validation dépend de plusieurs règles autour d'un champ déjà existant.
  • Utilisez useDisabledLayout quand un champ doit temporairement disparaître du formulaire.

Ce qu'il faut retenir

  • un layout retourne lui aussi un FormField ;
  • les layouts sont donc librement imbriquables ;
  • ils servent autant à structurer les données qu'à piloter le comportement ;
  • le formulaire final reste toujours défini par un seul arbre de FormField.

Released under the MIT License.