Dez79
Dez79

Reputation: 577

Styling a view in MVC based on logic from the controller

I'm creating an application where the user has to complete a series of steps sequentially. Within the controller I check if each step is complete or in progress and then based on this information, I add the style attributes to the ViewModel which are then passed to the view.

Here's the view with the steps: enter image description here

So in the controller I have the following code that is currently populating the view:

switch (WhatStep)
{
    case 1:
        ViewModel.WhatStep = 1;

        ViewModel.StepOneComplete = false;
        ViewModel.StepOnePanelColour = "info";
        ViewModel.StepOneStatusText = "<span class=\"text-info pull-right\"><strong>Next Step</strong></span>";
        ViewModel.StepOneCollapsedText = "open";

        ViewModel.StepTwoComplete = false;
        ViewModel.StepTwoPanelColour = "default";
        ViewModel.StepTwoStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
        ViewModel.StepTwoCollapsedText = "collapsed";

        ViewModel.StepThreeComplete = false;
        ViewModel.StepThreePanelColour = "default";
        ViewModel.StepThreeStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
        ViewModel.StepThreeCollapsedText = "collapsed";

        ViewModel.StepFourComplete = false;
        ViewModel.StepFourPanelColour = "default";
        ViewModel.StepFourStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
        ViewModel.StepFourCollapsedText = "open";

        ViewModel.StepFiveComplete = false;
        ViewModel.StepFivePanelColour = "default";
        ViewModel.StepFiveStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
        ViewModel.StepFiveCollapsedText = "collapsed";

        ViewModel.StepSixComplete = false;
        ViewModel.StepSixPanelColour = "default";
        ViewModel.StepSixStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
        ViewModel.StepSixCollapsedText = "collapsed";

        break;
    case 2:
        ViewModel.WhatStep = 2;

        ViewModel.StepOneComplete = true;
        ViewModel.StepOnePanelColour = "success";
        ViewModel.StepOneStatusText = "<span class=\"text-success pull-right\"><strong>✔ Complete</strong></span>";
        ViewModel.StepOneCollapsedText = "collapsed";

        ViewModel.StepTwoComplete = false;
        ViewModel.StepTwoPanelColour = "info";
        ViewModel.StepTwoStatusText = "<span class=\"text-info pull-right\"><strong>Next Step</strong></span>";
        ViewModel.StepTwoCollapsedText = "open";

The controller goes on and on as the viewModel is populated for each step.

The html in view for each panel look like this:

<div class="panel [email protected]">
    <div class="panel-heading">
        <h4 class="panel-title">
            <strong>Step One:</strong>
            <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne" class="@Model.StepOneCollapsedText"></a>
            @Html.Raw(Model.StepOneStatusText)
        </h4>
    </div>
    <div id="collapseOne" class="panel-collapse collapse" style="height: 0px;">
        <div class="panel-body">
            <p>Instructions here<p>
        </div>
    </div>
</div>

Although this approach is working fine, the controller contains over 500 lines of code, just to populate the viewModel depending on what step the user is at.

Is there a better approach to doing this?

Upvotes: 2

Views: 2199

Answers (2)

user3559349
user3559349

Reputation:

Not only could you reduce the controller code to just a few lines, you can significantly reduce the view code as well.

Rather than having a view models with multiple properties for each step, use a collection to represent the steps

public enum StepStatus
{
    Upcoming,
    Current,
    Complete
}
public class Step
{
    public int Number { get; set; } 
    public StepStatus Status { get; set; }
}

Then in the method which generates the view

public ActionResult Index(int currentStep)
{
    List<Step> model = new List<Step>();
    for (int i = 0; i < 10; i++) // generates 10 steps
    {
        model.Add(new Step() { Number = i + 1 });
    }
    // Set the status of the steps based on the current step
    for (int i = 0; i < currentStep; i++)
    {
        model[i].Status = StepStatus.Complete;
    }
    model[currentStep - 1].Status = StepStatus.InProgress;
    return View(model);
}

And the view simply uses a loop to generate each step

@model List<Step>
....
<div id="accordion">
    @for (int i = 0; i < Model.Count; i++)
    {
        <div class="panel @Model[i].Status.ToString().ToLower()"> // adds class name based on the status
            <div class=" panel-heading">
                <h4 class="panel-title">
                    <strong>Step @Model[i].Number</strong>
                    @{ string className = Model[i].Status == StepStatus.Current ? "open" : "collapsed"; }
                    <a data-toggle="collapse" data-parent="#accordion" href="#step@(Model[i].Number)" class="@className"></a>
                    <span class="text-default pull-right">
                        @if (Model[i].Status == StepStatus.Complete)
                        {
                            <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> // adds the tick mark
                        }
                        <strong>@Model[i].Status</strong>
                    </span>
                </h4>
            </div>
            <div id="step@(Model[i].Number)" class="panel-collapse collapse">
                <div class="panel-body">
                    <p>Instructions here<p>
                </div>
            </div>
        </div>
    }
</div>

and add some css to mimic the panel-succcess, panel-infoand panel-default bootstap classes

.complete {
    border-color: #d6e9c6;
}
.complete > .panel-heading {
    color: #3c763d;
    background-color: #dff0d8;
    border-color: #d6e9c6;
}
.current {
    border-color: #bce8f1;
}
.current > .panel-heading {
    color: #31708f;
    background-color: #d9edf7;
    border-color: #bce8f1;
}
.upcoming {
    border-color: #ddd;
}
.upcoming > .panel-heading {
    color: #333;
    background-color: #f5f5f5;
    border-color: #ddd;
}

You have not indicated how your rendering the content within the <div class="panel-body"> element, but this can simply be done using @Html.Action() if you want to include content for all steps, or using ajax to load the content as required. Using some conventions, the controller method could simply be

public PartialViewResult Step(int number, StepStatus status)
{
    var model = .... // get the model based on the step number
    string viewName = string.Format("_Step{0}{1}", number, status)
    return PartialView(viewName, model);
}

where your partials are named _Step1Complete.cshtml, _Step1Current.cshtml etc (I assume you have different content based on the status), and the to generate the content in the view

<div class="panel-body">
    @{ Html.RenderAction("Step", new { number = Model[i].Number, status = Model[i].Status }) // assumes the Step() method is in the same controller
</div>

Upvotes: 1

john
john

Reputation: 696

I would suggest to do the styling via javascript or jQuery and CSS. By evaluating your ViewModel. Let the browser do the handling of UI styling which was designed to that.

Upvotes: 0

Related Questions