Validate a Telerik component as child component and apply invalid border
Environment
Product |
AutoComplete for Blazor, ComboBox for Blazor, DatePicker for Blazor, DateTimePicker for Blazor, DropDownList for Blazor, MultiColumnComboBox for Blazor, MultiSelect for Blazor, TimePicker for Blazor |
Description
I am wrapping a Telerik component inside a custom component for my application. When I try to validate it the red invalid border does not appear.
Solution
Internally, the Telerik Blazor components use the cascading EditContext
parameter that the EditForm
provides to determine if validation passes or fails. If it fails, we add a class that shows a red border around the component.
When you abstract the component in a custom component you should specify the ValueExpression
- this is the field that notifies the framework (EditForm
) what value should pass certain criteria. It is generated automatically by the framework when using @bind-Value
when directly in the edit form, but not when there is another component in the hierarchy.
The example below shows how to wrap a ComboBox (adding two-way data binding) in a different .razor
file and get the invalid red border when the validation does not pass.
@* Validate the value for the combobox. In this example the invalid value is the CEO or no selection.
This component is generic to showcase the concept, it does not have to be if you know the data type of the field or you do not itend to use it for several field types. It receives its Data from the parent to showcase this is possible too. We use a nullable integer and a string in this example.
*@
@typeparam T
@typeparam TItem
@using System.Linq.Expressions
<TelerikComboBox Value="@CBValue" ValueChanged="@( async (T v) => await RaiseValueChanged(v) )" ValueExpression="@CustomValueExpression"
Data="@MyData" TextField="@TextFieldCustom" ValueField="@ValueFieldCustom" Id="@MyId" AllowCustom="@AllowCustom">
</TelerikComboBox>
@code {
[Parameter]
public T CBValue { get; set; }
[Parameter]
public EventCallback<T> CBValueChanged { get; set; }
[Parameter]
public Expression<System.Func<T>> CustomValueExpression { get; set; }
[Parameter]
public IEnumerable<TItem> MyData { get; set; }
[Parameter]
public string TextFieldCustom { get; set; }
[Parameter]
public string ValueFieldCustom { get; set; }
[Parameter]
public string MyId { get; set; }
[Parameter]
public bool AllowCustom { get; set; }
async Task RaiseValueChanged(T v)
{
// two-way binding for the current component, and raising an event that provides
// two-way binding for the parent component. We can't use @bind-Value here because
// we want to explicitly set the ValueExpression for the validation
CBValue = v;
if (CBValueChanged.HasDelegate)
{
await CBValueChanged.InvokeAsync(CBValue);
}
}
}
@* We declare the child component here. The EditContext is generated by the edit form and comes down as a cascading parameter. We provide the ValueExpression to the child component so validation can work there too. *@
<EditForm Model="@person" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p class="team">
<label for="teamCombobox">Team:</label>
<MyCustomComponent @bind-CBValue="@person.Team"
CustomValueExpression="@( () => person.Team )"
MyData="@teams"
MyId="teamCombobox"
TextFieldCustom="MyTextField"
ValueFieldCustom="MyValueField">
</MyCustomComponent>
<div>Current value: @person.Team</div>
<ValidationMessage For="@(() => person.Team)"></ValidationMessage>
</p>
<p>
<label for="rolesCombobox">Team:</label>
<MyCustomComponent @bind-CBValue="@person.Role"
CustomValueExpression="@( () => person.Role )"
MyData="@roles"
MyId="rolesCombobox"
TextFieldCustom="Text"
ValueFieldCustom="Value"
AllowCustom="true">
</MyCustomComponent>
<ValidationMessage For="@(() => person.Role)"></ValidationMessage>
<div>Current value: @person.Role</div>
</p>
<TelerikButton ButtonType="@ButtonType.Submit">Submit</TelerikButton>
</EditForm>
@code {
Person person = new Person();
IEnumerable<MyDdlModel> teams = new List<MyDdlModel>
{
new MyDdlModel {MyTextField = "Team 1", MyValueField = 1},
new MyDdlModel {MyTextField = "Team 2", MyValueField = 2},
new MyDdlModel {MyTextField = "Team 3", MyValueField = 3},
new MyDdlModel {MyTextField = "CEO", MyValueField = 4}
};
IEnumerable<MyDdlModelString> roles = new List<MyDdlModelString>
{
new MyDdlModelString { Text = "Developer", Value = "Dev" },
new MyDdlModelString { Text = "QA", Value = "QA" },
new MyDdlModelString { Text = "Support", Value = "Support" }
};
void HandleValidSubmit()
{
Console.WriteLine("OnValidSubmit");
}
}
using System.ComponentModel.DataAnnotations;
public class Person
{
[Required(ErrorMessage = "Team is mandatory.")]//the value field in the combobox model must be null for this to have effect
[Range(1, 3, ErrorMessage = "Please select an actual team.")] //limits the fourth option just to showcase this is honored
public int? Team { get; set; }
[Required]
[StringLength(10, ErrorMessage = "Enter less than 10 symbols")]
public string Role { get; set; }
}
public class MyDdlModel
{
public int? MyValueField { get; set; }
public string MyTextField { get; set; }
}
public class MyDdlModelString
{
public string Value { get; set; }
public string Text { get; set; }
}