Reputation: 6202
I'm creating a Blazor component similar to DataTables
and also I want to implement generic filters. For that, I have created 2 components for numbers: one integer number and another one for floating number.
From the main component, I want to pass type of number.
<NumberInput TNumber="short"
Value="@FilterRule.FilterValue"
OnValueChangedEvent="@FilterRule.UpdateFilterValue"
IncludeApply="IncludeApply()"
IsApplied="@FilterRule.IsApplied"
AppliedText="@FilterRule.FilterValue!.ToString()"
ApplyFilterEvent="(e) => ApplyFilter(FilterRule.Guid, true)"
UnApplyFilterEvent="(e) => UnApplyFilter(FilterRule.Guid, true)"
Attributes="Attributes"
ApplyButtonCssClass="@ApplyButtonCssClass"
InputCssClass="@InputCssClass"
MaxWidth="MaxWidth" />
For example, TNumber
can have short
, int
, long
and so on. This is because I have a function that gives me the minimum and maximum values for each type of number.
public static Tuple<T, T> GetMinMaxValue<T>()
{
object obj1 = (object)default(T);
object obj2 = (object)default(T);
if (obj1 == null || obj2 == null)
return (Tuple<T, T>)null;
switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.Int16:
obj1 = (object)char.MinValue;
obj2 = (object)char.MaxValue;
break;
case TypeCode.SByte:
obj1 = (object)sbyte.MinValue;
obj2 = (object)sbyte.MaxValue;
break;
// and so on...
}
}
The NumberInput
has this code:
<input class="@DefaultClass form-control-sm @InputCssClass" type="number" value="@Value"
style="@(MaxWidth != 0 ? string.Format("max-width: {0}px", (object) this.MaxWidth) : "")"
@onchange="@UpdateValue" step="any">
@code {
[Parameter] public long Value { get; set; }
[Parameter] public EventCallback<ValueChangedEventArgs> OnValueChangedEvent { get; set; }
[Parameter] public string DefaultClass { get; set; } = "form-control";
[Parameter] public bool IncludeApply { get; set; } = false;
[Parameter] public bool IsApplied { get; set; } = false;
[Parameter] public string AppliedText { get; set; } = "";
[Parameter] public string InputCssClass { get; set; } = "";
[Parameter] public string ApplyButtonCssClass { get; set; } = "";
[Parameter] public Dictionary<string, object> Attributes { get; set; }
[Parameter] public int MaxWidth { get; set; }
[Parameter] public EventCallback<MouseEventArgs> ApplyFilterEvent { get; set; }
[Parameter] public EventCallback<MouseEventArgs> UnApplyFilterEvent { get; set; }
private async Task UpdateValue(ChangeEventArgs args)
{
Value = Convert.ToInt32(args.Value.ToString());
await this.OnValueChangedEvent.InvokeAsync(new ValueChangedEventArgs((object)this.Value));
}
}
So, the question is: how can I pass as parameter the type of variable I want to use?
Upvotes: 2
Views: 1836
Reputation: 4854
@typeparam
directiveAs Brian Parker hinted in the comments, the solution is to add a type parameter to the NumberInput
component. In the NumberInput
component, add the @typeparam
Razor directive:
@typeparam TNumber
This is nothing but a generic type parameter in disguise.
You can now use this parameter anywhere in the NumberInput
component just like you'd with a generic parameter in a C# type like a class or struct.
Most of the time, the types of type parameters can be inferred and setting them explicitly is redundant. However, if the type cannot be inferred, it can be explicitly specified:
<NumberInput
TNumber="int" @* Explicitly passing type *@
Value="@FilterRule.FilterValue"
OnValueChangedEvent="@FilterRule.UpdateFilterValue"
IncludeApply="IncludeApply()"
IsApplied="@FilterRule.IsApplied"
AppliedText="@FilterRule.FilterValue!.ToString()"
ApplyFilterEvent="(e) => ApplyFilter(FilterRule.Guid, true)"
UnApplyFilterEvent="(e) => UnApplyFilter(FilterRule.Guid, true)"
Attributes="Attributes"
ApplyButtonCssClass="@ApplyButtonCssClass"
InputCssClass="@InputCssClass"
MaxWidth="MaxWidth" />
Also, as Mister Magoo pointed out, you should not overwrite to parameter values because if the container component calls StateHasChanged()
, all the parameters of the child components will be reset to their original values and state will be lost. For more info, see this. To safely maintain state, copy the parameter value to a private field first, in OnInitialized
or OnInitializedAsync
:
private long _value;
protected override void OnInitialized()
{
_value = Value;
}
NumberInput
Finally, NumberInput
would look like this:
@typeparam TNumber
<input class="@DefaultClass form-control-sm @InputCssClass"
type="number"
value="@Value"
style="@(MaxWidth != 0 ? string.Format("max-width: {0}px", (object) this.MaxWidth) : "")"
@onchange="@UpdateValue"
step="any" />
@code {
private long _value;
[Parameter] public long Value { get; set; }
[Parameter] public EventCallback<ValueChangedEventArgs> OnValueChangedEvent { get; set; }
[Parameter] public string DefaultClass { get; set; } = "form-control";
[Parameter] public bool IncludeApply { get; set; } = false;
[Parameter] public bool IsApplied { get; set; } = false;
[Parameter] public string AppliedText { get; set; } = "";
[Parameter] public string InputCssClass { get; set; } = "";
[Parameter] public string ApplyButtonCssClass { get; set; } = "";
[Parameter] public Dictionary<string, object> Attributes { get; set; }
[Parameter] public int MaxWidth { get; set; }
[Parameter] public EventCallback<MouseEventArgs> ApplyFilterEvent { get; set; }
[Parameter] public EventCallback<MouseEventArgs> UnApplyFilterEvent { get; set; }
private async Task UpdateValue(ChangeEventArgs args)
{
_value = Convert.ToInt32(args.Value.ToString());
await this.OnValueChangedEvent.InvokeAsync(new ValueChangedEventArgs((object)this.Value));
}
protected override void OnInitialized()
{
_value = Value;
}
}
Upvotes: 3