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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

Archives
Categories

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

Join 2 other followers

%d bloggers like this: