using DevExpress.Blazor; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Web; using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; using TIAMWebApp.Shared.Application.Utility; using Microsoft.Extensions.Localization; using TIAM.Resources; using TIAMWebApp.Shared.Application.Interfaces; using System.Reflection; using TIAMWebApp.Shared.Application.Models; using TIAMSharedUI.Shared; using System.Text.RegularExpressions; using AyCode.Core.Consts; using TIAM.Core.Loggers; using AyCode.Services.Loggers; namespace TIAMSharedUI.Pages.Components { public partial class InputWizard : ComponentBase { [Inject] public required IEnumerable LogWriters { get; set; } [Inject] IStringLocalizer localizer { get; set; } [Inject] public ISessionService sessionService { get; set; } public Dictionary FormSteps { get; set; } = new Dictionary(); public int CurrentStep { get; set; } = 0; //TestUserData Data { get; set; } = new TestUserData(); [Parameter] public string TitleResourceString { get; set; } = "Wizard"; [Parameter] public string SubtitleResourceString { get; set; } = "Let's fill in this form"; [Parameter] public object Data { get; set; } = new object(); [Parameter] public EventCallback OnSubmit { get; set; } [Parameter] public string SubmitButtonText { get; set; } = "Submit"; [Required] [Parameter] public List IgnoreReflection { get; set; } [Parameter] public string CssClass { get; set; } = ""; private LoggerClient _logger; string _formSubmitResult = ""; private string _spinnerClass = ""; protected override async Task OnInitializedAsync() { _logger = new LoggerClient(LogWriters.ToArray()); } async Task HandleValidSubmit() { _spinnerClass = "spinner-border spinner-border-sm"; await Task.Delay(500); var debugString = "Success: "; var myType = Data.GetType(); IList props = new List(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); } void HandleInvalidSubmit() { _formSubmitResult = "Please correct all errors"; } public void OnNext(MouseEventArgs args) { _logger.Info("OnNext called"); CurrentStep++; } public void OnPrevious(MouseEventArgs args) { _logger.Info("OnPrev called"); CurrentStep--; } public RenderFragment CreateEditFormFields() => formLayoutBuilder => { var type = Data.GetType(); //for (var i = 0; i < 1000; i++) _logger.Info("Hellooooo " + type.AssemblyQualifiedName); var propertyList = type.GetProperties(); //var length = propertyList.Length - IgnoreReflection.Count; //var propertyList = typeof(TestUserData).GetProperties(); formLayoutBuilder.OpenComponent(0); formLayoutBuilder.AddAttribute(1, "ChildContent", (RenderFragment)((layoutItemBuilder) => { var i = 0; var k = 0; foreach (var property in propertyList) { var isActive = k == CurrentStep; //if (property.Name == "Id" || property.Name == "Latitude" || property.Name == "Longitude" || property.Name == "Created" || property.Name == "Modified") //if (property.Name == "Id" || property.Name == "Created" || property.Name == "Modified") if (IgnoreReflection.Contains(property.Name)) { continue; } Guid stepId; if (!FormSteps.ContainsKey(k)) { stepId = Guid.NewGuid(); _logger.DetailConditional($"Adding step {k}, {stepId}, for {property.Name}"); FormSteps.Add(k, stepId); } else { stepId = FormSteps[k]; } //the following line creates an expression that accesses the property value by name var access = Expression.Property(Expression.Constant(Data), property.Name); //the following line creates a lambda expression that returns the value of the property var lambda = Expression.Lambda(typeof(Func<>).MakeGenericType(property.PropertyType), access); _logger.DetailConditional($"{property.Name}, {property.GetType().FullName}; lambda: {lambda.ToString()}"); layoutItemBuilder.OpenElement(i++, "div");//open div layoutItemBuilder.AddAttribute(i++, "id", stepId.ToString()); layoutItemBuilder.AddAttribute(i++, "class", "disply-flex align-items-center "+CssClass); layoutItemBuilder.AddAttribute(i++, "style", "width: 100%;"); if (k != CurrentStep) { layoutItemBuilder.AddAttribute(i++, "hidden", "true"); } else { //this input should be focused, so we set a flag _logger.DetailConditional($"Setting focus to {property.Name}"); } var attrList = (DataTypeAttribute)property.GetCustomAttributes(typeof(DataTypeAttribute), false).First(); var displayLabel = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayAttribute), false).First(); layoutItemBuilder.OpenComponent(i++); //open dxformlayoutitem layoutItemBuilder.AddAttribute(i++, "Caption", localizer.GetString(displayLabel.Name)); layoutItemBuilder.AddAttribute(i++, "ColSpanMd", 12); layoutItemBuilder.AddAttribute(i++, "CaptionPosition", CaptionPosition.Vertical); layoutItemBuilder.AddAttribute(i++, "CssClass", "justify-content-center"); layoutItemBuilder.AddAttribute(i++, "Template", (RenderFragment)((context) => ((editor) => { var j = 0; switch (attrList.DataType) { case DataType.Text: { editor.OpenComponent(j++); _logger.DetailConditional($"{property.Name}, {property.PropertyType}"); editor.AddAttribute(j++, "Text", property.GetValue(Data)); editor.AddAttribute(j++, "TextExpression", lambda); editor.AddAttribute(j++, "CssClass", "form-field"); if (isActive) { editor.AddAttribute(j++, "Id", "ActiveInput"); } editor.AddAttribute(j++, "TextChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); editor.CloseComponent(); break; } case DataType.Password: { editor.OpenComponent(j++); editor.AddAttribute(j++, "Password", true); editor.AddAttribute(j++, "NullText", "Password"); editor.AddAttribute(j++, "Text", property.GetValue(Data)); editor.AddAttribute(j++, "TextExpression", lambda); editor.AddAttribute(j++, "TextChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); editor.CloseComponent(); break; } case DataType.PhoneNumber: { editor.OpenComponent>(j++); editor.AddAttribute(j++, "Value", property.GetValue(Data)); editor.AddAttribute(j++, "Mask", AcRegExpression.PhoneNumberMask); editor.AddAttribute(j++, "MaskMode", MaskMode.RegEx); editor.AddAttribute(j++, "BindValueMode", BindValueMode.OnInput); editor.AddAttribute(j++, "NullText", "+11234567890"); editor.AddAttribute(j++, "MaskAutoCompleteMode", MaskAutoCompleteMode.None); editor.AddAttribute(j++, "ValueExpression", lambda); //editor.AddAttribute(j++, "Placeholder", "#"); //editor.AddAttribute(j++, "PlaceholdersVisible", false); editor.AddAttribute(j++, "ValueChanged", EventCallback.Factory.Create(this, str => SetPhoneNumber(property, Data, str))); //editor.AddAttribute(j++, "TextChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); /*editor.AddAttribute(i++, "Template", (RenderFragment)((context) => ((regexProperties) => { var l = 0; regexProperties.OpenComponent(l++); regexProperties.AddAttribute(l++, "Placeholder", "#"); regexProperties.AddAttribute(l++, "PlaceholdersVisible", false); regexProperties.CloseComponent(); })));*/ editor.CloseComponent(); break; } case DataType.EmailAddress: { editor.OpenComponent>(j++); editor.AddAttribute(j++, "Value", property.GetValue(Data)); editor.AddAttribute(j++, "MaskMode", MaskMode.RegEx); editor.AddAttribute(j++, "Mask", AcRegExpression.EmailMask); editor.AddAttribute(j++, "BindValueMode", BindValueMode.OnInput); //MaskAutoCompleteMode="@((MaskAutoCompleteMode)AutoCompleteMode)" editor.AddAttribute(j++, "MaskAutoCompleteMode", MaskAutoCompleteMode.None); editor.AddAttribute(j++, "NullText", "example@example.com"); editor.AddAttribute(j++, "ValueExpression", lambda); editor.AddAttribute(j++, "Placeholder", "#"); editor.AddAttribute(j++, "PlaceholdersVisible", false); editor.AddAttribute(j++, "ValueChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); //editor.AddAttribute(j++, "Template", (RenderFragment)((context) => ((editor2) => //{ // var l = 0; // editor2.OpenComponent(l++); // editor2.AddAttribute(l++, "Placeholder", "#"); // editor2.AddAttribute(l++, "PlaceholdersVisible", "false"); // editor2.CloseComponent(); //}))); editor.CloseComponent(); break; } case DataType.Date: { editor.OpenComponent>(j); editor.AddAttribute(j++, "Date", property.GetValue(Data)); editor.AddAttribute(j++, "DateExpression", lambda); editor.AddAttribute(j++, "TimeSectionVisible", true); editor.AddAttribute(j++, "TimeSectionScrollPickerFormat", "tt h m"); editor.AddAttribute(j++, "CssClass", "form-field"); editor.AddAttribute(j++, "DateChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); editor.CloseComponent(); break; } case DataType.Custom: { if (property.PropertyType == typeof(double)) { editor.OpenComponent>(j); editor.AddAttribute(j++, "Value", property.GetValue(Data)); editor.AddAttribute(j++, "Mask", "n6"); editor.AddAttribute(j++, "BindValueMode", BindValueMode.OnInput); editor.AddAttribute(j++, "ValueExpression", lambda); editor.AddAttribute(j++, "CssClass", "form-field"); editor.AddAttribute(j++, "ValueChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); editor.CloseComponent(); break; } else if (property.PropertyType == typeof(int)) { editor.OpenComponent>(j); editor.AddAttribute(j++, "Value", property.GetValue(Data)); editor.AddAttribute(j++, "BindValueMode", BindValueMode.OnInput); editor.AddAttribute(j++, "Mask", NumericMask.WholeNumber); editor.AddAttribute(j++, "ValueExpression", lambda); editor.AddAttribute(j++, "CssClass", "form-field"); editor.AddAttribute(j++, "ValueChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); editor.CloseComponent(); break; } else if (property.PropertyType == typeof(IEnumerable) && property.Name == "Occupation") { editor.OpenComponent>(j); editor.AddAttribute(j++, "Data", AdditionalData.Occupations); editor.AddAttribute(j++, "Value", property.GetValue(Data)); editor.AddAttribute(j++, "ValueExpression", lambda); editor.AddAttribute(j++, "ValueChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); editor.CloseComponent(); break; } else if (property.PropertyType == typeof(DriverModel) && property.Name == "Driver") { //load possible drivers for this serviceprovider //DriverModel driver = (DriverModel)property.GetValue(Data); IEnumerable drivers = new DriverModel[] { new DriverModel(Guid.NewGuid(), Guid.NewGuid(), "John Doe"), new DriverModel(Guid.NewGuid(), Guid.NewGuid(), "Jane Doe"), new DriverModel(Guid.NewGuid(), Guid.NewGuid(), "James Doe") }; var defaultDriver = drivers.FirstOrDefault(); //editor.OpenElement(j++, "p"); //editor.AddContent(j++, localizer.GetString("Driver")); //editor.CloseElement(); editor.OpenComponent>(j); editor.AddAttribute(j++, "Data", drivers); editor.AddAttribute(j++, "TextFieldName", nameof(defaultDriver.Name)); editor.AddAttribute(j++, "Value", drivers.FirstOrDefault()); //editor.AddAttribute(j++, "Text", defaultDriver); editor.AddAttribute(j++, "CssClass", "form-field"); //editor.AddAttribute(j++, "TextExpression", lambda); editor.AddAttribute(j++, "ValueExpression", lambda); editor.AddAttribute(j++, "ValueChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); editor.CloseComponent(); break; } //string.Compare(metadata.CustomDataType, "BoldRed", true) == 0 //else if (property.PropertyType == typeof(string) && property.Name == "Destination") else if (property.PropertyType == typeof(string) && string.Compare(attrList.CustomDataType, "TransferDestination", true) == 0) { editor.OpenComponent(j); editor.AddAttribute(j++, "OwlId", "owlSelector" + stepId); editor.AddAttribute(j++, "TextValue", property.GetValue(Data)); //editor.AddAttribute(j++, "TExpression", lambda); editor.AddAttribute(j++, "OnSliderChanged", EventCallback.Factory.Create(this, result => { _logger.DetailConditional($"Slider changed to {result}"); property.SetValue(Data, result); _logger.DetailConditional($"bleh: {property.Name} = {property.GetValue(Data)}"); //StateHasChanged(); // Add this line to refresh the UI })); editor.CloseComponent(); editor.OpenComponent(j++); /*editor.AddAttribute(j++, "CssClass", "form-field");*/ editor.AddAttribute(j++, "NullText", "Slide or type"); editor.AddAttribute(j++, "Enabled", false); editor.AddAttribute(j++, "Text", property.GetValue(Data)); editor.AddAttribute(j++, "TextExpression", lambda); editor.AddAttribute(j++, "TextChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); _logger.DetailConditional($"bleh: {property.Name} = {property.GetValue(Data)}"); })); editor.CloseComponent(); } else if (property.PropertyType == typeof(string) && string.Compare(attrList.CustomDataType, "FullName", true) == 0) { editor.OpenComponent(j); editor.AddAttribute(j++, "NullText", "Please tell us your name."); editor.AddAttribute(j++, "FirstNameChanged", EventCallback.Factory.Create(this, result => { _logger.DetailConditional($"FirstName changed to {result}"); //find property with name FirstName var firstNameProperty = propertyList.FirstOrDefault(p => p.Name == "FirstName"); firstNameProperty.SetValue(Data, result); //find property with name LastName var lastNameProperty = propertyList.FirstOrDefault(p => p.Name == "LastName"); //combine the two values, if they are not null if (firstNameProperty != null && lastNameProperty != null) { var firstName = result; var lastName = (string)lastNameProperty.GetValue(Data); var fullName = $"{firstName} {lastName}"; property.SetValue(Data, fullName); } })); editor.AddAttribute(j++, "LastNameChanged", EventCallback.Factory.Create(this, result => { _logger.DetailConditional($"LastName changed to {result}"); //find property with name FirstName var firstNameProperty = propertyList.FirstOrDefault(p => p.Name == "FirstName"); //find property with name LastName var lastNameProperty = propertyList.FirstOrDefault(p => p.Name == "LastName"); lastNameProperty.SetValue(Data, result); //combine the two values, if they are not null if (firstNameProperty != null && lastNameProperty != null) { var firstName = (string)firstNameProperty.GetValue(Data); var lastName = result; var fullName = $"{firstName} {lastName}"; property.SetValue(Data, fullName); } _logger.DetailConditional($"bleh: {property.Name} = {property.GetValue(Data)}"); StateHasChanged(); // Add this line to refresh the UI })); editor.CloseComponent(); editor.OpenComponent(j++); /*editor.AddAttribute(j++, "CssClass", "form-field");*/ editor.AddAttribute(j++, "NullText", "Please type in the above fields"); editor.AddAttribute(j++, "Enabled", false); editor.AddAttribute(j++, "Text", property.GetValue(Data)); editor.AddAttribute(j++, "TextExpression", lambda); editor.AddAttribute(j++, "TextChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); _logger.DetailConditional($"bleh: {property.Name} = {property.GetValue(Data)}"); })); editor.CloseComponent(); } break; } case DataType.MultilineText: { editor.OpenComponent(j); editor.AddAttribute(j++, "Text", property.GetValue(Data)); editor.AddAttribute(j++, "TextExpression", lambda); editor.AddAttribute(j++, "CssClass", "form-field"); editor.AddAttribute(j++, "TextChanged", EventCallback.Factory.Create(this, str => { property.SetValue(Data, str); })); editor.CloseComponent(); break; } default: break; } }))); layoutItemBuilder.CloseComponent(); //close dxformlayoutitem layoutItemBuilder.OpenComponent(i++); layoutItemBuilder.AddAttribute(i++, "Click", EventCallback.Factory.Create(this, OnPrevious)); layoutItemBuilder.AddAttribute(i++, "SubmitFormOnClick", false); layoutItemBuilder.AddAttribute(i++, "CssClass", "btn btn-secondary mt-3"); if (!(k > 0)) { layoutItemBuilder.AddAttribute(i++, "disabled", "true"); } layoutItemBuilder.AddAttribute(i++, "style", "margin-left: auto; width:49%"); layoutItemBuilder.AddAttribute(i++, "Text", localizer.GetString(ResourceKeys.ButtonPrevious)); layoutItemBuilder.CloseComponent(); layoutItemBuilder.OpenComponent(i++); layoutItemBuilder.AddAttribute(i++, "Click", EventCallback.Factory.Create(this, OnNext)); layoutItemBuilder.AddAttribute(i++, "SubmitFormOnClick", false); layoutItemBuilder.AddAttribute(i++, "CssClass", "btn btn-primary mt-3"); if (!(k < FormSteps.Count - 1)) { layoutItemBuilder.AddAttribute(i++, "disabled", "true"); } layoutItemBuilder.AddAttribute(i++, "style", "margin-left: auto; width:49%"); layoutItemBuilder.AddAttribute(i++, "Text", localizer.GetString(ResourceKeys.ButtonNext)); layoutItemBuilder.CloseComponent(); layoutItemBuilder.CloseElement(); //close div layoutItemBuilder.OpenElement(i++, "div"); layoutItemBuilder.AddAttribute(i++, "class", "text-danger"); layoutItemBuilder.OpenComponent(i++, typeof(ValidationMessage<>).MakeGenericType(property.PropertyType)); layoutItemBuilder.AddAttribute(i++, "For", lambda); layoutItemBuilder.CloseComponent(); layoutItemBuilder.CloseElement(); //_logger.DetailConditional($"loop {k}, length: {length}, formSteps: {FormSteps.Count} "); k++; } layoutItemBuilder.OpenComponent(i++); layoutItemBuilder.AddAttribute(i++, "ColSpanMd", 12); layoutItemBuilder.AddAttribute(i++, "CssClass", "full-width justify-content-center"); layoutItemBuilder.AddAttribute(i++, "Template", (RenderFragment)((context) => ((editor) => { _logger.DetailConditional($"Submit button {CurrentStep}, {FormSteps.Count}"); editor.OpenElement(i++, "button"); editor.AddAttribute(i++, "type", "submit"); editor.AddAttribute(i++, "class", "btn btn-primary mt-3 w-100"); editor.AddAttribute(i++, "style", "margin: 0 auto"); if (CurrentStep < FormSteps.Count - 1) { editor.AddAttribute(i++, "disabled", "true"); } //editor.AddAttribute(i++, "disabled", "true"); editor.OpenElement(i++, "span"); editor.AddAttribute(i++, "class", _spinnerClass); editor.CloseElement(); editor.AddContent(i++, localizer.GetString(SubmitButtonText)); editor.CloseElement(); /*editor.OpenComponent(i++); editor.AddAttribute(i++, "SubmitFormOnClick", true); editor.AddAttribute(i++, "Text", SubmitButtonText); editor.AddAttribute(i++, "IconClass", _spinnerClass); editor.AddAttribute(i++, "CssClass", "btn btn-primary mt-3"); if (CurrentStep == propertyList.Length-1) { editor.AddAttribute(i++, "Visible", true); } else { editor.AddAttribute(i++, "Visible", false); } editor.CloseComponent();*/ }))); layoutItemBuilder.CloseComponent(); })); formLayoutBuilder.CloseComponent(); }; private void SetPhoneNumber(PropertyInfo property, object data, string str) { _logger.Debug($"SetPhoneNumber called with {str}"); property.SetValue(data, str); } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] public class MinPasswordLengthAttribute : ValidationAttribute { private int MinLength { get; } public MinPasswordLengthAttribute(int minLength, string errorMsg) : base(errorMsg) { MinLength = minLength; } public override bool IsValid(object? value) { return value != null && ((string)value).Length >= MinLength; } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] public class EmailAttribute : ValidationAttribute { public override bool IsValid(object value) { return Regex.IsMatch((string)value, @"^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*" + "@" + @"((([\-\w]+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))$"); } } }