How to implement the "Step Wizard"
Create: /models/WizardStepViewModel.cs
using System;
namespace {service}.Web.Portal.Models
{
public enum WizardStepState
{
NotAccessed,
Completed,
Incomplete,
InProgress,
Inapplicable,
Summary
}
public class WizardStepViewModel
{
public string Title { get; set; }
public string Url { get; set; }
public WizardStepState State { get; set; }
public string GetUrl()
{
return State == WizardStepState.Inapplicable ||
State == WizardStepState.NotAccessed
? null
: Url;
}
}
}
Create: /models/WizardViewModel.cs
using System.Collections.Generic;
using System.Linq;
namespace {service}.Web.Portal.Models
{
public class WizardViewModel
{
public WizardViewModel()
{
WizardSteps = new List<WizardStepViewModel>();
}
public ICollection<WizardStepViewModel> WizardSteps { get; set; }
public int Count => WizardSteps.Count;
public int? LastCompletedIndex => WizardSteps.Reverse()
.Select((x,i) => new {State = x.State, Index = Count - i - 1})
.FirstOrDefault(x=>x.State == WizardStepState.Completed)
?.Index;
public int? InProgressIndex => WizardSteps.Reverse()
.Select((x, i) => new { State = x.State, Index = Count - i - 1})
.FirstOrDefault(x => x.State == WizardStepState.InProgress)
?.Index;
}
}
Create: /views/shared/wizard.cshtml
@using {service}.Web.Portal.Models
@model {service}.Web.Portal.Models.WizardViewModel
<!--From: https://bootsnipp.com/user/snippets/aRlN3 -->
@functions {
int CalculateConnectingLineWidth(WizardViewModel wizard)
{
int[] lineMap = { -1, -1, 48, 69, 79, 80, 80, 84, 84, 91, 91 };
return wizard.Count <= 10 ? lineMap[Model.WizardSteps.Count] : 100;
}
int CalculateCompletionLineStartingPosition(WizardViewModel wizard)
{
int[] lineMap = { -1, -1, 27, 15, 11, 9, 9, 9, 8, 7, 7 };
return wizard.Count <= 10 ? lineMap[Model.WizardSteps.Count] : 178;
}
int CalculateCompletionLineWidth(WizardViewModel wizard)
{
var inProgressIndex = wizard.InProgressIndex;
var lastCompletedIndex = wizard.LastCompletedIndex;
if (!inProgressIndex.HasValue && !lastCompletedIndex.HasValue)
{
return 0;
}
// in case the inProgress index hasn't been defined, use the lastCompleted index
int index = inProgressIndex.HasValue
? inProgressIndex.Value
: lastCompletedIndex.GetValueOrDefault();
if (index == 0)
{
return 0;
}
int fullWidth = CalculateConnectingLineWidth(wizard);
int singleWidth = fullWidth / (wizard.Count - 1);
return singleWidth * index;
}
}
<style>
@@media screen and ( max-width : 585px ) {
.app-name-submenu {
font-size: 25px;
}
}
.wizard .nav-tabs > li {
width: @(100 / Model.WizardSteps.Count)%;
}
.connecting-line {
width: @CalculateConnectingLineWidth(Model)%;
}
.completion-line {
width: @CalculateCompletionLineWidth(Model)%;
left: @CalculateCompletionLineStartingPosition(Model)%;
}
</style>
<div class="row">
<section>
<div class="wizard">
<div class="wizard-inner">
<div class="completion-line"></div>
<div class="connecting-line"></div>
<ul class="nav nav-tabs">
@{
int counter = 1;
}
@foreach (var wizardStep in Model.WizardSteps)
{
<li role="presentation" class="@(wizardStep.State == WizardStepState.InProgress ? "active": "")@(wizardStep.State == WizardStepState.Completed ? "completed": "")">
<p class="step-label-top">Step @counter</p>
<a @Html.Raw(wizardStep.GetUrl() != null ? string.Format("href=\"{0}\"", wizardStep.GetUrl()) : "") aria-label="step@count">
<span class="round-tab@(wizardStep.GetUrl()==null?"":" pointer")">
@if (counter != Model.Count)
{
switch (wizardStep.State)
{
case WizardStepState.NotAccessed:
<i class="fa fa-file-text"></i>
break;
case WizardStepState.Completed:
<i class="fa fa-check fa-lg"></i>
break;
case WizardStepState.Incomplete:
<i class="fa fa-exclamation-triangle fa-lg"></i>
break;
case WizardStepState.InProgress:
<i class="fa fa-file-text fa-lg bg-white"></i>
break;
case WizardStepState.Inapplicable:
<i class="fa fa-minus fa-lg"></i>
break;
case WizardStepState.Summary:
<i class="fa fa-print fa-lg"></i>
break;
default:
throw new ArgumentOutOfRangeException();
}
}
else
{
<i class="fa fa-paper-plane fa-lg"></i>
}
</span>
</a>
<div style="width:100%">
<div class="step-label-bottom">@wizardStep.Title</div>
</div>
</li>
counter++;
}
</ul>
</div>
</div>
</section>
</div>
Create: /content/css/wizard.css
Edit: /App_Start/BundleConfig.cs
Paste:
Edit your base controller and add:
Find the BaseModel for the Step’s view model:
Add the following property to the BaseModel:
Edit each affected page controller method
(e.g. public ActionResult Step1Page(int id))
Locate the viewmodel, and add:
Edit each affected view(cshtml)
Paste:
Edit _Layout{service}.cshtml
Output the stylesheet:
Render the Wizard, e.g.