I have followed tutorial below how to create a simple wizard component in Blazor Webbassembly, so far so good.
https://sinclairinat0r.com/2019/12/08/creating-a-simple-wizard-component-in-blazor
But when I try to put the wizard inside an Edit form as shown below, I run into a problem.
@page "/wizarddemo"<EditForm Model="@wizardViewModel" OnValidSubmit="@OnValidSubmit" OnInvalidSubmit="@OnInvalidSubmit"><DataAnnotationsValidator /><Wizard Id="DemoWizard" @ref="wizard"><WizardStep StepModel="firstStepModel" Name="First Step"><div class="form-group"><label class="label">First name</label><span style="color:red;">*</span><InputText id="companyName" class="form-control" type="text" @bind-Value="@firstStepModel.FirstName"></InputText><ValidationMessage For="@(() => firstStepModel.FirstName)" /></div><div class="form-group"><label class="label">Last name</label><span style="color:red;">*</span><InputText id="companyName" class="form-control" type="text" @bind-Value="@firstStepModel.LastName"></InputText><ValidationMessage For="@(() => firstStepModel.LastName)" /></div></WizardStep><WizardStep StepModel="companyModel" Name="Second Step"><div class="form-group"><label class="label"Company</label><span style="color:red;">*</span><InputText id="companyName" class="form-control" type="text" @bind-Value="@companyModel.CompanyName"></InputText><ValidationMessage For="@(() => companyModel.CompanyName)" /></div></WizardStep><WizardStep StepModel="countryModel" Name="Final Step"><div class="row"><div class="row"><div class="form-group"><label class="label">Country</label><span style="color:red;">*</span><select id="country" class="form-control selectpicker" @bind="countryModel.CountryId"><option value="">--select a country--</option> @foreach (var country in countries) {<option value="@country.Id">@country.Name</option> }</select><ValidationMessage For="@(() => countryModel.CountryId)" /></div></div></div></WizardStep></Wizard></EditForm>Code behind
public Wizard wizard; protected EditForm editForm; private FirstStepModel firstStepModel; private CountryModel countryModel; private CompanyModel companyModel; private WizardViewModel wizardViewModel; private List<Country> countries = new List<Country>(); public string StatusMessage { get; set; } public string StatusClass { get; set; } protected override async Task OnInitializedAsync() { wizardViewModel = new WizardViewModel(); wizardViewModel.CountryModel = new CountryModel(); wizardViewModel.CompanyModel = new CompanyModel(); wizardViewModel.FirstStepModel = new FirstStepModel(); countryModel = new CountryModel(); companyModel = new CompanyModel(); firstStepModel = new FirstStepModel(); countries.Add(new Country { Id = 1, Name = "Sweden" }); countries.Add(new Country { Id = 2, Name = "england" }); } private async Task OnValidSubmit() { StatusClass = "alert-info"; StatusMessage = DateTime.Now +" Handle valid submit"; wizard.ActiveStep.StepCompleted = true; } private async Task OnInvalidSubmit() { StatusClass = "alert-danger"; StatusMessage = DateTime.Now +" Handle invalid submit"; wizard.ActiveStep.StepCompleted = false; } public class Country { public int Id { get; set; } public string Name { get; set; } } public class CountryModel { [Required] public int? CountryId { get; set; } } public class CompanyModel { [Required] public string CompanyName { get; set; } } public class FirstStepModel { [Required] public string FirstName { get; set; } [Required] public string LastName { get; set; } } public class WizardViewModel { public FirstStepModel FirstStepModel { get; set; } public CountryModel CountryModel { get; set; } public CompanyModel CompanyModel { get; set; } }}
The problem is that the "ValidationMessage" component does not trigger (or is visible) when I click on the "next" button in the wizard or when I click inside the empty "InputText" for firstStepModel.FirstName and leave it empty then click on the "InputText" field for firstStepModel.LastName and type a name iside that field.
But when I then again click inside the "InputText" field for FirstName type a name and then remove the name and click outside the InputText field then the ValidationMessages appears.
The rest of the code looks like this.
Wizard.razor
Wizard.razor.cs
public partial class Wizard{ /// <summary> /// List of <see cref="WizardStep"/> added to the Wizard /// </summary> protected internal List<WizardStep> Steps = new List<WizardStep>(); /// <summary> /// The control Id /// </summary> [Parameter] public string Id { get; set; } public Object CurrentStepModelObject { get; set; } /// <summary> /// The ChildContent container for <see cref="WizardStep"/> /// </summary> [Parameter] public RenderFragment ChildContent { get; set; } /// <summary> /// The Active <see cref="WizardStep"/> /// </summary> [Parameter] public WizardStep ActiveStep { get; set; } /// <summary> /// The Index number of the <see cref="ActiveStep"/> /// </summary> [Parameter] public int ActiveStepIx { get; set; } /// <summary> /// Determines whether the Wizard is in the last step /// </summary> public bool IsLastStep { get; set; } /// <summary> /// Sets the <see cref="ActiveStep"/> to the previous Index /// </summary> protected internal void GoBack() { if (ActiveStepIx > 0) SetActive(Steps[ActiveStepIx - 1]); } /// <summary> /// Sets the <see cref="ActiveStep"/> to the next Index /// </summary> protected internal void GoNext() { bool defaultValidatorSuccess = CheckValidationStatus(CurrentStepModelObject); if (defaultValidatorSuccess) { ActiveStep.StepCompleted = true; } if (ActiveStepIx == Steps.Count - 1 && ActiveStep.StepCompleted) { if (defaultValidatorSuccess) { ActiveStep.StatusClass = "alert-info"; ActiveStep.StatusMessage = DateTime.Now +" Handle valid submit"; // last step, try save to database... } else { ActiveStep.StatusClass = "alert-danger"; ActiveStep.StatusMessage = DateTime.Now +" Handle invalid submit"; } } if (ActiveStepIx < Steps.Count - 1 && ActiveStep.StepCompleted) { SetActive(Steps[(Steps.IndexOf(ActiveStep) + 1)]); } } /// <summary> /// /// </summary> /// <param name="obj"></param> /// <returns></returns> private bool CheckValidationStatus(Object obj) { //if all fields is ok ValidationContext ctx = new ValidationContext(obj); List<ValidationResult> results = new List<ValidationResult>(); bool _defaultvalidatorSuccess = Validator.TryValidateObject(obj, ctx, results); return _defaultvalidatorSuccess; } /// <summary> /// Populates the <see cref="ActiveStep"/> the Sets the passed in <see cref="WizardStep"/> instance as the /// </summary> /// <param name="step">The WizardStep</param> protected internal void SetActive(WizardStep step) { ActiveStep = step ?? throw new ArgumentNullException(nameof(step)); CurrentStepModelObject = ActiveStep.StepModel; ActiveStepIx = StepsIndex(step); if (ActiveStepIx == Steps.Count - 1) IsLastStep = true; else IsLastStep = false; } /// <summary> /// Retrieves the index of the current <see cref="WizardStep"/> in the Step List /// </summary> /// <param name="step">The WizardStep</param> /// <returns></returns> public int StepsIndex(WizardStep step) => StepsIndexInternal(step); protected int StepsIndexInternal(WizardStep step) { if (step == null) throw new ArgumentNullException(nameof(step)); return Steps.IndexOf(step); } /// <summary> /// Adds a <see cref="WizardStep"/> to the WizardSteps list /// </summary> /// <param name="step"></param> protected internal void AddStep(WizardStep step) { Steps.Add(step); } protected override void OnAfterRender(bool firstRender) { if (firstRender) { SetActive(Steps[0]); ActiveStep.StepCompleted = false; StateHasChanged(); } } protected override async Task OnInitializedAsync() { CurrentStepModelObject = new object(); }WizardStep.razor
@if (Parent.ActiveStep == this) {<div class="alert @StatusClass">@StatusMessage</div><div id="step-@(Parent.StepsIndex(this) + 1)"> @ChildContent</div> }WizardStep.razor.cs
public partial class WizardStep{ /// <summary> /// The <see cref="Wizard"/> container /// </summary> [CascadingParameter] protected internal Wizard Parent { get; set; } public string StatusMessage { get; set; } public string StatusClass { get; set;} [Parameter] public object StepModel { get; set; } /// <summary> /// The Child Content of the current <see cref="WizardStep"/> /// </summary> [Parameter] public RenderFragment ChildContent { get; set; } /// <summary> /// The Name of the step /// </summary> [Parameter] public string Name { get; set; } public bool StepCompleted { get; set; } protected override void OnInitialized() { Parent.AddStep(this); }}Does anyone have an idea of what's wrong with the code?