A partir da versão 1.0.0-beta.15
, o FormKit inclui um plugin oficial de primeira parte que cria um tipo de entrada multi-step
.
Embora ainda haja valor em entender como construir uma entrada de várias etapas por conta própria — se você está procurando a maneira mais fácil de usar uma entrada de várias etapas em seu projeto, confira o plugin oficial de várias etapas do FormKit — é gratuito e de código aberto!
Poucas interações na web causam tanto desprazer quanto ser confrontado com um grande e intimidador formulário. Formulários de várias etapas — às vezes chamados de "wizards" — podem aliviar essa dor dividindo um grande formulário em etapas menores e mais abordáveis — mas eles também podem ser complicados de construir.
Neste guia, vamos percorrer a construção de um formulário de várias etapas com o FormKit e ver como podemos proporcionar uma experiência de usuário elevada com código mínimo. Vamos começar!
Este guia pressupõe que você esteja familiarizado com a API de Composição do Vue.
Vamos começar definindo os requisitos para nosso formulário de várias partes:
Primeiro, vamos criar um formulário básico sem etapas para que tenhamos conteúdo para trabalhar. Nosso exemplo será uma aplicação fictícia para receber uma bolsa, então organizaremos o formulário em 3 seções — "Informações de Contato", "Informações da Organização" e "Aplicação". Estes se tornarão as etapas completas do formulário mais tarde.
Incluiremos uma mistura de regras de validação para cada entrada, e limitaremos cada seção a 1 pergunta por enquanto até termos a estrutura completa no lugar. Por fim, para os propósitos deste guia, vamos exibir os dados coletados do formulário no final de cada exemplo:
Agora que temos uma estrutura definida, vamos dividir o formulário em seções distintas.
Para começar, vamos envolver cada seção de entradas com um grupo (<FormKit type="group" />
) para que possamos validar cada grupo independentemente. Os grupos FormKit são poderosos porque estão cientes do estado de validação de todos os seus descendentes sem afetar a marcação do seu formulário.
Um grupo se torna válido quando todos os seus filhos (e os filhos deles) são válidos:
<!-- Mostrando apenas um único grupo aqui por brevidade -->
<FormKit type="group" name: "contactInfo">
<FormKit type="email" label="*Endereço de email" validation="required|email" />
</FormKit>
...
No nosso caso, também vamos querer um HTML de envolvimento. Vamos colocar cada grupo em uma seção de "etapa" que podemos mostrar e ocultar condicionalmente:
<!-- Mostrando apenas um único grupo aqui por brevidade -->
<section v-show="step === 'contactInfo'">
<FormKit type="group" name: "contactInfo">
<FormKit type="email" label="*Endereço de email" validation="required|email" />
</FormKit>
</section>
...
Em seguida, vamos introduzir uma interface de usuário de navegação para que possamos alternar entre cada etapa:
// por enquanto, defina manualmente os nomes das etapas
const stepNames = ['contactInfo', 'organizationInfo', 'application']
<!-- Configure a interface de usuário de navegação por abas. Ao clicar, mude a etapa -->
<ul class="steps">
<li
v-for="stepName in stepNames"
class="step"
@click="step = stepName"
:data-step-active="step === stepName"
>
{{ camel2title(panel) }}
</li>
</ul>
Veja como fica quando juntamos tudo:
O CSS para formulários de várias etapas — como as abas neste exemplo — não está incluído no tema padrão Genesis. Os estilos foram escritos personalizadamente para este exemplo e você precisará fornecer os seus próprios.
Está começando a parecer um verdadeiro formulário de várias etapas! Ainda há mais trabalho a ser feito, pois temos alguns problemas:
Vamos abordar o primeiro problema.
FormKit já rastreia a validade do group
por padrão. Só precisaremos capturar esses dados para que possamos usá-los em nossa interface de usuário.
Um conceito importante para lembrar sobre o FormKit é que todo componente <FormKit>
tem um nó central correspondente, que por sua vez tem um objeto node.context
reativo. Este objeto context
rastreia a validade do nó em context.state.valid
. Como mencionado acima, um group
se torna válido quando todos os seus descendentes são válidos. Com isso em mente, vamos construir um objeto que armazena a validade reativa de cada um dos grupos.
Vamos aproveitar a funcionalidade de plugin do FormKit para fazer este trabalho. Embora o termo "plugin" possa parecer intimidante, os plugins no FormKit são apenas funções de configuração que são chamadas quando um nó é criado. Os plugins são herdados por todos os descendentes (como filhos dentro de um grupo).
Aqui está nosso plugin personalizado, chamado stepPlugin
:
// nosso plugin e nosso código de template farão uso de 'steps'
const steps = reactive({})
const stepPlugin = (node) => {
// só executa para <FormKit type="group" />
if (node.props.type == 'group') {
// construir nosso objeto steps
steps[node.name] = steps[node.name] || {}
// adicionar a validade reativa do grupo atual
node.on('created', () => {
steps[node.name].valid = toRef(node.context.state, 'valid')
})
// Parar a herança do plugin para nós descendentes.
// Só nos importamos com os grupos de nível superior
// que representam as etapas.
return false
}
}
O objeto steps
reativo resultante do nosso plugin acima se parece com isto:
{
contactInfo: { valid: false },
organizationInfo: { valid: false }
application: { valid: false }
}
Para usar nosso plugin, vamos adicioná-lo ao nosso formulário raiz <FormKit type="form" />
. Isso significa que todo grupo de nível superior em nosso formulário herdará o plugin:
<FormKit type="form" :plugins="[stepPlugin]"> ... restante do formulário </FormKit>
Agora que nosso template tem acesso em tempo real ao estado de validade de cada grupo através do nosso plugin, vamos escrever a interface do usuário para mostrar esses dados na barra de navegação de etapas.
Também não precisamos mais definir manualmente nossas etapas, pois nosso plugin está armazenando dinamicamente o nome de todos os grupos no objeto steps
. Vamos adicionar um atributo data-step-valid="true"
a cada etapa se ela for válida para que possamos direcionar com 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>
Com essas atualizações, nosso formulário agora é capaz de informar ao usuário quando eles preencheram corretamente todos os campos em uma determinada etapa!
Também faremos algumas outras melhorias:
activeStep
.A exibição de erros é mais matizada. Embora o usuário possa não estar ciente, existem na verdade 2 tipos de erros que precisamos tratar e comunicar ao usuário:
messages
do tipo validation
)messages
do tipo error
)O FormKit usa sua loja de mensagens para rastrear ambos os tipos de erros/mensagens.
Com nosso plugin já em vigor, é relativamente simples adicionar rastreamento para ambos:
const stepPlugin = (node) => {
...
// Armazene ou atualize a contagem de mensagens de validação bloqueantes.
// FormKit emite o evento "count:blocking" (com a contagem) cada vez
// que a contagem muda.
node.on('count:blocking', ({ payload: count }) => {
steps[node.name].blockingCount = count
})
// Armazene ou atualize a contagem de mensagens de erro de backend.
node.on('count:errors', ({ payload: count }) => {
steps[node.name].errorCount = count
})
...
}
FormKit faz uma distinção entre mensagens de validação frontend (messages
do tipo validation
), e erros (messages
do tipo error
).
Vamos atualizar nosso exemplo para mostrar ambos os tipos de erros com os seguintes requisitos:
Como "desfocar um grupo" não existe em HTML, vamos introduzi-lo em nosso plugin com um array chamado visitedSteps
. Aqui está o código relevante:
import { watch } from 'vue'
import { getNode, createMessage } from '@formkit/core'
const stepPlugin = (node) => {
...
const activeStep = ref('')
const visitedSteps = ref([]) // rastrear etapas visitadas
// Observar nossa activeStep e armazenar etapas visitadas
watch(activeStep, (newStep, oldStep) => {
if (oldStep && !visitedSteps.value.includes(oldStep)) {
visitedSteps.value.push(oldStep)
}
// Acionar a exibição de validação nos campos se um grupo foi visitado
visitedSteps.value.forEach((step) => {
const node = getNode(step)
// o método node.walk() percorre todos os descendentes do nó atual
// e executa a função fornecida.
node.walk((n) => {
n.store.set(
createMessage({
key: 'submitted',
value: true,
visible: false
})
)
})
})
})
...
}
Você pode estar se perguntando por que estamos percorrendo todos os descendentes de uma determinada etapa (node.walk()
) e criando mensagens com uma chave de submitted
e valor de true
? Quando um usuário tenta enviar um formulário, é assim que o FormKit informa a si mesmo que todas as entradas estão em um estado submitted
. Neste estado, o FormKit força qualquer mensagem de validação bloqueante a aparecer. Estamos acionando manualmente a mesma coisa em nosso evento de "desfoque de grupo".
Usaremos a mesma interface para ambos os tipos de erros, já que os usuários finais realmente não se importam com a distinção. Aqui está nosso HTML atualizado, que exibe uma bolha vermelha com a soma total dos erros 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>
Estamos quase na linha de chegada! Aqui está nosso formulário atual — que agora pode informar ao usuário quando eles preencheram corretamente ou incorretamente cada etapa:
A última peça do quebra-cabeça é enviar o formulário e lidar com quaisquer erros que recebemos do servidor backend. Vamos simular o backend para os propósitos deste guia.
Enviamos o formulário adicionando um manipulador @submit
ao <FormKit type="form">
:
<FormKit type="form" @submit="submitApp"> ... restante do formulário</FormKit>
E aqui está nosso manipulador de envio:
const submitApp = async (formData, node) => {
try {
const res = await axios.post(formData)
node.clearErrors()
alert('Sua aplicação foi enviada com sucesso!')
} catch (err) {
node.setErrors(err.formErrors, err.fieldErrors)
}
}
Observe que o FormKit passa ao nosso manipulador de envio 2 argumentos úteis: os dados do formulário em um único objeto pronto para solicitação (que estamos chamando de formData
), e o núcleo subjacente do formulário node
, que podemos usar para limpar erros ou definir quaisquer erros retornados usando os auxiliares node.clearErrors()
e node.setErrors()
, respectivamente.
setErrors()
recebe 2 argumentos: erros de nível de formulário e erros específicos de campo. Nosso backend falso retorna a resposta err
que usamos para definir quaisquer erros.
Então, o que acontece se o usuário estiver na etapa 3 (Aplicação) quando enviar, e houver erros de nível de campo em uma etapa oculta? Felizmente, desde que os nós existam no DOM, o FormKit é capaz de colocar esses erros adequadamente. É por isso que usamos um v-show
para as etapas em vez de v-if
— O nó DOM precisa existir para ter erros definidos no nó FormKit correspondente.
E Voilà! 🎉 Terminamos! Além do nosso manipulador de envio, adicionamos alguns outros toques de UI e UX a este formulário final para torná-lo mais real:
utils.js
que retorna erros.válido
.Aqui está — um formulário de várias etapas totalmente funcional:
Claro, sempre há maneiras de melhorar qualquer coisa, e este formulário não é exceção. Aqui estão algumas ideias:
window.localStorage
para que o estado do formulário do usuário seja mantido mesmo que ele saia acidentalmente.Cobrimos muitos tópicos neste guia e esperamos que você tenha aprendido mais sobre o FormKit e como usá-lo para facilitar a criação de formulários de várias etapas!