While perusing questions over on Stack Overflow I came across a question concerning jQuery Mobile and multi-step forms. I thought I'd take a stab and building one. This is - of course - just one way to do it. At the end of the blog entry I'll discuss some alternatives to consider, and as always, I'd love to hear from my readers about how they would do it.

One quick note. For my solution I'll be making use of ColdFusion. The technique though is what it is important. What I do here with my server side language could also be done in PHP, Ruby, etc. Let's begin with my application's home page. I made the assumption that the form would not be the first thing users see. So I created a simple page with a menu of links.

<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Page Title</title> <link rel="stylesheet" href="http://code.jquery.com/mobile/latest/jquery.mobile.min.css" /> <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script> <script src="http://code.jquery.com/mobile/latest/jquery.mobile.min.js"></script> </head> <body>

<div data-role="page">

<div data-role="header"> <h1>Page Title</h1> </div>

<div data-role="content">

<ul data-role="listview" data-inset="true"> <li><a href="notreal.html">Something</a></li> <li><a href="someform.cfm">The Form</a></li> </ul>

</div>

<div data-role="footer"> <h4>Page Footer</h4> </div>

</div>

</body> </html>

I didn't bother building out notreal.html since the important part is the form. Please remember that if you download the zip of the code. Here's how it looks:

The real meat of the logic is going to take place in someform.cfm. I decided to take the approach of a self-posting form. As you complete each step of the form, I'll take in your input, store it (in my case I'm using ColdFusion session variables, but this could be any persistence system), and increment a "step" variable that notes where you in the process. My registration form will have four steps. The first three involve actual form fields while the fourth is the final page. (This is normally where you would also store the data in the database, email it, or do whatever.) Here's the core file.

<cfif not structKeyExists(session, "regform")> <cfset session.regform = {step=1}> </cfif>

<cfif structKeyExists(form, "submit1")> <!--- normally we would validate the fields, for now, just store ---> <cfset session.regform.name = form.name> <cfset session.regform.email = form.email> <cfset session.regform.step = 2> </cfif> <cfif structKeyExists(form, "submit2")> <cfset session.regform.gender = form.gender> <cfset session.regform.coolness = form.coolness> <cfset session.regform.step = 3> </cfif> <cfif structKeyExists(form, "submit3")> <cfparam name="form.stuffilike" default=""> <cfset session.regform.stuffilike = form.stuffilike> <cfset session.regform.step = 4> </cfif>

<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Page Title</title> <link rel="stylesheet" href="http://code.jquery.com/mobile/latest/jquery.mobile.min.css" /> <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script> <script src="http://code.jquery.com/mobile/latest/jquery.mobile.min.js"></script> </head> <body>

<div data-role="page">

<div data-role="header"> <a href="index.html" data-icon="home">Home</a> <h1>Reg Form</h1> </div>

<div data-role="content">

<cfswitch expression="#session.regform.step#">

<cfcase value="1"> <cfinclude template="step1.cfm"> </cfcase>

<cfcase value="2"> <cfinclude template="step2.cfm"> </cfcase>

<cfcase value="3"> <cfinclude template="step3.cfm"> </cfcase>

<cfcase value="4"> <cfinclude template="step4.cfm"> <cfset structDelete(session,"regform")> </cfcase> </cfswitch>

</div>

<div data-role="footer"> <h4>Page Footer</h4> </div>

</div>

</body> </html>

Starting at the top, I begin by initializing my session variable to store the form data. I'm creating a structure to keep all the values. I also default the step to 1. Skip over the validation logic a bit and go down to the main page. You can see I make use of a simple switch statement. Now - my forms are not that big. I could included the code within each of my case blocks, but I wanted to make the file a bit easier to read. If you go back to the top then, you can see where I handle the form submissions. Right now I'm not doing validation, but that would be trivial to add. The basic concept is - store the values, increment the step.

Here's the code for step 1:

<form method="post">

<div data-role="fieldcontain">
<label for="name">Name:</label>
<input type="text" name="name" id="name" value="" />
</div> <div data-role="fieldcontain">
<label for="email">Email:</label>
<input type="email" name="email" id="email" value="" />
</div>

<div data-role="fieldcontain">
<input type="submit" name="submit1" value="Send" />
</div> </form>

And the result:

Carrying on - here is step 2. It's just more form fields and since it's pretty arbitrary, I won't bother explaining why I picked them.

<form method="post">

<div data-role="fieldcontain"> <fieldset data-role="controlgroup"> <legend>Gender:</legend> <input type="radio" name="gender" id="male" value="male" checked="checked" /> <label for="male">Male</label>

<input type="radio" name="gender" id="female" value="female" /> <label for="female">Female</label> </fieldset> </div>

<div data-role="fieldcontain"> <label for="coolness">Coolness:</label> <input type="range" name="coolness" id="coolness" value="25" min="0" max="100" /> </div>

<div data-role="fieldcontain">
<input type="submit" name="submit2" value="Send" />
</div> </form>

Which gives us....

And then step 3....

<form method="post">

<div data-role="fieldcontain"> <fieldset data-role="controlgroup">

<legend>Stuff I like:</legend>

<input type="checkbox" name="stuffilike" id="checkbox-1" value="Star Wars" /> <label for="checkbox-1">Star Wars</label>

<input type="checkbox" name="stuffilike" id="checkbox-2" value="BSG" /> <label for="checkbox-2">BSG</label>

<input type="checkbox" name="stuffilike" id="checkbox-3" value="Beer" /> <label for="checkbox-3">Beer</label>

</fieldset> </div>

<div data-role="fieldcontain">
<input type="submit" name="submit3" value="Send" />
</div> </form>

And finally, here is step 4. Note that back in the original file, after we include this we clear out the session data. That way in case they come to the form again it should start from scratch.

<p> Thank you for completing the form. </p>

<cfoutput> <p> Your name is #session.regform.name# and your email address is #session.regform.email#. </p>

<p> You are a #session.regform.gender# and your coolness level is #session.regform.coolness#. </p>

<p> And you like #session.regform.stuffilike#. </p> </cfoutput>

And that's it. I've attached the code below. You can demo the code here: http://coldfusionjedi.com/demos/2011/nov/18/. So I mentioned alternatives - what are some?

  • One possibility would be to make use of 'accordion' controls. I've seen that before on multi-step forms, but not on mobile. You can see an example of the control here.
  • JavaScript can be used to show and hide items - so instead of hitting the server we could simply hide a block of fields and show the next step.

Download attached file.