From a usability standpoint, you usually want to try and keep forms as small and simple as possible. Unless people are required to fill out your form, the more information they have to supply, the less likely they are to do so. So, when a user is presented with a form of, say, 10 questions, it looks quite daunting. One option is to break those 10 questions up into related groups and present them in a tab form. This makes the form seem smaller and easier for the user to comprehend. Further, if you need to guide your user through a process, then you will want to consider the "wizard" user interface pattern. With this approach, the form is broken into steps. When the user completes step one, she clicks the Next button to go to the next step. At the end of the process, the user submits the form. I'll show you how to create a wizard in this article, using XMod Pro and jQuery.
Let's begin with our starting form. I'm using a scaled down version of the Article Entry form I built in the Beginner's Guide to XMod Pro. I've included the full code for the form so you can use it as a starting point. We're not actually going to be saving any data so you could remove the <SubmitCommand>
if you'd like to avoid any errors if you haven't created the XMP_Articles table from that guide. Rather than creating a form specifically for this example, I wanted to "retro-fit" an existing form so you could see how easy it is to add a wizard to the forms XMod Pro generates for you. So, other than a few minor tweaks, I haven't made any changes to the code that XMod Pro generated for me when I converted the Article form from an Auto-Layout form to a Custom HTML layout form.
<AddForm>
<ScriptBlock BlockType="HeadScript" RegisterOnce="True" ScriptId="KBXM_Style_Article">
<style type="text/css">
.xmp-Article {
padding: 10px 5px 5px 5px;
}
.xmp-Article .xmp-form-row {
margin: 3px;
padding: 3px;
clear:left;
}
.xmp-Article label.xmp-form-label, .xmp-Article span.xmp-form-label{
display:block;
float:left;
width: 120px;
text-align: left;
margin-right: 5px;
}
.xmp-Article .xmp-button {
margin-right: 5px;
}
</style>
</ScriptBlock>
<SubmitCommand CommandText="INSERT INTO [XMP_Articles] ([Title], [AuthorId], [Synopsis]) VALUES(@Title, @AuthorId, @Synopsis) " />
<ControlDataSource Id="dsAuthors" CommandText="SELECT [AuthorId], [Name] FROM [XMP_ArticleAuthors] ORDER BY [Name] ASC" />
<div class="xmp-form xmp-Article">
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="Title">
Title
</Label>
<TextBox Id="Title" Width="250" MaxLength="80" DataField="Title" DataType="string">
</TextBox>
<Validate Target="Title" CssClass="NormalRed xmp-validation" Type="required" Text="**" Message="A Title is required">
</Validate>
</div>
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="AuthorId">
Author
</Label>
<DropDownList Id="AuthorId" DataField="AuthorId" DataSourceId="dsAuthors" DataTextField="Name" DataValueField="AuthorId" AppendDataBoundItems="true" DataType="int32">
<ListItem Value="">
(Select One)
</ListItem>
</DropDownList>
<Validate Target="AuthorId" CssClass="NormalRed xmp-validation" Type="required" Text="**" Message="An author is required">
</Validate>
</div>
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="Synopsis">
Synopsis
</Label>
<TextArea Id="Synopsis" Height="80" Width="400" Nullable="true" DataField="Synopsis" DataType="string">
</TextArea>
</div>
<ValidationSummary CssClass="NormalRed xmp-validation" Id="vsXMP_Articles">
</ValidationSummary>
<div class="xmp-form-row">
<Label class="xmp-form-label NormalBold">
</Label>
<UpdateButton Text="Update">
</UpdateButton>
<CancelButton Text="Cancel" style="margin-left: 12px;" Visible="true">
</CancelButton>
</div>
</div>
</AddForm>
Create the Wizard Steps
The first thing we need to do is break our form up into the steps the user will move through while completing the form. This is done simply by wrapping those sections in a <div>
tag. Each <div>
will have a CSS class assigned that identifies it and makes it easier to hide and show them as a group later. Additionally, each <div>
will have a unique ID, identifying what step it is.
<div id="wizard-step-1" class="wizard-step">
<!-- Step One controls go here -->
</div>
<div id="wizard-step-2" class="wizard-step">
<!-- Step Two controls go here -->
</div>
<div id="wizard-step-3" class="wizard-step">
<!-- Step Three controls go here -->
</div>
So, in XMP-generated forms, each control is in its own <div>
with a class of xmp-form-row
and all those controls are contained by a <div>
with a class of xmp-form
. Our wizard steps will wrap the control xmp-form-row <div>
tags like so:
<div class="xmp-form xmp-Article">
<div id="wizard-step-1" class="wizard-step">
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="Title">
Title
</Label>
<TextBox Id="Title" Width="250" MaxLength="80" DataField="Title" DataType="string">
</TextBox>
<Validate Target="Title" CssClass="NormalRed xmp-validation" Type="required" Text="**" Message="A Title is required">
</Validate>
</div>
</div>
<div id="wizard-step-2" class="wizard-step">
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="AuthorId">
Author
</Label>
<DropDownList Id="AuthorId" DataField="AuthorId" DataSourceId="dsAuthors" DataTextField="Name" DataValueField="AuthorId" AppendDataBoundItems="true" DataType="int32">
<ListItem Value="">
(Select One)
</ListItem>
</DropDownList>
<Validate Target="AuthorId" CssClass="NormalRed xmp-validation" Type="required" Text="**" Message="An author is required">
</Validate>
</div>
</div>
<div id="wizard-step-3" class="wizard-step">
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="Synopsis">
Synopsis
</Label>
<TextArea Id="Synopsis" Height="80" Width="400" Nullable="true" DataField="Synopsis" DataType="string">
</TextArea>
</div>
<div class="xmp-form-row">
<Label class="xmp-form-label NormalBold">
</Label>
<AddButton Text="Submit Article" CssClass="dnnPrimaryAction">
</AddButton>
<CancelButton Text="Cancel" style="margin-left: 12px;" Visible="true" CssClass="dnnSecondaryAction">
</CancelButton>
<ValidationSummary CssClass="NormalRed xmp-validation" Id="vsXMP_Articles">
</ValidationSummary>
</div>
</div>
</div>
Add the Navigation Buttons
Each step in the wizard needs some navigation - a way for the user to move between steps. For that we'll use standard HTML hyperlinks. In the full code, you'll find something similar to the below code. I wanted to pull it out and explain it more.
<div>
<a href="#" data-step="1" class="wizard-nav dnnSecondaryAction"><< Back</a>
<a href="#" data-step="3" class="wizard-nav dnnSecondaryAction">Next >></a>
</div>
As you can see, they're standard hyperlinks with a hash tag for their href
property. I've also given them a CSS class for style (dnnSecondaryAction
) and one for functionality (wizard-nav
). The latter will make it easier to target the links as a whole when we write our jQuery. The other critical bit here is the data-step
property I've created. The data-
attribute is an HTML5 mechanism of storing user-defined data and works quite well with jQuery. We're storing the target of the button in this property. So, the first link, when clicked, will take the user to Step 1, the 2nd link will take the user to Step 3.
Now, here's the full code thus far. I've added H2 tags as the title for each step to make it easier to tell which step we're viewing:
<div class="xmp-form xmp-Article">
<div id="wizard-step-1" class="wizard-step">
<h2>Step One</h2>
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="Title">
Title
</Label>
<TextBox Id="Title" Width="250" MaxLength="80" DataField="Title" DataType="string">
</TextBox>
<Validate Target="Title" CssClass="NormalRed xmp-validation" Type="required" Text="**" Message="A Title is required">
</Validate>
</div>
<div>
<a href="#" data-step="2" class="wizard-nav dnnSecondaryAction">Next >></a>
</div>
</div>
<div id="wizard-step-2" class="wizard-step">
<h2>Step Two</h2>
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="AuthorId">
Author
</Label>
<DropDownList Id="AuthorId" DataField="AuthorId" DataSourceId="dsAuthors" DataTextField="Name" DataValueField="AuthorId" AppendDataBoundItems="true" DataType="int32">
<ListItem Value="">
(Select One)
</ListItem>
</DropDownList>
<Validate Target="AuthorId" CssClass="NormalRed xmp-validation" Type="required" Text="**" Message="An author is required">
</Validate>
</div>
<div>
<a href="#" data-step="1" class="wizard-nav dnnSecondaryAction"><< Back</a>
<a href="#" data-step="3" class="wizard-nav dnnSecondaryAction">Next >></a>
</div>
</div>
<div id="wizard-step-3" class="wizard-step">
<h2>Step Three</h2>
<div class="xmp-form-row">
<Label CssClass="xmp-form-label NormalBold" For="Synopsis">
Synopsis
</Label>
<TextArea Id="Synopsis" Height="80" Width="400" Nullable="true" DataField="Synopsis" DataType="string">
</TextArea>
</div>
<div>
<a href="#" data-step="2" class="wizard-nav dnnSecondaryAction"><< Back</a>
</div>
<div class="xmp-form-row">
<Label class="xmp-form-label NormalBold">
</Label>
<AddButton Text="Submit Article" CssClass="dnnPrimaryAction">
</AddButton>
<CancelButton Text="Cancel" style="margin-left: 12px;" Visible="true" CssClass="dnnSecondaryAction">
</CancelButton>
<ValidationSummary CssClass="NormalRed xmp-validation" Id="vsXMP_Articles">
</ValidationSummary>
</div>
</div>
</div>
Let's take a look at what the form looks like at this point
Here you can clearly see each step as well as the navigation buttons. The Submit button is on the final Step Three. Of course, the navigation buttons don't work yet. Let's get those hooked up.
Finishing the Form
The last thing to do is write some jQuery to initially hide all but the first panel and hook up the buttons to selectively hide/show the steps.
Before the closing </AddForm>
tag in your form, add this code:
<jQueryReady>
$('.wizard-step').hide();
$('#wizard-step-1').show();
$('.wizard-nav').on('click', function(e) {
e.preventDefault();
var stepNum = parseInt($(this).data('step'));
$('.wizard-step').hide();
switch (stepNum) {
case 1:
$('#wizard-step-1').fadeIn();
break;
case 2:
$('#wizard-step-2').fadeIn();
break;
case 3:
$('#wizard-step-3').fadeIn();
break;
}
});
</jQueryReady>
If you're familiar with jQuery and Javascript this should be pretty straightforward. If not, let's go through the code in a bit more detail:
$('.wizard-step').hide();
$('#wizard-step-1').show();
The first two lines of code are hiding all the wizard steps and then immediately showing the first step. The result is that only the first step will show when the form loads.
$('.wizard-nav').on('click', function(e) {
e.preventDefault();
The next line traps the click event for all the navigation buttons. Adding the wizard-nav class makes it easy to target them all at once. the following line prevents the browser from trying to do anything with the clicked hyperlink - a useful bit of code for keeping the page from jumping back to the top unnecessarily.
var stepNum = parseInt($(this).data('step'));
This line sets up a variable to store what step number we should navigate to. It pulls its value from link's data-step
attribute we talked about earlier. The link is represented by $(this)
jQuery, incidentally, is setup to read data-
properties using the syntax above. Simply use the .data()
function and use what comes after the dash as the argument.
$('.wizard-step').hide();
switch (stepNum) {
case 1:
$('#wizard-step-1').fadeIn();
break;
case 2:
$('#wizard-step-2').fadeIn();
break;
case 3:
$('#wizard-step-3').fadeIn();
break;
}
In this final chunk of code we're first hiding all the wizard steps, as we did earlier. Except in this case we're using the Javascript switch function to determine which step to show, based on the value we retrieved from the data-step
attribute. To add a little flare, we're fading the step in, so the transition isn't quite so jarring.
And that's all there is.