多步 Blazor 表单尝试通过单击普通按钮提交
Multi step Blazor form attempts to get submitted on click of an ordinary button
我正在根据 this 文章使用向导组件逻辑创建一个多步骤 Blazor 表单。项目是 .NET 5.0 Blazor 客户端(托管)。有一个父 Wizard
组件有 WizardStep
个子组件。
我正在尝试创建一个简单的两步表单。步骤号1 包含输入日期控件和步骤编号。 2/最后一步包含文本区域控件。
在上图中可以看出,我表单第 1 步中的 Next 按钮的类型为“按钮”。单击此 Next 按钮时,当前的 WizardStep
组件将被替换并呈现 next/final WizardStep
组件。最后的 WizardStep
包含文本区域控件,并且 Next 按钮被替换为 Submit 按钮。但是可以看到最后一步显示Validation errors。这些验证错误在最后一步没有单击 提交 按钮时出现。
我已经调试了该应用程序,可以看出在步骤 1 中单击 下一步 按钮后,表单会尝试提交,这就是出现验证错误的原因。这不应该发生,因为 Next 按钮的类型是“按钮”。
为什么会出现这个问题?表单应仅在单击“提交”类型的按钮后提交,并且此按钮出现在向导的最后一步。
上图中还需要注意的一点是,表单 next/final 步骤中的 提交 按钮看起来就像被单击一样。为什么?
表格
<EditForm Model="model" OnValidSubmit="SubmitValidForm">
<DataAnnotationsValidator />
<ValidationSummary />
<Wizard Id="TestForm">
<WizardStep Name="First Step">
<div class="form-group">
<label>Date:</label>
<div>
<InputDate @bind-Value="@model.Date" class="form-control" />
</div>
<ValidationMessage For="@(() => model.Date)" />
</div>
</WizardStep>
<WizardStep Name="Final Step">
<div class="form-group ">
<label>Details:</label>
<div>
<InputTextArea @bind-Value="@model.Entry" class="form-control" rows="8" />
</div>
<ValidationMessage For="@(() => model.Entry)" />
</div>
</WizardStep>
</Wizard>
</EditForm>
精灵
<CascadingValue Value="this">
<div id="@Id">
<div class="card">
<div class="card-header">
<div class="row justify-content-center align-items-center">
Step @(ActiveStepIx + 1) of @Steps.Count
</div>
</div>
<div class="card-body">
@ChildContent
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col p-0">
<button class="col-6 btn btn-secondary btn-sm float-left" type="button"
disabled="@(ActiveStepIx == 0)" @onclick="GoBack">
Previous
</button>
</div>
<div class="col p-0">
<button class="col-6 btn btn-primary btn-sm float-right"
type="@(IsLastStep ? "submit" : "button")" @onclick="GoNext">
@(IsLastStep ? "Submit" : "Next")
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</CascadingValue>
@code
{
/// <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; }
/// <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()
{
if (ActiveStepIx < Steps.Count - 1)
SetActive(Steps[(Steps.IndexOf(ActiveStep) + 1)]);
}
/// <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));
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]);
StateHasChanged();
}
}
}
向导步骤
@if (Parent.ActiveStep == this)
{
<div id="step-@(Parent.StepsIndex(this) + 1)">
@ChildContent
</div>
}
@code {
/// <summary>
/// The <see cref="Wizard"/> container
/// </summary>
[CascadingParameter]
protected internal Wizard Parent { 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; }
protected override void OnInitialized()
{
Parent.AddStep(this);
}
}
Wizard
和 WizardStep
逻辑可以在 this 文章中阅读。根据我的理解,基本问题是通过单击普通“按钮”提交表单,这不应该发生。
请尝试在下一个按钮上使用此设置:
<button @onclick:stopPropagation=true/>
HTMLDOM(文档对象模型)中的事件冒泡,在这种情况下,按钮单击事件冒泡并到达提交按钮并导致提交,如果我们不这样做想要一个事件冒泡到父 DOM 那么我们应该抑制它们。
我正在根据 this 文章使用向导组件逻辑创建一个多步骤 Blazor 表单。项目是 .NET 5.0 Blazor 客户端(托管)。有一个父 Wizard
组件有 WizardStep
个子组件。
我正在尝试创建一个简单的两步表单。步骤号1 包含输入日期控件和步骤编号。 2/最后一步包含文本区域控件。
在上图中可以看出,我表单第 1 步中的 Next 按钮的类型为“按钮”。单击此 Next 按钮时,当前的 WizardStep
组件将被替换并呈现 next/final WizardStep
组件。最后的 WizardStep
包含文本区域控件,并且 Next 按钮被替换为 Submit 按钮。但是可以看到最后一步显示Validation errors。这些验证错误在最后一步没有单击 提交 按钮时出现。
我已经调试了该应用程序,可以看出在步骤 1 中单击 下一步 按钮后,表单会尝试提交,这就是出现验证错误的原因。这不应该发生,因为 Next 按钮的类型是“按钮”。
为什么会出现这个问题?表单应仅在单击“提交”类型的按钮后提交,并且此按钮出现在向导的最后一步。
上图中还需要注意的一点是,表单 next/final 步骤中的 提交 按钮看起来就像被单击一样。为什么?
表格
<EditForm Model="model" OnValidSubmit="SubmitValidForm">
<DataAnnotationsValidator />
<ValidationSummary />
<Wizard Id="TestForm">
<WizardStep Name="First Step">
<div class="form-group">
<label>Date:</label>
<div>
<InputDate @bind-Value="@model.Date" class="form-control" />
</div>
<ValidationMessage For="@(() => model.Date)" />
</div>
</WizardStep>
<WizardStep Name="Final Step">
<div class="form-group ">
<label>Details:</label>
<div>
<InputTextArea @bind-Value="@model.Entry" class="form-control" rows="8" />
</div>
<ValidationMessage For="@(() => model.Entry)" />
</div>
</WizardStep>
</Wizard>
</EditForm>
精灵
<CascadingValue Value="this">
<div id="@Id">
<div class="card">
<div class="card-header">
<div class="row justify-content-center align-items-center">
Step @(ActiveStepIx + 1) of @Steps.Count
</div>
</div>
<div class="card-body">
@ChildContent
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col p-0">
<button class="col-6 btn btn-secondary btn-sm float-left" type="button"
disabled="@(ActiveStepIx == 0)" @onclick="GoBack">
Previous
</button>
</div>
<div class="col p-0">
<button class="col-6 btn btn-primary btn-sm float-right"
type="@(IsLastStep ? "submit" : "button")" @onclick="GoNext">
@(IsLastStep ? "Submit" : "Next")
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</CascadingValue>
@code
{
/// <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; }
/// <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()
{
if (ActiveStepIx < Steps.Count - 1)
SetActive(Steps[(Steps.IndexOf(ActiveStep) + 1)]);
}
/// <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));
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]);
StateHasChanged();
}
}
}
向导步骤
@if (Parent.ActiveStep == this)
{
<div id="step-@(Parent.StepsIndex(this) + 1)">
@ChildContent
</div>
}
@code {
/// <summary>
/// The <see cref="Wizard"/> container
/// </summary>
[CascadingParameter]
protected internal Wizard Parent { 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; }
protected override void OnInitialized()
{
Parent.AddStep(this);
}
}
Wizard
和 WizardStep
逻辑可以在 this 文章中阅读。根据我的理解,基本问题是通过单击普通“按钮”提交表单,这不应该发生。
请尝试在下一个按钮上使用此设置:
<button @onclick:stopPropagation=true/>
HTMLDOM(文档对象模型)中的事件冒泡,在这种情况下,按钮单击事件冒泡并到达提交按钮并导致提交,如果我们不这样做想要一个事件冒泡到父 DOM 那么我们应该抑制它们。