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:
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:
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.
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:
div
or span
element (this also facilitates formatting the steps individually)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">
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…
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 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.