Validation Messages is not visible when using an EditForm with custom Wizard Component

374 Views Asked by At

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 like 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 wizzard 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.

enter image description here

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?

0

There are 0 best solutions below