W dzisiejszych czasach tworzenie interaktywnych i dynamicznych formularzy jest niezwykle istotne dla wielu projektów internetowych. Odpowiednie narzędzia pozwalają na stworzenie funkcjonalnych, łatwych w obsłudze i atrakcyjnych dla użytkowników formularzy. W tym artykule skupimy się na wykorzystaniu Storyblok wraz z frame-workiem Nuxt, aby stworzyć dynamiczne formularze w prosty i efektywny sposób. Przedstawimy możliwości, jakie daje nam ta kombinacja, oraz kroki potrzebne do stworzenia formularza przy użyciu tej technologii.
Od czego powinniśmy zacząć?
Do rozpoczęcia pracy związanej będzie nam potrzeba dodatkowa paczka - Vuelidate, która zapewni nam walidację naszych dynamicznych pól.
Dodaj paczki do swojego projektu:
npm install @vuelidate/core @vuelidate/validators
# or
yarn add @vuelidate/core @vuelidate/validators
Następnie dodaj w folderze plugins
plik z wtyczką vuelidate.ts
import Vuelidate from '@vuelidate/core'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Vuelidate)
})
Setup w Storybloku
Komponenty walidacyjne
Aby uzyskać dynamiczną walidację pól w formularzu, należy utworzyć odpowiednik walidatorów jako komponenty Storybloka. W tym przykładnie użyjemy:
required
- wymaga, aby pole nie było pusteemail
- wymaga, aby podać poprawny adres e-mailnumeric
- wymaga, aby podać jedynie cyfryminLength
- wymaga, aby podać określoną minimalną długość znakówmaxLength
- wymaga, aby podać określoną maksymalną długość znaków
Ważne jest, aby nazwy komponentów były takie same jak nazwy walidatorów, ze względu na to, że będziemy je dynamicznie mapować na walidatory z pakietu Vuelidate.
Komponenty email, numeric
oraz required
mają jedno edytowalne pole - komunikat o błędzie.
Komponenty minLength
i maxLength
mają dodatkowe edytowalne pole, które wyznacza określoną długość.
Komponent pola formularza
Tworzymy dynamiczne pole formularza poprzez dodanie komponentu inputField
Komponent składa się z pola:
name
- pole tekstowe, dzięki któremu nadamy polu formularza unikalny identyfikator <input name="" />type
- pole pojedyńczego wyboru, z nadanymi opcjami:text, tel, email
. Będzie określać typ pola formularza <input type="" />label
- pole tekstowe, używane jako nazwa etykiety pola fomularzaplaceholder
- pole tesktowe, które wyświetli się w polu formularza gdy będzie puste <input placeholder="" />validators
- bloki z listą walidacji wcześniej przez nas utworzonych
Komponent formularza
Teraz tworzymy dynamiczny komponent formularza dynamicForm
.
Składa się on z:
inputs
- zawiera listę pól formularza (inputField)formEndpoint
- adres URL, pod który formularz zostanie wysłanysubmitButtonText
- nazwa jaka wyświetli się w przycisku zatwierdzającym wysłanie formularza
Komponenty Vue
Kiedy mamy już przygotowane komponenty i pola po stronie Storybloka, możemy przejść do tworzenia komponentów we Vue. Potrzebujemy dwóch komponentów: DynamicForm.vue
, który zawierać będzie dynamiczny formularz, oraz InputField.vue
, gdzie będzie się mieścić pojedyncze pole formularza.
DynamicForm.vue
Logika
Na początku należy zaimportować walidatory z pakietu Vuelidate oraz funkcję useVualidate
do aktywowania walidacji.import { useVuelidate } from '@vuelidate/core'
import * as validators from '@vuelidate/validators'
W komponencie należy również zadeklarować props pochodzące ze Storybloka.const DynamicFormProps = defineProps({
blok: {
type: Object,
default: () => ({})
}
})
Początkowe dane formularza są generowane za pomocą funkcji form. Tworzy ona obiekt, używając do tego unikalnej nazwy pola formularza jako klucz oraz pustego łańcucha znaków jako wartości.const form = reactive(DynamicFormProps.blok.inputs.reduce(
(prevFields, inputField) => ({
...prevFields,
[inputField.name]: ''
}),
{}
))
Funkcja generateFieldRules()
odpowiada za generowanie walidatorów dla danego pola formularza. Tworzymy tu obiekt składający się z nazwy walidatora jako klucz, a wartością jego jest dostarczony przez pakiet Vuelidate domyślny komunikat o błędach lub funkcja, jeżeli element posiada parametr (np. dla walidacji, która definiuje minimalną długość tekstu, należy wywołać funkcje minLenght()
i przekazać w niej parametr określający liczbę znaków)const generateFieldRules = (fieldValidators) => {
return fieldValidators.reduce(
(prevValidators, validator) => ({
...prevValidators,
[validator.component]: validator.param ? validators[validator.component](validator.param) : validators[validator.component]
}),
{}
)
}
W zmiennej fieldRules
trzymamy nazwy pól formularza z wygenerowanymi walidatorami.const fieldRules = computed(() => {
return DynamicFormProps.blok.inputs.reduce(
(prevFields, inputField) => ({
...prevFields,
[inputField.name]: generateFieldRules(inputField.validators)
}),
{}
)
})
Aby walidacja zaczęła działać, należy aktywować pakiet Vuelidate poprzez wykonanie metody useVualidate
, która przyjmuje jako parametry stworzone przez nas wcześniej zmienne. Dzięki temu za pośrednictwem v$
uzyskuje się dostęp do danych oraz opcji.const v$ = useVuelidate(fieldRules, form)
Ostatnią funkcją jest obsłużenie wysyłki formularza - formSubmit()
. Sprawdzając wartość zmiennej $invalid
dostajemy informację czy jakiekolwiek pole w formularzu nie przeszło walidacji. W takim wypadku wywołujemy metodę $touch()
, która powoduje wyświetlenie się ostrzeżeń o błędach w poszczególnych polach formularza.const formSubmit = (e) => {
if (v$.value.$invalid) {
v$.value.$touch()
e.preventDefault()
}
}
Aby komponent InputFields.vue
mógł korzystać z walidacji formularza należy mu dostarczyć zmienną v$
provide('v$', v$)
Łącząc wszystkie opisane elementy, dostajemy następującą logikę:<script setup>
import { useVuelidate } from '@vuelidate/core'
import * as validators from '@vuelidate/validators'
const DynamicFormProps = defineProps({
blok: {
type: Object,
default: () => ({})
}
})
const form = reactive(DynamicFormProps.blok.inputs.reduce(
(prevFields, inputField) => ({
...prevFields,
[inputField.name]: ''
}),
{}
))
const generateFieldRules = (fieldValidators) => {
return fieldValidators.reduce(
(prevValidators, validator) => ({
...prevValidators,
[validator.component]: validator.param ? validators[validator.component](validator.param) : validators[validator.component]
}),
{}
)
}
const fieldRules = computed(() => {
return DynamicFormProps.blok.inputs.reduce(
(prevFields, inputField) => ({
...prevFields,
[inputField.name]: generateFieldRules(inputField.validators)
}),
{}
)
})
const formSubmit = (e) => {
if (v$.value.$invalid) {
v$.value.$touch()
e.preventDefault()
}
}
const v$ = useVuelidate(fieldRules, form)
provide('v$', v$)
</script>
Szablon komponentu formularza
W szablonie tym tworzymy formularz, który posiada:
action
- atrybut określający gdzie wysłać dane formularza po jego przesłaniuformSubmit
- wywołanie metody przy próbie wysyłki formularzaInputField
- komponent zawierający pojedyncze pole formularzabutton
- przycisk wywołujący wysyłkę formularza
<template>
<form v-if="v$" :id="blok._uid" class="form" method="post" :action="blok.formEndpoint" @submit="formSubmit">
<InputField v-for="inputField in blok.inputs" :key="inputField.name" :inputField="inputField" />
<button type="submit" class="btn">
{{ blok.submitButtonText }}
</button>
</form>
</template>
InputField.vue
Logika
Na początku należy zaimportować funkcje pozwalającą przyjmowanie danych.import { inject } from 'vue'
Następnie zadeklarować props pochodzących z komponentu DynamicForm.vue
defineProps({ inputField: Object })
A na koniec stworzyć zmienną, dzięki której będziemy mieć dostęp do walidacji formularzaconst v$ = inject('v$')
Szablon komponentu pola formularza
Pojedyncze pole formularza składa się z:
- sprawdzenia, czy pole zostało poprawnie uzupełnione poprzez zmienną
v$[inputField.name].$error
. W przypadku nieprawidłowej zawartości zostaje dynamicznie dodana dodatkowa klasa. - powiązania danych pola za pomocą dyrektywy
v-model
między elementem a modelem danych Vualidate ($model
) - obsłużonych błędów walidacji. Dzieje się to za pomocą iteracji po błędach. Jeśli pole posiada dany problem, jest on wyświetlany.
<template>
<div
:class="{
'form__group': true,
'form__group--error': v$[inputField.name].$error,
}"
>
<label class="form__label" :for="inputField._uid">{{ inputField.label }}</label>
<input
:id="inputField._uid"
v-model.trim="v$[inputField.name].$model"
:type="inputField.type"
:name="inputField.name"
:placeholder="inputField.placeholder"
class="form__input"
>
<div v-if="v$[inputField.name].$error">
<div v-for="{ component, errorMessage } in inputField.validators" :key="component" class="form__group__warninig">
<div v-if="v$[inputField.name][component].$invalid">
{{ errorMessage }}
</div>
</div>
</div>
</div>
</template>
Podsumowanie
Każdy developer prędzej czy później stoi przed wyzwaniem, jakim jest stworzenie dynamicznych formularzy. Dzięki nim klient sam może dodawać oraz edytować pola w formularzach bez ingerencji programisty.
Przedstawiony przykład można rozwinąć poprzez stworzenie dodatkowych typów pól jak radio
, checkbox
czy textarea
.