À partir de 1.0.0-beta.15
, FormKit inclut un plugin officiel de première partie qui crée un type d'entrée multi-step
.
Bien qu'il soit toujours utile de comprendre comment construire une entrée en plusieurs étapes par vous-même — si vous cherchez le moyen le plus simple d'utiliser une entrée en plusieurs étapes dans votre projet, consultez le plugin officiel FormKit multi-step — il est gratuit et open-source !
Peu d'interactions sur le web sont aussi désagréables que de se retrouver face à un grand formulaire intimidant. Les formulaires en plusieurs étapes — parfois appelés "wizards" — peuvent atténuer cette douleur en divisant un grand formulaire en petites étapes plus abordables — mais ils peuvent aussi être compliqués à construire.
Dans ce guide, nous allons vous montrer comment construire un formulaire en plusieurs étapes avec FormKit et comment nous pouvons offrir une expérience utilisateur supérieure avec un minimum de code. Commençons !
Ce guide suppose que vous êtes familiarisé avec la Vue Composition API.
Commençons par définir les exigences de notre formulaire en plusieurs parties :
D'abord, créons un formulaire basique sans étapes pour avoir du contenu avec lequel travailler. Notre exemple sera une fausse demande pour recevoir une subvention, nous organiserons donc le formulaire en 3 sections — "Informations de contact", "Informations sur l'organisation" et "Demande". Ces sections deviendront les étapes complètes du formulaire plus tard.
Nous inclurons un mélange de règles de validation pour chaque entrée, et nous limiterons chaque section à une question pour l'instant jusqu'à ce que nous ayons la structure complète en place. Enfin, pour les besoins de ce guide, nous afficherons les données du formulaire collectées en bas de chaque exemple :
Maintenant que nous avons une structure définie, divisons le formulaire en sections distinctes.
Pour commencer, enveloppons chaque section d'entrées avec un groupe (<FormKit type="group" />
) afin que nous puissions valider chaque groupe indépendamment. Les groupes FormKit sont puissants car ils sont conscients de l'état de validation de tous leurs descendants sans affecter le balisage de votre formulaire.
Un groupe lui-même devient valide lorsque tous ses enfants (et leurs enfants) sont valides :
<!-- Seul un groupe est montré ici pour plus de brièveté -->
<FormKit type="group" name: "contactInfo">
<FormKit type="email" label="*Adresse e-mail" validation="required|email" />
</FormKit>
...
Dans notre cas, nous allons également vouloir un balisage HTML enveloppant. Mettons chaque groupe dans une section "étape" que nous pouvons montrer et cacher conditionnellement :
<!-- Seul un groupe est montré ici pour plus de brièveté -->
<section v-show="step === 'contactInfo'">
<FormKit type="group" name: "contactInfo">
<FormKit type="email" label="*Adresse e-mail" validation="required|email" />
</FormKit>
</section>
...
Ensuite, introduisons une interface utilisateur de navigation pour que nous puissions basculer entre chaque étape :
// pour l'instant, définissons manuellement les noms des étapes
const stepNames = ['contactInfo', 'organizationInfo', 'application']
<!-- Mettre en place une interface utilisateur de navigation par onglets. Au clic, changer d'étape -->
<ul class="steps">
<li
v-for="stepName in stepNames"
class="step"
@click="step = stepName"
:data-step-active="step === stepName"
>
{{ camel2title(panel) }}
</li>
</ul>
Voici à quoi cela ressemble une fois assemblé :
Les CSS pour les formulaires en plusieurs étapes — comme les onglets dans cet exemple — ne sont pas inclus dans le thème par défaut Genesis. Les styles ont été écrits sur mesure pour cet exemple et vous devrez fournir les vôtres.
Ça commence à ressembler à un vrai formulaire en plusieurs étapes ! Il reste cependant du travail à faire car nous avons quelques problèmes :
Réglons le premier problème.
FormKit suit déjà la validité du group
par défaut. Nous devrons simplement capturer ces données pour pouvoir les utiliser dans notre interface utilisateur.
Un concept important à retenir à propos de FormKit est que chaque composant <FormKit>
a un noeud central correspondant, qui a lui-même un objet node.context
réactif. Cet objet context
suit la validité du noeud dans context.state.valid
. Comme mentionné ci-dessus, un group
devient valide lorsque tous ses descendants sont valides. Avec cela à l'esprit, construisons un objet qui stocke la validité réactive de chacun des groupes.
Nous allons utiliser la fonctionnalité plugin de FormKit pour faire ce travail. Bien que le terme "plugin" puisse sembler intimidant, les plugins dans FormKit ne sont que des fonctions de configuration qui sont appelées lorsqu'un noeud est créé. Les plugins sont hérités par tous les descendants (comme les enfants au sein d'un groupe).
Voici notre plugin personnalisé, appelé stepPlugin
:
// notre plugin et notre code de modèle feront usage de 'steps'
const steps = reactive({})
const stepPlugin = (node) => {
// ne fonctionne que pour <FormKit type="group" />
if (node.props.type == 'group') {
// construire notre objet steps
steps[node.name] = steps[node.name] || {}
// ajouter la validité réactive du groupe actuel
node.on('created', () => {
steps[node.name].valid = toRef(node.context.state, 'valid')
})
// Arrêter l'héritage du plugin aux noeuds descendants.
// Nous ne nous soucions que des groupes de niveau supérieur
// qui représentent les étapes.
return false
}
}
L'objet réactif steps
résultant de notre plugin ci-dessus ressemble à ceci :
{
contactInfo: { valid: false },
organizationInfo: { valid: false }
application: { valid: false }
}
Pour utiliser notre plugin, nous l'ajouterons à notre formulaire racine <FormKit type="form" />
. Cela signifie que chaque groupe de niveau supérieur dans notre formulaire héritera du plugin :
<FormKit type="form" :plugins="[stepPlugin]"> ... reste du formulaire </FormKit>
Maintenant que notre modèle a accès en temps réel à l'état de validité de chaque groupe via notre plugin, écrivons l'interface utilisateur pour montrer ces données dans la barre de navigation des étapes.
Nous n'avons plus besoin de définir manuellement nos étapes puisque notre plugin stocke dynamiquement le nom de tous les groupes dans l'objet steps
. Ajoutons un attribut data-step-valid="true"
à chaque étape si elle est valide afin que nous puissions la cibler avec CSS :
<ul class="steps">
<li
v-for="(step, stepName) in steps"
class="step"
@click="activeStep = stepName"
:data-step-valid="step.valid"
:data-step-active="activeStep === stepName"
>
{{ camel2title(stepName) }}
</li>
</ul>
Avec ces mises à jour, notre formulaire est maintenant capable d'informer un utilisateur lorsqu'ils ont correctement rempli tous les champs d'une étape donnée !
Nous apporterons également quelques autres améliorations :
activeStep
.L'affichage des erreurs est plus nuancé. Bien que l'utilisateur ne le sache peut-être pas, il existe en réalité 2 types d'erreurs que nous devons gérer et communiquer à l'utilisateur :
messages
de type validation
)messages
de type error
)FormKit utilise son magasin de messages pour suivre ces deux types d'erreurs/messages.
Avec notre plugin déjà en place, il est relativement simple d'ajouter un suivi pour les deux :
const stepPlugin = (node) => {
...
// Stocker ou mettre à jour le nombre de messages de validation bloquants.
// FormKit émet l'événement "count:blocking" (avec le compte) chaque fois
// que le compte change.
node.on('count:blocking', ({ payload: count }) => {
steps[node.name].blockingCount = count
})
// Stocker ou mettre à jour le nombre de messages d'erreur backend.
node.on('count:errors', ({ payload: count }) => {
steps[node.name].errorCount = count
})
...
}
FormKit fait une distinction entre les messages de validation frontend (messages
de type validation
), et les erreurs (messages
de type error
).
Mettons à jour notre exemple pour montrer les deux types d'erreurs avec les exigences suivantes :
Comme "flouter un groupe" n'existe pas en HTML, nous l'introduirons dans notre plugin avec un tableau appelé visitedSteps
. Voici le code pertinent :
import { watch } from 'vue'
import { getNode, createMessage } from '@formkit/core'
const stepPlugin = (node) => {
...
const activeStep = ref('')
const visitedSteps = ref([]) // suivre les étapes visitées
// Surveillez notre activeStep et stockez les étapes visitées
watch(activeStep, (newStep, oldStep) => {
if (oldStep && !visitedSteps.value.includes(oldStep)) {
visitedSteps.value.push(oldStep)
}
// Déclencher l'affichage de la validation sur les champs si un groupe a été visité
visitedSteps.value.forEach((step) => {
const node = getNode(step)
// la méthode node.walk() parcourt tous les descendants du nœud actuel
// et exécute la fonction fournie.
node.walk((n) => {
n.store.set(
createMessage({
key: 'submitted',
value: true,
visible: false
})
)
})
})
})
...
}
Vous vous demandez peut-être pourquoi nous parcourons tous les descendants d'une étape donnée (node.walk()
) et créons des messages avec une clé de submitted
et une valeur de true
? Lorsqu'un utilisateur tente de soumettre un formulaire, c'est ainsi que FormKit s'informe que toutes les entrées sont dans un état submitted
. Dans cet état, FormKit force l'apparition de tous les messages de validation bloquants. Nous déclenchons manuellement la même chose dans notre événement "flou de groupe".
Nous utiliserons la même interface utilisateur pour les deux types d'erreurs car les utilisateurs finaux ne se soucient pas vraiment de la distinction. Voici notre HTML de l'étape mise à jour, qui affiche une bulle rouge avec le total cumulé des erreurs errorCount
+ blockingCount
:
<li v-for="(step, stepName) in steps" class="step" ...>
<span
v-if="checkStepValidity(stepName)"
class="step--errors"
v-text="step.errorCount + step.blockingCount"
/>
{{ camel2title(stepName) }}
</li>
Nous sommes presque à la ligne d'arrivée ! Voici notre formulaire actuel - qui peut maintenant indiquer à un utilisateur quand il a correctement ou incorrectement rempli chaque étape :
La dernière pièce du puzzle est la soumission du formulaire et la gestion des erreurs que nous recevons du serveur backend. Nous simulerons le backend pour les besoins de ce guide.
Nous soumettons le formulaire en ajoutant un gestionnaire @submit
à <FormKit type="form">
:
<FormKit type="form" @submit="submitApp"> ... reste du formulaire</FormKit>
Et voici notre gestionnaire de soumission :
const submitApp = async (formData, node) => {
try {
const res = await axios.post(formData)
node.clearErrors()
alert('Votre demande a été soumise avec succès !')
} catch (err) {
node.setErrors(err.formErrors, err.fieldErrors)
}
}
Notez que FormKit passe à notre gestionnaire de soumission 2 arguments utiles : les données du formulaire dans un seul objet prêt à être demandé (que nous appelons formData
), et le node
de base du formulaire, que nous pouvons utiliser pour effacer les erreurs ou définir les erreurs retournées en utilisant les aides node.clearErrors()
et node.setErrors()
, respectivement.
setErrors()
prend 2 arguments : les erreurs au niveau du formulaire et les erreurs spécifiques au champ. Notre faux backend renvoie la réponse err
que nous utilisons pour définir les erreurs.
Alors, que se passe-t-il si l'utilisateur est à l'étape 3 (Application) lorsqu'il soumet, et qu'il y a des erreurs au niveau du champ sur une étape cachée ? Heureusement, tant que les nœuds existent dans le DOM, FormKit est capable de placer ces erreurs de manière appropriée. C'est pourquoi nous avons utilisé un v-show
pour les étapes au lieu de v-if
- Le nœud DOM doit exister pour avoir des erreurs définies sur le nœud FormKit correspondant.
Et Voilà! 🎉 Nous avons terminé! En plus de notre gestionnaire de soumission, nous avons ajouté quelques autres embellissements UI et UX à ce formulaire final pour le rendre plus réel :
utils.js
qui renvoie des erreurs.valid
.Le voici — un formulaire multi-étapes entièrement fonctionnel :
Bien sûr, il y a toujours des moyens d'améliorer quoi que ce soit, et ce formulaire ne fait pas exception. Voici quelques idées :
window.localStorage
afin que l'état du formulaire d'un utilisateur soit conservé même s'il quitte accidentellement.Nous avons couvert beaucoup de sujets dans ce guide et espérons que vous en avez appris davantage sur FormKit et comment l'utiliser pour faciliter les formulaires multi-étapes!