Telerik blogs
BlazorT4_1200x303

Once you’ve added the Blazor Stepper to your component, your next step is to integrate the Stepper with your UI. You’ve got a bunch of options and here’s how to implement all of them.

In a previous blog, I showed how you can use the Stepper component from the Telerik UI for Blazor components to create a UI that breaks a “difficult task” down into a step-by-step process.

The next step is to add the steps that make up that process (your UI) and integrate those steps with the Stepper’s UI. There are two parts to that integration because the Stepper performs two tasks for you:

  • The Stepper lets the user select the part of the process they want to do next so, when the user selects a step, your UI needs to update to reflect the user’s selected step.
  • The Stepper also reports where the user is in the process so, when you move the user to a new step, you need to update the Stepper to reflect that.

For this discussion, I’m going to assume this Blazor Stepper markup:

<TelerikStepper @bind-value="currentStep">
  <StepperSteps>
            <StepperStep Label="Overview" 
                         Icon="info-circle" ></StepperStep>
            <StepperStep Label="Pick Date Range" 
                         Icon="calendar" ></StepperStep>
            <StepperStep Label="Select Occurrences"
                         Icon="link" ></StepperStep>
            <StepperStep Label="Define Case"
                         Icon="folder-open" ></StepperStep>
            <StepperStep Label="Save Changes"
                         Icon="save" ></StepperStep>       
    </StepperSteps>
</TelerikStepper>

That would give this UI:

A five step process. Each circle in the step has an icon. The first step has the information icon (the lower case letter i inside a circle), the second step has a calendar, the third step has a link in a chain, the fourth step has an open folder, and the fifth step has a disk. Each step also has a label below it: “Overview” for step 1, “Pick Date Range” for step 2, “Select Occurrences” for step 3, “Define Case” for step 4, and “Save Changes” for Step 5.

To give you the control you need over the Stepper, the bind-value attribute in the TelerikStepper element will need to bind the control to an integer field (or property) in your code like this one:

int CurrentStep = 0;

Using bind-value implements two-way data binding: When the user selects a step in the Stepper, the currentStep field will be updated to show the current step’s index (i.e., if the user is on the second step, currentStep will be set to 1); if, in your code, you update the currentStep field, the corresponding step in the Stepper will be selected.

Showing All the Steps: Updating the Stepper

One solution for integrating your UI and the Stepper is to display all the steps in the process at once. With this design, you need to highlight each step in your UI as the user moves through the Stepper. This approach does allow the user to see the whole process in the UI (which, depending on how intimidating the process is, may or may not be helpful to the user).

Another note: With this design, to prevent Stepper from eventually scrolling off the top of the screen as the user moves through the steps, you either need some clever CSS or to have all the steps fit on a single screen with the Stepper. I’ll just assume you’ve addressed that problem in the rest of this discussion.

With this design, you must:

  • Enclose each step inside a div or span element (this also facilitates formatting the steps individually)
  • Select an event on the elements that make up the step that signals the user has moved into that step
  • On the enclosing element, bind your selected event to a method that will update the current step in the Stepper

When selecting the event to flag that the user has moved into a step, you must select how an event will bubble up to the enclosing element: onfocusin or onclick are good choices.

As an example, the following code binds the onfocusin event of a div element to a method called instep, passing the position of the step. When the user moves to either of the textboxes, the textbox’s focusin event will fire, the event will then bubble up to the div element, and the method bound to div element’s inStep event will be called:

<div @onfocusin="() => inStep(2)">
    Date Start: <input type="date" @bind-value="startDate"/>
    Date End: <input type="date" @bind-value="endDate"/>
</div>

In the method that you’ve bound to the enclosing element, you just need to set the field that the Stepper component is bound to (currentStep in my case) to update the Stepper’s UI. A typical method will look like this:

private void inStep(int newStep)
{
   currentStep = newStep;
}

You don’t have to write a separate method if you don’t want to. You could, instead, just put your code inside the lambda expression for your event as in this example:

<div @onfocusin="() => currentStep = 2">

Showing All the Steps: Moving to the Step

That takes care of updating the Stepper as the user moves through the steps. However, you also need to move the user to the right step as the user selects the “next step” in the Stepper. The first thing to do is add a ref attribute to the first element in each step and bind that to a field (or property) in your code. This example binds a textbox to a field called step1TextBox:

<div>
    Start Date: <input @ref="step1Textbox" type="date" @bind-value="startDate" />

Within your code, you need to declare that field as an ElementRef:

ElementReference step1Textbox;

To react to the user selecting the “next step” in the Stepper, you can bind a method to the StepperStep’s OnChange event, like this:

<StepperStep Label="Pick Date Range"
                     Icon="calendar"
                     OnChange='@StepChanged'></StepperStep>

The method that you bind to the OnChange event must accept a parameter of type StepperStepChangeEventArgs. That parameter has a TargetIndex property that tells you which step the user selected. Using the TargetIndex property, you can select the field tied to the element in the step and call the field’s FocusAsync() to move the user’s cursor into the right step (you’ll also need to flag the bound method as async):

ElementReference step1Textbox;
private async Task StepChanged(StepperStepChangeEventArgs e)
{
   switch (e.TargetIndex)
   {
       case 0:
                    await step1Textbox.FocusAsync();
                   break;         
       case 1:
	//…more steps…

Revealing Steps Incrementally

The alternative to having all the steps on the page at the same time is to reveal the steps as the user needs them (i.e., the typical—and familiar—wizard UI design pattern). Having only one step on the screen at a time simplifies the UI presented to the user at any one time, but it does prevent the user from seeing the whole process.

The simplest way to implement this design is, in your UI, to move each step into the case blocks of a switch statement tied to the Stepper’s bound field. As the user moves from step to step (and, as a result, updates the Stepper’s bound field), the appropriate UI is displayed:

@switch (currentStep)
{
    case 0:
        <div>
            Date Start: <input type="date" @bind-value="startDate" />
            Date End: <input type="date" @bind-value="endDate" />
        </div>
        break;
    case 1:
        …step 2 markup
        break;
}

Because the user can’t move to a step without interacting with the Stepper, you don’t have to handle updating the Stepper to reflect the “current step.”

Enclosing all of every step’s UI in your switch statement can make for some messy Razor markup. A better solution might be to create a component for each step and pass a shared data item to each component in the step.

This code, for example, assumes the existence of field called pData in the parent component that holds all the data gathered from user in the process. That field is passed to each component that then (presumably) updates the data held in the field:

@switch (currentStep)
{
    case 0:
<Step1Component ProcessData=pData></Step1Component>
break;
    case 1:
<Step2Component ProcessData=pData></Step2Component>
break;
   //…more cases…

The parent component—the component with the Stepper—is just responsible for initializing the process’s data object (presumably the last component in the process is responsible for using the data gathered in the earlier steps):

    MasterData pData = new MasterData { startDate = DateTime.Now.AddDays(-30),
                                                                           endDate = DateTime.Now
                                                                        }

Each child component can accept the process’s data object by declaring a parameter to accept the object:

[Parameter]
public MasterData pData { get; set; }

The only limitation in all of this discussion is that I’ve assumed that you’re integrating the steps in your UI as part of a static process. There’s at least one standard case (and, I bet, an infinite number of non-standard cases) where your process won’t be dynamic—where you’ll need to add or remove steps. That’s my next blog post.


Peter Vogel
About the Author

Peter Vogel

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.

Related Posts

Comments

Comments are disabled in preview mode.