A reusable MVC 2 wizard

As discussed, I started with this sample code I found on Nadeem Afana’s blog. I wanted to have a wizard that would work with MVC 2 (not MVC 3) – and that "looked" like a Windows-style wizard.

So, starting from Nadeem’s code, below is what I came up with. This wizard supports n-number of steps and automatically shows the correct wizard buttons. For example, here is the starting step, a middle step, and the final step:

image

image

image

Your first question might be whether a wizard should be entirely client-side – where you put all the form information onto div’s, and show/hide them with each step – or whether it should be server-side. With server-side, you might create one step at a time, post back to a controller – or even use JQuery ajax calls to go get the content for each step.

After doing some basic searching, especially on stackoverflow.com – everyone seemed to agree that doing it all client-side was the way to go. That is what this method uses too. So, here are the ‘components’ that make up this wizard:

The Workflow:
First, just to levelset, here is a high-level description of how this works:

  • A person navigates to /JobWizard in a browser
  • This calls the /JobWizard/Index controller, which returns the /JobWizard/Index.aspx view
  • That view has javascript, css (which could be moved to the site.css), and some <div> tags that represent the steps of the wizard. This should be a strongly-typed view, where the type is a data structure that will hold all of the data that is to be collected and submitted by the wizard.
  • When the person clicks "Next >" or "< Previous", JQuery shows or hides the various steps (all automatically), and running the validation for the form elements of that step, before allowing the user to continue.
  • When you get to the summary step, it does a trial-run "POST" to the /JobWizard/Confirm to do server-side validation.
  • The server-side does validation and returns the /JobWizard/Confirm.ascx partial-view
  • The page the user sees, uses JQuery to get the results of that Confirm partial-view and puts the contents inside of the summary <div>
  • When the user clicks "Finish", that posts the data structure to /JobWizard/Index where the changes are committed, and the user is redirected to some other page (back to "Home", in our case).

Controller:
In this case, I have a JobWizardController.cs file that simply handles the:

  • Initial load of the page via Index()
  • Confirmation of what you are about to submit so we can do server-side validation – via Confirm()
  • Submitting of the data via Index(object)

Now in this case, I’m not dealing with a real data structure yet, so I just have this sample code using System.Object. In real-life, you’d have a real data structure set (as a Model) so that the wizard can fill-in that data structure. For example, let’s assume that is JobWizardData. In that case, JobWizardData is what your view would be strongly-typed against, and that is what the Confirm(object) and Index(object) would take in, instead of System.Object. Anyhow, here are the controller actions that you’d need:

    /// <summary>

    /// Controller action for a GET of /JobWizard

    /// </summary>

    public ActionResult Index()

    {

        return View();

    }

 

    /// <summary>

    /// Controller action for a POST to /JobWizard/Index

    /// </summary>

    [HttpPost]

    public ActionResult Index(object formData)

    {

        return RedirectToAction("Index", "Home");

    }

 

    /// <summary>

    /// Controller action for a POST to /JobWizard/Confirm

    /// </summary>

    [HttpPost]

    public ActionResult Confirm(object formData)

    {

        return View();

    }

 

Client-side HTML / View:
The Index.aspx view need only define the wizard steps (you can set up as many as you’d like):

    <% using (Html.BeginForm())

       { %>

    <div class="wizard-container">

        <div id="Div1" class="wizard-step">

            <div class="wizard-header">

                <span class="wizard-header-text">Step 1: Create Job Wizard</span> Welcome to the

                create job wizard.

            </div>

            <div class="wizard-content">

                content goes here

            </div>

        </div>

        <div id="wizard-step" class="wizard-step">

            <div class="wizard-header">

                <span class="wizard-header-text">Step 2: Choose customer information</span> Use

                the area below to choose an existing customer or create a new one.

            </div>

            <div class="wizard-content">

                content goes here

            </div>

        </div>

        <div class="wizard-step">

            <div class="wizard-header">

                <span class="wizard-header-text">Step 3: Enter job information</span> Use the area

                below to enter basic job information.

            </div>

            <div class="wizard-content">

                content goes here

            </div>

        </div>

        <div class="wizard-step">

            <div class="wizard-header">

                <span class="wizard-header-text">Step 4: Enter project details</span> Use the area

                below to enter details about the projects associated with this job.

            </div>

            <div class="wizard-content">

                content goes here

            </div>

        </div>

        <div class="wizard-step confirm">

            <div class="wizard-header">

                <span class="wizard-header-text">Summary</span> Use the area below to review the

                new job that is about to be added.

            </div>

            <div class="wizard-content confirm">

                content goes here

            </div>

        </div>

        <div class="wizard-navigation">

            <input type="button" id="back-step" name="back-step" value="< Back" />

            <input type="button" id="next-step" name="next-step" value="Next >" />

        </div>

    </div>

    <%} %> 

