How to create dynamic forms with custom validation in Storyblok and Nuxt.js
Storyblok is the first headless CMS that works for developers & marketers alike.
Web forms are usually some of the least beloved parts of developing a website. In our experience, it has something to do with the combination of styling the forms and tackling form validation.
In this article we’ll show you how to easily set-up dynamic forms with custom validation using Storyblok, Vuelidate and TailwindCSS.
We’re not going to talk about setting up a new Storyblok/Nuxt.js project. That is excellently described in this article. Instead, we focus on the Storyblok fields setup and Vue component code. This step-by-step guide will help you set-up your own dynamic form.
We’ve recorded a video about how the end product looks once you go through the steps in this article. Feel free to check out the video on the end of this article.
Github Repo
Clone the Vue-nuxt-boilerplate repo.
Vuelidate installation
Let’s start with adding Vuelidate to your Nuxt project. This package is responsible for the editable form validation.
1. Install Vuelidate
As described in the Vuelidate setup guide, install the package using npm or yarn
2. Create the Vuelidate plugin file
Create a plugin file vuelidate.js
in the plugins
directory of your project with this content:
3. Add the plugin to your Nuxt config file nuxt.config.js
Storyblok setup
Storyblok Validator components
To achieve custom input field validation, create a counterpart of the Vuelidate validators as Storyblok components. In this example we use a few validators from the builtin Vuelidate validators. The cool thing about Vuelidate is that you can create whole new custom validators in addition to the built-in ones. The ones that we going to use are:
- required - requires the input not be empty
- email - requires the input be a valid email
- numeric - requires the input be all numbers
- minLength - requires the input to have a specified minimum length
- maxLength - requires the input to have a specified maximum length
Each of these is represented by a separate Storyblok component.
The component names should be the exact names of the validators since we will map them dynamically to the validators from the Vuelidate package.
The email
, numeric
and required
components have one editable field- the custom error message.
The minLength
and maxLength
components have an extra param field, which determines the specified length.
Input Field component
We’re going to create a fully dynamic Form component in Storyblok where we add our input fields. Start by creating the input field component. For this tutorial, we’ve chosen to only work with the <input />
tag, but the setup can be done with all the other tags (textarea, select, etc.) as well. Let's call this component input-field
.
Here are the fields we want to edit in Storyblok:
name
- the unique input identificator in HTML
<input name="" />
type
- the input type
<input type="" />
- created as a
Single-Option
type in Storyblok with the options: text, tel, num, email to cover the basics
label
- the label of our input field
- will be used for
<label />
in Vue
placeholder
- the string that gets shown when the input field is empty
<input placeholder="" />
validators
- list of field validators; only our validator components are allowed
As you can see on the screenshot below, we create additional editable fields for styling under the Styling
tab. These contain TailwindCSS classes mapped to their target HTML tags in Vue.
The inputs
field contains the list of inputs fields in our form. The field formEndpoint
is the URL where the form sends the form data. This component has its own Styling tab with a field for custom TailwindCSS classes.
The Submit button tab contains the text and the styling of the submit button.
Vue Component code
After preparing the components and fields on the Storyblok side, we create the Vue component which makes the fully dynamic form possible. We need only one component, named DynamicForm.global.vue
to reflect its Storyblok counterpart. The .global
suffix makes the component globally registered automatically. If you’re not familiar with this, you can read about setting it up in your Nuxt project here.
Next, we'll go through the separate parts of the component and describe their part in the functionality. Let’s start with the <script>
logic part.
Component <script> logic
We need to import only the validators from the Vuelidate package. This makes dynamic validation possible. Using this approach, you can decide on adding a new validator from the built-in ones without needing to change the code, just add a new Storyblok component with the validator's name.
The prop blok
represents the Storyblok content. More on why it is here, and how it works, is found in the article mentioned at the beginning.
Our initial form data is generated from the input components using a .reduce()
function. In short, this function creates an object using the form field's unique name as a key. The values are empty string by default.
In our methods
part, we have a function responsible for generating the validators for a given input-field.
Here, we generate an object of validators for a field using the imported validators
variable containing every built-in Vuelidate validator. We use this function in the object which tells Vuelidate which input field has which validators. The ternary operator makes sure the validators' functions that are using params get called with the param as their argument.
The fieldRules
variable is used as the collection of the fields with their generated validators.
The validations
root key is where we initiate the Vuelidate frame. You can check out their documentation here. Once the package is mounted, you can access its data and options through this.$v.form
. This is also how we connected to the form data in our last method.
This function is called when the form gets submitted. The $invalid
property has truthy
value when one of the fields doesn't pass its validation. In that case, we make the warnings visible using the $touch()
method. You can read about the built-in props and methods in Vuelidate API.
Connecting all of the dots, we get the following component logic.
Component <template>
The Vue component template in its full glory looks like:
As a whole, we use the TailwindCSS classes as class names for the different parts of our template. The more content we delegate to Storyblok, the more editable the styling gets.
We want to highlight two parts of the template. The first is the <input>
tag.
We use the Storyblok-provided _uid
prop as the ID of the field. For binding the input realtime to our data layer and Vuelidate, we connect them by using Vuelidates $model
prop. This is available in our case through $v.form[inputField.name].$model
.
The dynamic class prop enables the styling from inputField.fieldErrorClass
in the case of invalid field content. We know the field has a validation error thanks to $v.form[inputField.name].$error
.
The second part I want to describe is the error message container:
This code iterates through the field validators and shows a custom error message if the iterable validator has a problem. You can read about this functionality here.
Conclusion
That’s it. With that, we’re wrapping up our dynamic form. Combining the parts we’ve talked about, we’ve created the functionality presented in our showcase video.
Storyblok makes even the least popular parts of web development, forms, easy to create and manage. There is a lot more we could show you about the fusion between Storyblok and Vuelidate, but you get the idea. Make sure to contact us if you have any questions or comments about the article. You can find our contact details below.
Resource | URL |
---|---|
How to connect Storyblok with Nuxt.js | https://www.storyblok.com/tp/headless-cms-nuxtjs |
Video Sample | https://www.youtube.com/watch?v=lA4VvL0ZXIg |