自定义组件上的 Blazor TwoWay 绑定
Blazor TwoWay Binding on custom Component
我正在创建 Blazor 服务器端应用程序,但在两个自定义组件之间绑定值时遇到问题。
我查看了 bind 或 @bind 应该如何工作的不同示例,但我无法弄清楚关于这个问题的最新信息是什么。
给定一个模型class用户:
public class User
{
[Mapping(ColumnName = "short_id")]
public string ShortId { get; set; }
public User()
{
}
}
我想构建一个表单来显示该用户的所有属性并将它们包含在输入中,以便对其进行编辑并最终保存到数据库中。
我的表单(父组件)如下所示:
<div class="edit-user-form">
<AnimatedUserInput TbText="@User.ShortId" Placeholder="MHTEE Id"/>
<button class="btn btn-secondary" @onclick="Hide" style="{display:inline-block;}">Back</button>
<button class="btn btn-primary" @onclick="SaveUser" style="{display:inline-block;}">Save</button>
</div>
@code {
[Parameter] public vUser User { get; set; }
[Parameter] public Action Hide { get; set; }
bool _error = false;
void SaveUser()
{
ValidateUserData();
}
void ValidateUserData()
{
_error = string.IsNullOrWhiteSpace(User.ShortId);
}
}
其中 AnimatedUserInput
是自定义组件,如下所示:
<div class="edit-area @FocusClass @WiggleClass">
<input type="text" @bind="@TbText" />
<span data-placeholder="@Placeholder"></span>
</div>
@code {
[Parameter]
public string TbText { get; set; }
[Parameter]
public string Placeholder { get; set; }
}
现在,在输入文本框中,我正确地看到了父组件中 User
对象的 ShortId
。
但是,如果我更改输入中的文本并单击触发 ValidateUserData
方法并允许我查看当前 User
对象的 Save
按钮,我会看到在实际 User.ShortId
属性 中未进行任何更改,但仅在输入中进行更改。
有没有办法绑定它,以便输入中的更改自动应用到绑定的属性?
我有几个属性需要在表单中显示,这就是为什么我不想为每个属性挂钩自定义 OnChanged
事件。
好的,对于任何遇到这个问题的人来说。我尝试了更多并找到了解决方案。
因此,对于我的自定义输入组件 AnimatedUserInput
,我添加了一个 EventCallback,每次更新输入值时我都会调用它:
@code {
[Parameter]
public string TbText
{
get => _tbText;
set
{
if (_tbText == value) return;
_tbText = value;
TbTextChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<string> TbTextChanged { get; set; }
[Parameter]
public string Placeholder { get; set; }
private string _tbText;
}
我的父组件上的绑定如下所示:
<div class="edit-user-form">
<AnimatedUserInput @bind-TbText="@User.ShortId" Placeholder="MHTEE Id"/>
<AnimatedUserInput @bind-TbText="@User.FirstName" Placeholder="First name" />
<AnimatedUserInput @bind-TbText="@User.LastName" Placeholder="Last name" />
<AnimatedUserInput @bind-TbText="@User.UserName" Placeholder="Username" />
<AnimatedUserInput @bind-TbText="@User.StaffType" Placeholder="Staff type" />
<AnimatedUserInput @bind-TbText="@User.Token" Placeholder="Token" />
<button class="btn btn-secondary" @onclick="Hide" style="{display:inline-block;}">Back</button>
<button class="btn btn-primary" @onclick="SaveUser" style="{display:inline-block;}">Save</button>
</div>
@code {
[Parameter] public vUser User { get; set; }
}
通过这种方式,blazor 可以正确地将值绑定在一起,并按照我希望它们绑定的方式从两侧更新它们。
这就是我实现双向绑定的方式
父组件
<LabelledField Class=""
Label="Label value"
DateValue="@Model.Value"
OnDateChange="@((e) => Model.Value = e )"
Type="InputType.date" />
子组件
<div class="@Class">
<div class="ft-07 mb-03 field-label @LabelClass">@Label</div>
@switch (Type)
{
case InputType.constant:
<div class="label-field-value @ValueClass">@Value</div>
break;
case InputType.text:
<input class="form-control input @ValueClass"
type="text"
@bind="Value" />
break;
case InputType.date:
<input class="form-control input @ValueClass"
type="date"
@bind="DateValue" />
break;
default:
break;
}
@code{
private string _tbText;
private DateTime? _tbDate;
[Parameter]
public string Class { get; set; }
[Parameter]
public string Label { get; set; }
[Parameter]
public string LabelClass { get; set; }
[Parameter]
public string Value
{
get => _tbText;
set
{
if (_tbText == value) return;
_tbText = value;
OnTextChange.InvokeAsync(value);
}
}
[Parameter]
public DateTime? DateValue
{
get => _tbDate;
set
{
if (_tbDate == value) return;
_tbDate = value;
OnDateChange.InvokeAsync(_tbDate);
}
}
[Parameter]
public string ValueClass { get; set; }
[Parameter]
public InputType Type { get; set; } = InputType.constant;
[Parameter]
public EventCallback<string> OnTextChange { get; set; }
[Parameter]
public EventCallback<DateTime?> OnDateChange { get; set; }
}
感谢你们在这里发布的例子。使用它们,我根据 W3schools' How TO-Toggle Switch 中的 Toggle Switch 示例做了一个自定义输入复选框 two-way 绑定组件,如下所示:
它在改变状态时触发事件。
组件
<div class="@Class">
<label class="switch">
<input id="@Id" type="checkbox" @bind="IsOn" />
<span class="slider round"></span>
</label>
</div>
@code {
[Parameter] public EventCallback<bool> OnToggle { get; set; }
[Parameter] public string Id { get; set; }
[Parameter] public string Class { get; set; }
[Parameter] public bool SetAsChecked { get; set; }
private bool _isSettingUp = true;
private bool _isOn;
protected override Task OnInitializedAsync()
{
IsOn = SetAsChecked;
return base.OnInitializedAsync();
}
private bool IsOn
{
get => _isOn;
set
{
_isOn = value;
if (!_isSettingUp)
{
OnToggle.InvokeAsync(value);
}
_isSettingUp = false;
}
}
}
_isSettingUp
标志是我发现在最初设置检查时不触发 EventCallback 的方式。也许有人找到了更好的方法来处理这个问题。
Parent
(这里是你使用组件的地方)
<ToggleSwitch Id="mySwitch" OnToggle="((isChecked)=>DoSomething(isChecked))" SetAsChecked="isChecked" />
@code {
private bool isChecked;
protected override void OnInitialized()
{
isChecked = true;
}
protected void DoSomething(bool isChecked)
{
Console.WriteLine($">>>> {isChecked} <<<<");
}
}
CSS
CSS 改编自 W3Scholl 示例 here。
如果您想利用 Blazor CSS Isolation,它应该放在一个名为组件的 .razor.css 文件中(在同一文件夹中)。然后你可以使用组件的Class
参数来适应不同的需求。
.switch {
position: relative;
display: inline-block;
width: 60px;
margin-top: 7px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding-top: 2px;
background-color: #737272;
-webkit-transition: .7s;
transition: .7s;
width: 62px;
height: 29px;
}
.slider:before {
position: absolute;
content: "16";
text-align: center;
color: dimgrey;
height: 25px;
width: 25px;
left: 2px;
bottom: 2px;
padding-top: 1px;
background-color: white;
-webkit-transition: .8s;
transition: .8s;
}
input:checked + .slider {
background-color: limegreen;
}
input:checked + .slider:before {
-webkit-transform: translateX(33px);
-ms-transform: translateX(33px);
transform: translateX(33px);
content: "14";
text-align: center;
color: dodgerblue;
}
.slider.round {
border-radius: 20px;
}
.slider.round:before {
border-radius: 50%;
}
顺便说一下,你可以放任何有搜索的HTML Symbol in the switch. I did pick these from this cool page和CSS代码,如果我们在CSS内容中添加符号,这就是我们需要的,如这里。
希望这对某人有用。
Blazor 的一些数据绑定资源没有提到,当您执行 @bind-Value 时,您实际上会自动映射到 3 个参数:
绑定调用
<CustomComponent TValue="DateTime?" @bind-Value="order.PurchaseDate"></CustomComponent>
自定义组件
@using System.Linq.Expressions;
@typeparam TValue
//...
@code {
[Parameter]
public virtual TValue Value { get; set; }
[Parameter]
public EventCallback<TValue> ValueChanged { get; set; }
[Parameter]
public Expression<Func<TValue>> ValueExpression { get; set; }
//...
}
如果您想要一个简化用户界面的组件,了解这一点很重要,因为您可能需要该值表达式来进行表单验证...
<ValidationMessage For="ValueExpression" ></ValidationMessage>
在类似的意义上,我也使用这个 ValueExpression 访问器方法来 运行 我的标签,当然你可能想要一个模型数据注释,就像你可能想知道字段是否有最大值一样长度,或者是必需的...
@( DisplayName.For(ValueExpression) )
代码的显示名称:
public static class StringHelpers
{
public static string CamelCaseToPhrase(this string self)
{
return Regex.Replace(self, "([A-Z])", " ").Trim();
}
}
public static class DisplayName
{
public static string For(string input)
{
return input.CamelCaseToPhrase();
}
public static string For<T>(Expression<Func<T>> accessor)
{
var expression = (MemberExpression)accessor.Body;
var value = expression.Member.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
return value?.Name ?? expression.Member.Name?.CamelCaseToPhrase() ?? "";
}
}
我在这里分享这个是因为 google 在我搜索这个的时候把我带到了这里,而且那里的大多数其他资源在提供这个完整图片方面做得不好,所以希望这也能帮助其他人。特别是如果您想访问数据注释,该模式也包括在内。
我正在创建 Blazor 服务器端应用程序,但在两个自定义组件之间绑定值时遇到问题。
我查看了 bind 或 @bind 应该如何工作的不同示例,但我无法弄清楚关于这个问题的最新信息是什么。
给定一个模型class用户:
public class User
{
[Mapping(ColumnName = "short_id")]
public string ShortId { get; set; }
public User()
{
}
}
我想构建一个表单来显示该用户的所有属性并将它们包含在输入中,以便对其进行编辑并最终保存到数据库中。
我的表单(父组件)如下所示:
<div class="edit-user-form">
<AnimatedUserInput TbText="@User.ShortId" Placeholder="MHTEE Id"/>
<button class="btn btn-secondary" @onclick="Hide" style="{display:inline-block;}">Back</button>
<button class="btn btn-primary" @onclick="SaveUser" style="{display:inline-block;}">Save</button>
</div>
@code {
[Parameter] public vUser User { get; set; }
[Parameter] public Action Hide { get; set; }
bool _error = false;
void SaveUser()
{
ValidateUserData();
}
void ValidateUserData()
{
_error = string.IsNullOrWhiteSpace(User.ShortId);
}
}
其中 AnimatedUserInput
是自定义组件,如下所示:
<div class="edit-area @FocusClass @WiggleClass">
<input type="text" @bind="@TbText" />
<span data-placeholder="@Placeholder"></span>
</div>
@code {
[Parameter]
public string TbText { get; set; }
[Parameter]
public string Placeholder { get; set; }
}
现在,在输入文本框中,我正确地看到了父组件中 User
对象的 ShortId
。
但是,如果我更改输入中的文本并单击触发 ValidateUserData
方法并允许我查看当前 User
对象的 Save
按钮,我会看到在实际 User.ShortId
属性 中未进行任何更改,但仅在输入中进行更改。
有没有办法绑定它,以便输入中的更改自动应用到绑定的属性?
我有几个属性需要在表单中显示,这就是为什么我不想为每个属性挂钩自定义 OnChanged
事件。
好的,对于任何遇到这个问题的人来说。我尝试了更多并找到了解决方案。
因此,对于我的自定义输入组件 AnimatedUserInput
,我添加了一个 EventCallback,每次更新输入值时我都会调用它:
@code {
[Parameter]
public string TbText
{
get => _tbText;
set
{
if (_tbText == value) return;
_tbText = value;
TbTextChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<string> TbTextChanged { get; set; }
[Parameter]
public string Placeholder { get; set; }
private string _tbText;
}
我的父组件上的绑定如下所示:
<div class="edit-user-form">
<AnimatedUserInput @bind-TbText="@User.ShortId" Placeholder="MHTEE Id"/>
<AnimatedUserInput @bind-TbText="@User.FirstName" Placeholder="First name" />
<AnimatedUserInput @bind-TbText="@User.LastName" Placeholder="Last name" />
<AnimatedUserInput @bind-TbText="@User.UserName" Placeholder="Username" />
<AnimatedUserInput @bind-TbText="@User.StaffType" Placeholder="Staff type" />
<AnimatedUserInput @bind-TbText="@User.Token" Placeholder="Token" />
<button class="btn btn-secondary" @onclick="Hide" style="{display:inline-block;}">Back</button>
<button class="btn btn-primary" @onclick="SaveUser" style="{display:inline-block;}">Save</button>
</div>
@code {
[Parameter] public vUser User { get; set; }
}
通过这种方式,blazor 可以正确地将值绑定在一起,并按照我希望它们绑定的方式从两侧更新它们。
这就是我实现双向绑定的方式
父组件
<LabelledField Class=""
Label="Label value"
DateValue="@Model.Value"
OnDateChange="@((e) => Model.Value = e )"
Type="InputType.date" />
子组件
<div class="@Class">
<div class="ft-07 mb-03 field-label @LabelClass">@Label</div>
@switch (Type)
{
case InputType.constant:
<div class="label-field-value @ValueClass">@Value</div>
break;
case InputType.text:
<input class="form-control input @ValueClass"
type="text"
@bind="Value" />
break;
case InputType.date:
<input class="form-control input @ValueClass"
type="date"
@bind="DateValue" />
break;
default:
break;
}
@code{
private string _tbText;
private DateTime? _tbDate;
[Parameter]
public string Class { get; set; }
[Parameter]
public string Label { get; set; }
[Parameter]
public string LabelClass { get; set; }
[Parameter]
public string Value
{
get => _tbText;
set
{
if (_tbText == value) return;
_tbText = value;
OnTextChange.InvokeAsync(value);
}
}
[Parameter]
public DateTime? DateValue
{
get => _tbDate;
set
{
if (_tbDate == value) return;
_tbDate = value;
OnDateChange.InvokeAsync(_tbDate);
}
}
[Parameter]
public string ValueClass { get; set; }
[Parameter]
public InputType Type { get; set; } = InputType.constant;
[Parameter]
public EventCallback<string> OnTextChange { get; set; }
[Parameter]
public EventCallback<DateTime?> OnDateChange { get; set; }
}
感谢你们在这里发布的例子。使用它们,我根据 W3schools' How TO-Toggle Switch 中的 Toggle Switch 示例做了一个自定义输入复选框 two-way 绑定组件,如下所示:
它在改变状态时触发事件。
组件
<div class="@Class">
<label class="switch">
<input id="@Id" type="checkbox" @bind="IsOn" />
<span class="slider round"></span>
</label>
</div>
@code {
[Parameter] public EventCallback<bool> OnToggle { get; set; }
[Parameter] public string Id { get; set; }
[Parameter] public string Class { get; set; }
[Parameter] public bool SetAsChecked { get; set; }
private bool _isSettingUp = true;
private bool _isOn;
protected override Task OnInitializedAsync()
{
IsOn = SetAsChecked;
return base.OnInitializedAsync();
}
private bool IsOn
{
get => _isOn;
set
{
_isOn = value;
if (!_isSettingUp)
{
OnToggle.InvokeAsync(value);
}
_isSettingUp = false;
}
}
}
_isSettingUp
标志是我发现在最初设置检查时不触发 EventCallback 的方式。也许有人找到了更好的方法来处理这个问题。
Parent
(这里是你使用组件的地方)
<ToggleSwitch Id="mySwitch" OnToggle="((isChecked)=>DoSomething(isChecked))" SetAsChecked="isChecked" />
@code {
private bool isChecked;
protected override void OnInitialized()
{
isChecked = true;
}
protected void DoSomething(bool isChecked)
{
Console.WriteLine($">>>> {isChecked} <<<<");
}
}
CSS
CSS 改编自 W3Scholl 示例 here。
如果您想利用 Blazor CSS Isolation,它应该放在一个名为组件的 .razor.css 文件中(在同一文件夹中)。然后你可以使用组件的Class
参数来适应不同的需求。
.switch {
position: relative;
display: inline-block;
width: 60px;
margin-top: 7px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding-top: 2px;
background-color: #737272;
-webkit-transition: .7s;
transition: .7s;
width: 62px;
height: 29px;
}
.slider:before {
position: absolute;
content: "16";
text-align: center;
color: dimgrey;
height: 25px;
width: 25px;
left: 2px;
bottom: 2px;
padding-top: 1px;
background-color: white;
-webkit-transition: .8s;
transition: .8s;
}
input:checked + .slider {
background-color: limegreen;
}
input:checked + .slider:before {
-webkit-transform: translateX(33px);
-ms-transform: translateX(33px);
transform: translateX(33px);
content: "14";
text-align: center;
color: dodgerblue;
}
.slider.round {
border-radius: 20px;
}
.slider.round:before {
border-radius: 50%;
}
顺便说一下,你可以放任何有搜索的HTML Symbol in the switch. I did pick these from this cool page和CSS代码,如果我们在CSS内容中添加符号,这就是我们需要的,如这里。
希望这对某人有用。
Blazor 的一些数据绑定资源没有提到,当您执行 @bind-Value 时,您实际上会自动映射到 3 个参数:
绑定调用
<CustomComponent TValue="DateTime?" @bind-Value="order.PurchaseDate"></CustomComponent>
自定义组件
@using System.Linq.Expressions;
@typeparam TValue
//...
@code {
[Parameter]
public virtual TValue Value { get; set; }
[Parameter]
public EventCallback<TValue> ValueChanged { get; set; }
[Parameter]
public Expression<Func<TValue>> ValueExpression { get; set; }
//...
}
如果您想要一个简化用户界面的组件,了解这一点很重要,因为您可能需要该值表达式来进行表单验证...
<ValidationMessage For="ValueExpression" ></ValidationMessage>
在类似的意义上,我也使用这个 ValueExpression 访问器方法来 运行 我的标签,当然你可能想要一个模型数据注释,就像你可能想知道字段是否有最大值一样长度,或者是必需的...
@( DisplayName.For(ValueExpression) )
代码的显示名称:
public static class StringHelpers
{
public static string CamelCaseToPhrase(this string self)
{
return Regex.Replace(self, "([A-Z])", " ").Trim();
}
}
public static class DisplayName
{
public static string For(string input)
{
return input.CamelCaseToPhrase();
}
public static string For<T>(Expression<Func<T>> accessor)
{
var expression = (MemberExpression)accessor.Body;
var value = expression.Member.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
return value?.Name ?? expression.Member.Name?.CamelCaseToPhrase() ?? "";
}
}
我在这里分享这个是因为 google 在我搜索这个的时候把我带到了这里,而且那里的大多数其他资源在提供这个完整图片方面做得不好,所以希望这也能帮助其他人。特别是如果您想访问数据注释,该模式也包括在内。