Last week I wrote up a demo explaining how to build a simple quiz using Vue.js. As part of that process, I demonstrated how to render one question of the quiz at a time and navigate through the set of questions. It occurred to me that it may make sense to also demonstrate how to build a simple "multi-step" form in Vue.js as well. Let's begin with a simple example. I'll show the HTML first.
<div id="app">
<form>
<div v-if="step === 1">
<h1>Step One</h1>
<p>
<legend for="name">Your Name:</legend>
<input id="name" name="name" v-model="registration.name">
</p>
<p>
<legend for="email">Your Email:</legend>
<input id="email" name="email" type="email" v-model="registration.email">
</p>
<button @click.prevent="next()">Next</button>
</div>
<div v-if="step === 2">
<h1>Step Two</h1>
<p>
<legend for="street">Your Street:</legend>
<input id="street" name="street" v-model="registration.street">
</p>
<p>
<legend for="city">Your City:</legend>
<input id="city" name="city" v-model="registration.city">
</p>
<p>
<legend for="state">Your State:</legend>
<input id="state" name="state" v-model="registration.state">
</p>
<button @click.prevent="prev()">Previous</button>
<button @click.prevent="next()">Next</button>
</div>
<div v-if="step === 3">
<h1>Step Three</h1>
<p>
<legend for="numtickets">Number of Tickets:</legend>
<input id="numtickets" name="numtickets" type="number" v-model="registration.numtickets">
</p>
<p>
<legend for="shirtsize">Shirt Size:</legend>
<select id="shirtsize" name="shirtsize" v-model="registration.shirtsize">
<option value="S">Small</option>
<option value="M">Medium</option>
<option value="L">Large</option>
<option value="XL">X-Large</option>
</select>
</p>
<button @click.prevent="prev()">Previous</button>
<button @click.prevent="submit()">Save</button>
</div>
</form>
<br/><br/>Debug: {{registration}}
</div>
This is a three step form that kind of mimics a typical conference registration. I've got three steps each set up in a div using v-if
to control if the particular step is rendered. Note the buttons in each step. I call either next
, prev
, or submit
based on what part of the process I'm in. The last part (where it says Debug) was simply a little, well, "debugger" so I could see data being entered as the process was completed.
Now let's look at the code.
const app = new Vue({
el:'#app',
data() {
return {
step:1,
registration:{
name:null,
email:null,
street:null,
city:null,
state:null,
numtickets:0,
shirtsize:'XL'
}
}
},
methods:{
prev() {
this.step--;
},
next() {
this.step++;
},
submit() {
alert('Submit to blah and show blah and etc.');
}
}
});
I've got two interesting things going on here. First, I decided to put all the form data in a key called registration
. This gave me a nice separation between the "UI stuff" and the actual form data. The prev
and next
methods simply change the step
value. In theory I could add additional logic here to ensure I don't go to an invalid step, but as I'm writing the HTML myself I trusted myself to not screw that up. Finally, the submit
action would do a simple AJAX post and then either redirect to a thank you page or show a server-side error. (I assumed folks were here for the multi-step form and not the AJAX, but if you want to see that, just ask!) Here is a complete embed of this demo:
See the Pen Multi-step Vue form by Raymond Camden (@cfjedimaster) on CodePen.
You may be curious - what if you didn't want to use AJAX for the form submission. I mean, there's a real form there, right? Well don't forget that v-if
actually takes things in and out of the DOM. If you tried to submit this version, only the form fields from the last visible step would be posted. Here's a modified version that uses v-show
instead:
<div id="app">
<form action="https://postman-echo.com/post" method="post">
<div v-show="step === 1">
<h1>Step One</h1>
<p>
<legend for="name">Your Name:</legend>
<input id="name" name="name" v-model="registration.name">
</p>
<p>
<legend for="email">Your Email:</legend>
<input id="email" name="email" type="email" v-model="registration.email">
</p>
<button @click.prevent="next()">Next</button>
</div>
<div v-show="step === 2">
<h1>Step Two</h1>
<p>
<legend for="street">Your Street:</legend>
<input id="street" name="street" v-model="registration.street">
</p>
<p>
<legend for="city">Your City:</legend>
<input id="city" name="city" v-model="registration.city">
</p>
<p>
<legend for="state">Your State:</legend>
<input id="state" name="state" v-model="registration.state">
</p>
<button @click.prevent="prev()">Previous</button>
<button @click.prevent="next()">Next</button>
</div>
<div v-show="step === 3">
<h1>Step Three</h1>
<p>
<legend for="numtickets">Number of Tickets:</legend>
<input id="numtickets" name="numtickets" type="number" v-model="registration.numtickets">
</p>
<p>
<legend for="shirtsize">Shirt Size:</legend>
<select id="shirtsize" name="shirtsize" v-model="registration.shirtsize">
<option value="S">Small</option>
<option value="M">Medium</option>
<option value="L">Large</option>
<option value="XL">X-Large</option>
</select>
</p>
<button @click.prevent="prev()">Previous</button>
<input type="submit" value="Save">
</div>
</form>
<br/><br/>Debug: {{registration}}
</div>
Note I also added an action and method to the form tag. In this case I'm using the Postman echo service so the result won't be terribly pretty, but you will see all the fields posted. Here's this version:
See the Pen Multi-step Vue form (2) by Raymond Camden (@cfjedimaster) on CodePen.
Woot. Ok, so for a final version, I thought I'd try a quick Veutify version. Veutify is a material design UI skin for Vue and it's pretty bad ass. I want to be clear that I whipped this up pretty quickly so it is probably not the most ideal version, but it looks pretty cool.
See the Pen Multi-step form, Vuetify by Raymond Camden (@cfjedimaster) on CodePen.
In case you're curious, that control is called the stepper and has quite a few options for how to configure it. The default is a horizontal step process, but I noticed the labels in the header went away when the width was constrained. This version seemed to work better in the space I have here.
Anyway - I hope this helps, and as always, remember there's going to be many different ways of doing what I demonstrated here. If you've been working with Vue and have some examples of this, please share below!