SeemGen/Components/Partials/DynamicEditForm.razor

292 lines
12 KiB
Plaintext

@using System.Linq.Expressions
@using System.ComponentModel.DataAnnotations
@using System.Reflection
@using Microsoft.AspNetCore.Components.Rendering
@if (isEditing)
{
<EditForm Model="@Data"
OnValidSubmit="@HandleValidSubmit"
OnInvalidSubmit="@HandleInvalidSubmit"
Context="EditFormContext">
<DataAnnotationsValidator />
<div class="card">
<div class="card-header text-center py-3">
<h4>@TitleString</h4>
</div>
<div class="card-body">
@CreateEditFormFields()
</div>
</div>
</EditForm>
}
else
{
<div class="card cw-480">
<div class="card-header text-center py-3">
<h4>Details</h4>
@* <DxButton Click="StartEditing" RenderStyle="ButtonRenderStyle.Primary">Create new</DxButton> *@
</div>
<div class="card-body">
@CreateCardView()
</div>
</div>
}
<p class="tm-8 cw-480 mt-2">
@FormSubmitResult
</p>
@code {
[Parameter] public object? Data { get; set; }
[Parameter] public List<string> IgnoreReflection { get; set; } =new();
[Parameter] public EventCallback<object> OnSubmit { get; set; }
[Parameter] public bool isEditing { get; set; } = false;
[Parameter] public string TitleString { get; set; } = "Edit your details";
[Parameter] public string ButtonTextString { get; set; } = "Submit";
string _formSubmitResult = "";
private string _spinnerClass = "";
string FormSubmitResult = "";
string PhoneMask { get; set; } = @"((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?";
string EmailMask { get; set; } = @"\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\W*\d(\W*\d){1,14}";
protected override void OnInitialized()
{
base.OnInitialized();
}
protected override void OnParametersSet()
{
StateHasChanged();
base.OnParametersSet();
}
async Task HandleValidSubmit()
{
//_spinnerClass = "spinner-border spinner-border-sm";
//await Task.Delay(500);
var debugString = "Success: ";
var myType = Data.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
foreach (var prop in props)
{
var propValue = prop.GetValue(Data, null);
// Do something with propValue
debugString += $"{prop.Name} = {propValue}\n";
}
_formSubmitResult = debugString;
_spinnerClass = "";
await OnSubmit.InvokeAsync(Data);
isEditing = false;
}
void HandleInvalidSubmit()
{
FormSubmitResult = "Please correct all errors";
}
void StartEditing()
{
isEditing = true;
}
public RenderFragment CreateCardView() => cardViewBuilder =>
{
var mytype = Data.GetType();
var propertyList = mytype.GetProperties();
foreach (var property in propertyList)
{
if (IgnoreReflection.Contains(property.Name))
{
continue;
}
var displayLabel = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault();
cardViewBuilder.OpenElement(0, "p");
cardViewBuilder.AddContent(1, $"{displayLabel?.Name ?? property.Name}: {property.GetValue(Data)}");
cardViewBuilder.CloseElement();
}
};
public RenderFragment CreateEditFormFields() => formLayoutBuilder =>
{
var modelType = Data.GetType();
var properties = modelType.GetProperties();
formLayoutBuilder.OpenComponent<EditForm>(0);
formLayoutBuilder.AddAttribute(1, "Model", Data);
formLayoutBuilder.AddAttribute(2, "ChildContent", (RenderFragment<EditContext>)((editContext) => (builder) =>
{
int seq = 0;
foreach (var property in properties)
{
if (IgnoreReflection.Contains(property.Name))
continue;
var dataTypeAttr = (DataTypeAttribute)property.GetCustomAttributes(typeof(DataTypeAttribute), false).FirstOrDefault();
var displayAttr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault();
var displayName = displayAttr?.Name ?? property.Name;
var propertyAccess = Expression.Property(Expression.Constant(Data), property.Name);
var lambda = Expression.Lambda(
typeof(Func<>).MakeGenericType(property.PropertyType),
propertyAccess
);
// Label
builder.OpenElement(seq++, "div");
builder.AddContent(seq++, displayName);
// Input control
builder.OpenElement(seq++, "div");
if (property.PropertyType == typeof(string))
{
if (dataTypeAttr?.DataType == DataType.MultilineText)
{
builder.OpenComponent<InputTextArea>(seq++);
builder.AddAttribute(seq++, "Value", (string?)property.GetValue(Data));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<string>(this, val => property.SetValue(Data, val)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
}
else
{
builder.OpenComponent<InputText>(seq++);
builder.AddAttribute(seq++, "Value", (string?)property.GetValue(Data));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<string>(this, val => property.SetValue(Data, val)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
}
}
else if (property.PropertyType == typeof(int))
{
builder.OpenComponent<InputNumber<int>>(seq++);
builder.AddAttribute(seq++, "Value", (int?)property.GetValue(Data));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int>(this, val => property.SetValue(Data, val)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
}
else if (property.PropertyType == typeof(double))
{
builder.OpenComponent<InputNumber<double>>(seq++);
builder.AddAttribute(seq++, "Value", (double?)property.GetValue(Data));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double>(this, val => property.SetValue(Data, val)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
}
else if (property.PropertyType == typeof(DateOnly))
{
builder.OpenComponent<InputDate<DateOnly>>(seq++);
builder.AddAttribute(seq++, "Value", (DateOnly?)property.GetValue(Data));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<DateOnly>(this, val => property.SetValue(Data, val)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
}
builder.CloseElement(); // end of input wrapper
// Validation message
builder.OpenComponent(seq++, typeof(ValidationMessage<>).MakeGenericType(property.PropertyType));
builder.AddAttribute(seq++, "For", lambda);
builder.CloseComponent();
builder.CloseElement(); // end of outer div
}
// Submit button
builder.OpenElement(seq++, "button");
builder.AddAttribute(seq++, "type", "submit");
builder.AddContent(seq++, ButtonTextString ?? "Submit");
builder.CloseElement();
}));
formLayoutBuilder.CloseComponent();
};
private void RenderInputComponent(RenderTreeBuilder builder, ref int seq, PropertyInfo prop, DataTypeAttribute? typeAttr, object? value, LambdaExpression lambda)
{
var type = typeAttr?.DataType ?? DataType.Text;
switch (type)
{
case DataType.Password:
builder.OpenComponent<InputText>(seq++);
builder.AddAttribute(seq++, "type", "password");
builder.AddAttribute(seq++, "Value", value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<string>(this, v => prop.SetValue(Data, v)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
break;
case DataType.PhoneNumber:
builder.OpenComponent<InputText>(seq++);
builder.AddAttribute(seq++, "Value", value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<string>(this, v => prop.SetValue(Data, v)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.AddAttribute(seq++, "Placeholder", "+11234567890");
builder.CloseComponent();
break;
case DataType.Date:
builder.OpenComponent<InputDate<DateOnly>>(seq++);
builder.AddAttribute(seq++, "Value", value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<DateOnly>(this, v => prop.SetValue(Data, v)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
break;
case DataType.MultilineText:
builder.OpenComponent<InputTextArea>(seq++);
builder.AddAttribute(seq++, "Value", value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<string>(this, v => prop.SetValue(Data, v)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
break;
case DataType.Custom:
if (prop.PropertyType == typeof(double))
{
builder.OpenComponent<InputNumber<double>>(seq++);
builder.AddAttribute(seq++, "Value", value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double>(this, v => prop.SetValue(Data, v)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
}
else if (prop.PropertyType == typeof(int))
{
builder.OpenComponent<InputNumber<int>>(seq++);
builder.AddAttribute(seq++, "Value", value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int>(this, v => prop.SetValue(Data, v)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
}
break;
default:
builder.OpenComponent<InputText>(seq++);
builder.AddAttribute(seq++, "Value", value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<string>(this, v => prop.SetValue(Data, v)));
builder.AddAttribute(seq++, "ValueExpression", lambda);
builder.CloseComponent();
break;
}
}
}