As far as the /JobWizard/Confirm.ascx partial-view, here is that complete file – although yours would be strongly-typed and would show the confirmation information:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>

 

Hi there!

Javascript/JQuery:
This is pretty much just what Nadeem had, but I added some logic for the "Finish" button and wizard height too:

    $(function () {

 

        $(".wizard-step:first").fadeIn(); // show first step

 

        // attach backStep button handler

        // hide on first step

        $("#back-step").hide().click(function () {

            var $step = $(".wizard-step:visible"); // get current step

            if ($step.prev().hasClass("wizard-step")) { // is there any previous step?

                $step.hide().prev().fadeIn();  // show it and hide current step

 

                // disable backstep button?

                if (!$step.prev().prev().hasClass("wizard-step")) {

                    $("#back-step").hide();

                }

            }

 

            // In case we came from the final page, set the button text back

            $("#next-step").val("Next >");

        });

 

 

        // attach nextStep button handler      

        $("#next-step").click(function () {

 

            var $step = $(".wizard-step:visible"); // get current step

 

            var validator = $("form").validate(); // obtain validator

            var anyError = false;

            $step.find("input").each(function () {

                if (!validator.element(this)) { // validate every input element inside this step

                    anyError = true;

                }

 

            });

 

            if (anyError)

                return false; // exit if any error found

 

 

 

 

            if ($step.next().hasClass("confirm")) { // is it confirmation?

                // show confirmation asynchronously

                $.post("/jobwizard/confirm", $("form").serialize(), function (r) {

                    // inject response in confirmation step

                    $(".wizard-content.confirm").html("From /wizard/confirm: " + r);

 

                    $("#next-step").val("Finish");

                });

            }

            else {

                $("#next-step").val("Next >");

            }

 

            if ($step.next().hasClass("wizard-step")) { // is there any next step?

                $step.hide().next().fadeIn();  // show it and hide current step

                $("#back-step").show();   // recall to show backStep button

            }

 

            else { // this is last step, submit form

                $("form").submit();

            }

 

 

        });

 

    });

 

    $(function () {

        var heightToFill = $(".wizard-container").outerHeight() – $(".wizard-header").outerHeight() – $(".wizard-navigation").outerHeight();

        $(".wizard-content").outerHeight(heightToFill – 6);

 

    });

a note about the height-hack. I searched high and low to try to find out how to make a <div> be a fixed (or percentage) height. About the best I could find or get to work, is by manually setting the height or outerHeight. The hack above (highlighted in yellow) was basically because the wizard has a 3 pixel border on the top and bottom. Manually putting this here was the only way I could get this to work right.

If you have a better solution for this, please let me know!

CSS:
The CSS here is pretty much to make it look like a pretty wizard – pretty straight-forward:

    .wizard-step

    {

        display: none;

    }

    .wizard-container

    {

        border: 3px solid #7d7d7d;

        width: 680px;

        height: 420px;

    }

    .wizard-header

    {

        padding: 4px;

        background-color: #dddddd;

        border-bottom: 1px solid black;

    }

    .wizard-header-text

    {

        font-size: 12pt;

        font-weight: bold;

        display: block;

    }

    .wizard-content

    {

        padding: 10px;

    }

    .wizard-navigation

    {

        text-align: right;

        padding: 2px 2px 2px 2px;

        border-top: 2px solid #7d7d7d;

        background-color: #eeeeff;

    }

Note that you set the height and width of the wizard in the wizard-container CSS class. You can for example, remove the width and the wizard will be 100% of the available space. The height is required though, if you want to have a consistent height in-between steps of the wizard.

Summary:
So that’s basically it. Pretty easy and pretty straight-forward, once it was all figured out. Getting this working in-theory is one thing. I’m going to talk all of this proof-of-concept code over to my real project now and make sure it all works as expected!

Tagged with: , , , ,
Posted in .NET 4.0, ASP.NET, ASP.NET MVC, JQuery, Uncategorized
2 comments on “A reusable MVC 2 wizard
  1. Jamie Dixon says:

    Awesome Rob! This totally helps me out on a project I am working on.

    Like

  2. Joshua Odugbemi says:

    i tried the code, whenever i click the next button, it post back and reloads the page instead of the next wizard step. Please help!

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Archives
Categories

Enter your email address to follow this blog and receive notifications of new posts by email.

Join 9 other followers

%d bloggers like this: