How to use either ?. or ?? to conditionally render Blazor template? - c#

I want to simplify the following
#if (Template != null)
{
#Template
}
else
{
<span>No contents!</span>
}
with either ?? or ?.
Is it possible?
Attempt
My attempts below
#Template?.BeginInvoke()
#{Template==null? #Template : <span> No data to display! </span>}
#(Template??<span> No data to display! </span>)
produce red squiggly lines.
Edit
I think I need to submit the real scenario that I want to simplify.
#typeparam T
#if (Items == null)
{
if (NullTemplate != null)
{
#NullTemplate
}
else
{
<span style="color: red">Null...</span>
}
}
else if (Items.Count == 0)
{
if (EmptyTemplate != null)
{
#EmptyTemplate
}
else
{
<span style="color: red">Empty ...</span>
}
}
else
{
#HeaderTemplate
foreach (T item in Items)
{
#ItemTemplate(item)
}
}
#code{
[Parameter] public RenderFragment NullTemplate { get; set; }
[Parameter] public RenderFragment EmptyTemplate { get; set; }
[Parameter] public RenderFragment HeaderTemplate { get; set; }
[Parameter] public RenderFragment<T> ItemTemplate { get; set; }
[Parameter] public List<T> Items { get; set; }
}
I don't want to buffer the incoming values to ****Template properties with private fields, pre-process the fields before rendering them (the fields) to HTML. In other words, no additional code in #code{} directive are allowed.

It could work (I think) when you store the <span style="color: red">Null...</span> parts as RenderFragments. Would that be acceptable?
That would move this logic to the constructor or the SetParameters method.
[Parameter] public RenderFragment NullTemplate { get; set; } = builder => StubTemplate(builder, "Null");
[Parameter] public RenderFragment EmptyTemplate { get; set; } = builder => StubTemplate(builder, "Empty");
private static void StubTemplate(RenderTreeBuilder __builder, string what)
{
<span style="color: red">#what</span>
}
Note that the __builder name is required.
And then you don't need any conditional code in the main component anymore:
#if (Items == null)
{
#NullTemplate
}
Alternatively, you can omit the property initialization and do :
#(EmptyTemplate ?? (builder => StubTemplate(builder, "Empty")))
but I would get dizzy of all the ().

Related

blazor dynamic forms add validation without model class

im learning blazor and wanted to build some small dynamic form generator
i known that there is already something like VxFormGenerator but wanted to learn by myself a bit - at least for simple forms purposes.
so i have it like this:
DynamicFormsComponent:
<EditForm Model = "#Params" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator/>
#if(Params != null ) #foreach (var field in Params.FormFields)
{
<div class="mb-3">
<label for= "#field.Id">#field.Label :</label>
#switch (field.Type)
{
case FormFieldType.Text:
{
<InputText id="#field.Id" #bind-Value="#field.StrValue" placeholder="#field.PlaceHolder" class="form-control"></InputText>
break;
}
case FormFieldType.Number:
{
<InputNumber id="#field.Id" #bind-Value="#field.IntValue" placeholder="#field.PlaceHolder" class="form-control"> ></InputNumber>
break;
}
case FormFieldType.Date:
{
<InputDate id="#field.Id" #bind-Value="#field.DateValue" placeholder="#field.PlaceHolder" class="form-control"></InputDate>
break;
}
default:
{
break;
}
}
</div>
}
<ValidationSummary></ValidationSummary>
<button type="submit" class="btn btn-primary">#Params?.SendButtonText</button>
public partial class DynamicFormComponent:ComponentBase
{
[Parameter]
public DynamicFormParams Params { get; set; } = new DynamicFormParams();
[Parameter]
public EventCallback<DynamicFormParams> OnValidSubmitCallback { get; set; }
void OnValidSubmit()
{
Console.WriteLine("onValidSubmit");
if (OnValidSubmitCallback.HasDelegate ) OnValidSubmitCallback.InvokeAsync(Params);
//NavigationManager.navigateto.....
}
}
public class DynamicFormParams
{
public List<DynamicFormField> FormFields { get; set; } = new List<DynamicFormField>();
public string FormTitle { get; set; } = string.Empty;
public string SendButtonText { get; set; } = "Send";
}
public class DynamicFormField
{
public string? Label { get; set; }
public string Id { get; set; } = Guid.NewGuid().ToString();
public string PlaceHolder { get; set; } = string.Empty;
public FormFieldType? Type { get; set; }
public string? StrValue { get; set; }
public int? IntValue { get; set; }
public DateTime? DateValue { get; set; }
}
public enum FormFieldType
{
Text,
Number,
Date
}
so the usage would be
<DynamicFormComponent Params="#p" OnValidSubmitCallback=#onChildFormSubmit ></DynamicFormComponent>
DynamicFormParams p = new DynamicFormParams()
{
FormTitle = "test form Title",
SendButtonText = "Wyƛlij",
FormFields = new List<DynamicFormField>()
{
new DynamicFormField()
{
Label="testLabelStr",
Id="anyid-notGuId",
StrValue="a",
PlaceHolder="asdadsad",
Type=FormFieldType.Text
},
new DynamicFormField()
{
Label="testLabelInt",
Type=FormFieldType.Number,
PlaceHolder="enter nr"
},
new DynamicFormField()
{
Label="testLabelDate",
Type=FormFieldType.Date,
DateValue=DateTime.Parse("2021-04-01")
}
}
};
private void onChildFormSubmit(DynamicFormParams pp)
{
Console.WriteLine("from local variable");
Console.WriteLine(JsonSerializer.Serialize(p));
Console.WriteLine("from event arg");
Console.WriteLine(JsonSerializer.Serialize(pp));
}
and the question is:
how with this approach i can use form validation ?
i have no clasic'model' so probably would need something like add 'list of validators ' to my DynamicFormField class
and somehow force DataAnnotationsValidator to use this list ? is it possible withoud clasic 'model' and 'data annotations attributes' on it ?
thanks and regards
The only way to validate form without a model is to use the Blazorise validation system. https://blazorise.com/docs/components/validation.
PS. I'm a Blazorise creator.

Blazor - How to bind list of classes to input

In the //this works, the item.isSelected is updated but the set in private List<CheckboxModel> checkboxes won't run. And in the //this causes infinite loop in this component, nothing is updated and the foreach loop keeps running infinitely.
#foreach (var item in checkboxes)
{
//this works
#*<div class="checkbox" #key="item">
<input type="checkbox" id="#item.text" #bind="item.isSelected">
<label for="#item.text">#item.text</label>
</div>*#
Console.WriteLine(item.text);
//this causes infinite loop in this component
<Checkbox id="#item.text" #key="item" label="#item.text" #bind-isSelected="item.isSelected" />
}
private List<CheckboxModel> _checkboxes { get; set; } = new List<CheckboxModel>();
private List<CheckboxModel> checkboxes
{
get => _checkboxes;
set
{
// won't run
_checkboxes = value;
Console.WriteLine(string.Join(',', value.Where(x => x.isSelected).Select(x => x.text)));
}
}
This is CheckboxModel.cs
public class CheckboxModel
{
public bool isSelected { get; set; }
public string text { get; set; }
}
Checkbox.razor
<div class="checkbox">
<input type="checkbox" id="#id" #bind="#isSelected">
<label for="#id">#label</label>
</div>
#code {
[Parameter]
public string id { get; set; }
[Parameter]
public string label { get; set; }
private bool _isSelected { get; set; }
[Parameter]
public bool isSelected
{
get => _isSelected;
set
{
isSelectedChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<bool> isSelectedChanged { get; set; }
}
What I end up doing was changing Checkbox.razor to
CheckboxList.razor
#foreach (var item in items)
{
<div class="checkbox" #key="item">
<input type="checkbox" id="#item.text" checked="#item.isSelected" #onchange="args=> checkedChanged(item,args)">
<label for="#item.text">#item.text</label>
</div>
}
#code {
[Parameter]
public List<CheckboxModel> items { get; set; }
[Parameter]
public EventCallback<List<CheckboxModel>> itemsChanged { get; set; }
async Task checkedChanged(CheckboxModel item, ChangeEventArgs args)
{
item.isSelected = (bool)args.Value;
await itemsChanged.InvokeAsync(items);
}
}
and in the parent file
<CheckboxList #bind-items="checkboxes" />
#code {
private List<CheckboxModel> checkboxes { get; set; }
}
You can find 2 links in here.
One of them is simpler and the other one has a select all button
In short: I let the child component handle the logic of looping and handling the List passed into it. The child component passes the List back instead of just a bool which fixes the problem where it doesn't trigger the setter function when I update an item in a List.

Blazor [Parameter] not updating input variables

I have a custom component InlineAutocomplete. No matter what I do my pic value and SelectedPIC value are always null when I do validation and unsure why it is.
The component does render correctly, but can't get my parameter back with the values set correctly.
Usage as follow:
<InlineAutocomplete TItem="PIC"
Value="#pic"
Selected="#SelectedPIC"
Data="#PICs"
Property="#((item) => item.PICNumber)" />
My component:
<div class="inline-autocomplete">
<input #oninput="(e)=> SuggestItem(e)" type="text" #bind-value="#Value" class="overlap inline-autocomplete-input " />
<input readonly class="overlap inline-autocomplete-label" value="#_suggestedValue" />
</div>
#code {
string _value;
string _suggestedValue;
[Parameter]
public string Value
{
get => _value;
set
{
if (value == null)
return;
_value = value;
}
}
[Parameter]
public List<TItem> Data { get; set; }
[Parameter]
public Func<TItem, string> Property { get; set; }
[Parameter]
public TItem Selected { get; set; }
void SuggestItem(ChangeEventArgs e)
{
if (string.IsNullOrEmpty(e.Value.ToString()))
return;
Value = e.Value.ToString().ToUpper();
if (string.IsNullOrEmpty(Value))
{
_suggestedValue = string.Empty;
}
else
{
var selectedItem = Data.Select(Property).FirstOrDefault(x => x.StartsWith(Value, StringComparison.OrdinalIgnoreCase));
_suggestedValue = !string.IsNullOrWhiteSpace(selectedItem) ? selectedItem : string.Empty;
Selected = Data.FirstOrDefault(x => string.Equals(Property(x), Value, StringComparison.OrdinalIgnoreCase));
StateHasChanged();
}
// State has changed
StateHasChanged();
}
}
The solution to this was to pass down a function that changes the property in the Parent instead of the Child.
Child:
[Parameter]
public Action<string, TItem> ValueChanged { get; set; }
...
#code {
void SomeFunction() {
ValueChanged(Value, Selected);
}
}
Parent:
<InlineAutocomplete ... ValueChanged="FireEvent" />
void FireEvent(string stringVal, MyClass c)
{
// ... Do Something
}

Dynamically binding input-text to class/object propertys using Blazor

Im trying to build a dynamic list of input field for properties inside a class using Blazor but cant figur out how to bind/link the content of a input box to a property of a class. (the class have can have a large number of public props, not only Name and Description as in the below example, they are not always of the type "string")
lets say that I have this class/model:
public class customer{
public string Name { get; set; }
public int Age { get; set; }
public string Description { get; set; }
}
I got this blazor component (updateC.razor):
#inherits CLogic
#if (nfo != null)
{
#foreach (var obj in nfo)
{
<input type="text" class="form-control"
bind=#SCustomer.GetType().GetProperty(obj.ToString())/>
}
}
and finally Clogic:
public class Clogic: ComponentBase{
[Parameter]
public Customer SCustomer { get; set; } = new Customer();
[Parameter]
public PropertyInfo[] nfo { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
nfo = SCustomer.GetType().GetProperties();
StateHasChanged();
}
}
}
This is suppose to bind changes made in each input field to the correct property in the current instance of SCustomer (when input is made it is suppose to update the correct property of the class/object). This is not working, values inside of SCustomer are not changed after input are done. I'm guessing that i'm going about this completely wrong but can't seem to figure out how to make this work and can't find any examples doing this.
#foreach (var propertyInfo in nfo)
{
<input type="text" class="form-control"
value="#propertyInfo.GetValue(SCustomer)"
#onchange="#((ChangeEventArgs __e) =>
propertyInfo.SetValue(SCustomer, __e.Value.ToString()))" />
}
#foreach(propertyInfo in nfo)
{
<label>#propertyInfo.Name</label>
<input type="text" value="#propertyInfo.GetValue(SCustomer)" #onchange="#((ChangeEventArgs __e) =>
propertyInfo.SetValue(SCustomer,Convert.ChangeType(__e.Value,
propertyInfo.PropertyType,null))"/>
}
I finally found a way of doing this, here is my solution:
I created a helper class:
public class PropHolder
{
[Parameter]
public PropertyInfo info { get; set; }
[Parameter]
public string type { get; set; }
public PropHolder(PropertyInfo nfo, string propType)
{
info = nfo;
type = propType;
}
}
then i created a dictionary of this class and some cheking functions (this is inside of Clogic)
[Parameter]
public Dictionary<int, PropHolder> Props{ get; set; }
public void GetAllProps()
{
Props = new Dictionary<int, PropHolder>();
//nfo = SCustomer.GetType().GetProperties();
int Cid = 0;
foreach (PropertyInfo pif in SCustomer.GetType().GetProperties())
{
Props[Cid] = new PropHolder(pif, pif.PropertyType.Name);
Cid++;
}
}
public string CheckName(PropHolder propertyInfo)
{
if (propertyInfo.GetType() == typeof(PropHolder))
{
return propertyInfo.type;
}
else
{
return propertyInfo.GetType().Name.ToString();
}
}
public PropertyInfo getInfo(PropHolder propertyInfo)
{
if (propertyInfo.GetType() == typeof(PropHolder))
{
return propertyInfo.info;
}
else
{
return null;
}
}
and finally im able to loop over the keys of my dictionary and bind all values correctly (got lots of help figuring this out from the answere given by: "agua from mars")
here is the updateC.razor content:
#if (Props != null)
{
#foreach (int key in Props.Keys)
{
var pinfo = Props[key];
#if (CheckName(pinfo) == "String")
{
<input type="text" class="form-control" value=#(getInfo(pinfo).GetValue(SCustomer)) #onchange="#((ChangeEventArgs __e) => getInfo(pinfo).SetValue(SCustomer, __e.Value.ToString()))" />
}
}
}
this gives me a input box for every prop that is of the type String, if additional types are to be handled it can now easily be added. This is probably not the best sulution but it do work. I will update the answere if any better working solutions are posted.
I solved this using generics. This lets each input type pass on a generic type which defines what type it wants to use.
<input #bind="Name.Value" />
<input #bind="Age.Value" />
<div><code>Name</code>: #Name.Value</div>
<div><code>Age</code>: #Age.Value</div>
#code {
private FormField<string> Name { get; set; } = new FormField<string>();
private FormField<int> Age { get; set; } = new FormField<int>();
public class Form
{
public ICollection<IFormField> Fields { get; set; }
}
public interface IFormField
{
public int ControlType { get; set; }
}
public class FormField<T> : IFormField
{
public int ControlType { get; set; }
public T Value { get; set; }
}
}
Here it is on BlazorFiddle: https://blazorfiddle.com/s/wen1g26q

Call method in component

I have this "Alert" component:
#if (Show)
{
<div class="alert #Class" role="alert">
#Text
</div>
}
#functions
{
[Parameter]
private bool Show { get; set; } = false;
[Parameter]
private string Text { get; set; } = String.Empty;
[Parameter]
private string Class { get; set; } = String.Empty; //Success, Warning etc.
}
However, when I call this component on my page I still need to create atleast two variables - ShowError and ErrorText - to handle the state of this alert still clutters my code a lot as this alert exists on practically all pages.
My question is: Is it possible to de-clutter the code by calling a ShowMessage method in the child component?
An example would be something like this:
Page
#page "/my-page"
#inject HttpClient Http
<!-- A lot of HTML code here -->
<Alert/>
<!-- A lot of HTML code here -->
#functions {
protected override async Task OnInitAsync()
{
var response = await Http.PostJsonAsync<Response>("/api/sessions/create", null);
if (response.StatusCode == HttpStatusCode.OK)
{
}
else
{
myAlertComponent.ShowSuccessMessage(response.Message);
}
}
}
"Alert" component
#if (Show)
{
<div class="alert #Class" role="alert">
#Text
</div>
}
#functions
{
[Parameter]
private bool Show { get; set; } = false;
[Parameter]
private string Text { get; set; } = String.Empty;
[Parameter]
private string Class { get; set; } = String.Empty; //Success, Warning, Danger
public void HideAlerts()
{
Show = false;
}
public void ShowSuccessMessage(string message)
{
Show = true;
Text = message;
Class = "success":
}
public void ShowErrorMessage(string message)
{
Show = true;
Text = message;
Class = "danger":
}
}
To call component methods try adding a reference to the component with #ref and then adding the component declaration in the #code block. If the methods are public in the component you can use them outside of the component's scope.
Parent.razor
<Alert #ref="Alert" Text="I am showing" /> #*Notice the #ref tag;*#
<button #onclick="() => ShowAlert(true)">Show Success</button>
<button #onclick="() => ShowAlert(false)">Show Failure</button>
<button #onclick="HideAlert">Hide</button>
#code {
private Alert Alert { get; set; } // This is where the #ref is bound; no need to initialize, the markup does that for you.
private void ShowAlert(bool success)
{
if (success) Alert.ShowSuccessMessage("Success!!");
else Alert.ShowErrorMessage("Failed :(");
}
private void HideAlert() => Alert.Hide();
}
Alert.razor
#if (_show)
{
<div class="alert alert-#_class" role="alert">
<strong>#Text</strong>
</div>
}
#code
{
[Parameter] public string Text { get; set; } = string.Empty; // set as "I am showing", but will be updated if a message is passed to the show methods below
private bool _show { get; set; }
private string _class { get; set; } = string.Empty;
public void Hide() { _show = false; StateHasChanged(); }
public void ShowSuccessMessage(string message? = null)
{
_show = true;
Text = message ?? Text ?? string.Empty;
_class = "success";
StateHasChanged(); // StateHasChanged() informs the parent component that the child has updated and to update the dom
}
public void ShowErrorMessage(string? message = null)
{
_show = true;
Text = message ?? Text ?? string.Empty;
_class = "danger";
StateHasChanged();
}
}
See Section: Capture references to components

Categories