From 540e6c306d022d3231159613d9ec25fc73735089 Mon Sep 17 00:00:00 2001 From: Sukhrob Ilyosbekov Date: Mon, 22 Jul 2024 23:48:48 -0400 Subject: [PATCH] added generated JSON textbox, implemented async serialization --- src/FormBuilder.DesignerApp/Program.cs | 2 - src/FormBuilder/Components/FormBuilder.razor | 11 ++-- .../Components/FormBuilder.razor.cs | 42 ++++++++++----- .../Components/PropertyEditor.razor.cs | 9 +--- .../NotificationServiceExtensions.cs | 5 ++ src/FormBuilder/FormBuilder.csproj | 7 +++ src/FormBuilder/Models/DateField.cs | 2 +- src/FormBuilder/Services/DragDropService.cs | 2 +- src/FormBuilder/Services/FormService.cs | 53 ++++++++++++++++--- src/FormBuilder/Utils/FieldJsonConverter.cs | 10 ++-- src/FormBuilder/wwwroot/fieldScheme.json | 53 +++++++++++++++++++ 11 files changed, 156 insertions(+), 40 deletions(-) create mode 100644 src/FormBuilder/wwwroot/fieldScheme.json diff --git a/src/FormBuilder.DesignerApp/Program.cs b/src/FormBuilder.DesignerApp/Program.cs index 318bee5..7d2f2d4 100644 --- a/src/FormBuilder.DesignerApp/Program.cs +++ b/src/FormBuilder.DesignerApp/Program.cs @@ -7,8 +7,6 @@ builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); -// builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - var formBuilderOptions = new FormBuilderOptions { FormApiUrl = builder.Configuration["FormBuilderOptions:FormApiUrl"] diff --git a/src/FormBuilder/Components/FormBuilder.razor b/src/FormBuilder/Components/FormBuilder.razor index 7f8dd60..a9046ba 100644 --- a/src/FormBuilder/Components/FormBuilder.razor +++ b/src/FormBuilder/Components/FormBuilder.razor @@ -14,8 +14,8 @@ - - + + @foreach (var field in _formDefinition.Fields) { @@ -40,11 +40,15 @@ + + + + - + Fields @@ -61,6 +65,7 @@ +
Properties diff --git a/src/FormBuilder/Components/FormBuilder.razor.cs b/src/FormBuilder/Components/FormBuilder.razor.cs index 373c52a..3a9775f 100644 --- a/src/FormBuilder/Components/FormBuilder.razor.cs +++ b/src/FormBuilder/Components/FormBuilder.razor.cs @@ -14,6 +14,7 @@ public partial class FormBuilder : ComponentBase { private FormDefinition _formDefinition = new(); private Field? _selectedField; + private string _formDesignJson = "{}"; private string? _formId; private bool _isLoading; @@ -29,20 +30,34 @@ public partial class FormBuilder : ComponentBase private DialogService DialogService { get; set; } = default!; #endregion + + protected override async Task OnInitializedAsync() + { + await UpdateFormDesignJsonAsync(); + } - - private void AddField(FieldType fieldType) + /// + /// Updates the form design JSON string when the form definition changes. + /// + private async Task UpdateFormDesignJsonAsync() + { + _formDesignJson = await FormService.SerializeFormDesignAsync(_formDefinition, true); + } + + private Task AddField(FieldType fieldType) { var field = FieldFactory.CreateField(fieldType); field.Label = fieldType.ToString(); _formDefinition.Fields.Add(field); SelectField(field); + return UpdateFormDesignJsonAsync(); } - private void RemoveField(Field field) + private Task RemoveField(Field field) { _formDefinition.Fields.Remove(field); _selectedField = null; + return UpdateFormDesignJsonAsync(); } private void SelectField(Field field) @@ -56,7 +71,7 @@ private void SelectField(Field field) /// Creates a new field with the new type and copies the properties from the old field. /// /// Event parameters - private void HandleFieldTypeChanged(FieldTypeChangedArgs args) + private Task HandleFieldTypeChanged(FieldTypeChangedArgs args) { var field = args.Field; var newField = FieldFactory.CreateField(args.NewType); @@ -69,23 +84,26 @@ private void HandleFieldTypeChanged(FieldTypeChangedArgs args) var index = _formDefinition.Fields.IndexOf(field); _formDefinition.Fields[index] = newField; // Replace the old field with the new one, old one will be deleted by GC SelectField(newField); - } - - private void HandleDropField(Action addFieldCallback) - { - addFieldCallback(); + return UpdateFormDesignJsonAsync(); } - private void SwapFields(Field targetField, Field droppedField) + private Task SwapFields(Field targetField, Field droppedField) { var targetIndex = _formDefinition.Fields.IndexOf(targetField); var droppedIndex = _formDefinition.Fields.IndexOf(droppedField); var fields = _formDefinition.Fields; (fields[targetIndex], fields[droppedIndex]) = (fields[droppedIndex], fields[targetIndex]); + return UpdateFormDesignJsonAsync(); } private async Task SaveFormAsync() { + if (_formDefinition.Fields.Count == 0) + { + NotificationService.NotifyWarning("Cannot save an empty form"); + return; + } + _isLoading = true; Result result; @@ -120,10 +138,10 @@ private Task OpenLoadFormDialogAsync() }); } - private void LoadForm(FormCreatedEventArgs args) + private Task LoadForm(FormCreatedEventArgs args) { _formId = args.FormId; _formDefinition = args.FormDefinition; - StateHasChanged(); + return UpdateFormDesignJsonAsync(); } } diff --git a/src/FormBuilder/Components/PropertyEditor.razor.cs b/src/FormBuilder/Components/PropertyEditor.razor.cs index 6f4e79c..01d7adb 100644 --- a/src/FormBuilder/Components/PropertyEditor.razor.cs +++ b/src/FormBuilder/Components/PropertyEditor.razor.cs @@ -30,8 +30,7 @@ public partial class PropertyEditor : ComponentBase #region Binding Properties - - private string? _label; + private string? Label { get => SelectedField?.Label; @@ -47,7 +46,6 @@ private string? Label } } - private string? _placeholder; private string? Placeholder { get => SelectedField?.Placeholder; @@ -63,7 +61,6 @@ private string? Placeholder } } - private FieldType _inputType; private FieldType InputType { get => SelectedField?.Type ?? FieldType.Text; @@ -80,7 +77,6 @@ private FieldType InputType } } - private bool _required; private bool Required { get => SelectedField?.Required ?? false; @@ -96,7 +92,6 @@ private bool Required } } - private bool _readOnly; private bool ReadOnly { get => SelectedField?.ReadOnly ?? false; @@ -112,7 +107,6 @@ private bool ReadOnly } } - private bool _disabled; private bool Disabled { get => SelectedField?.Disabled ?? false; @@ -128,7 +122,6 @@ private bool Disabled } } - private int? _selectedListValue; private int? SelectedListValue { get => (SelectedField as SelectField)?.Value; diff --git a/src/FormBuilder/Extensions/NotificationServiceExtensions.cs b/src/FormBuilder/Extensions/NotificationServiceExtensions.cs index 8cc7873..6eb064f 100644 --- a/src/FormBuilder/Extensions/NotificationServiceExtensions.cs +++ b/src/FormBuilder/Extensions/NotificationServiceExtensions.cs @@ -11,4 +11,9 @@ public static void NotifyError(this NotificationService notificationService, str { notificationService.Notify(NotificationSeverity.Error, "Error", message); } + + public static void NotifyWarning(this NotificationService notificationService, string? message) + { + notificationService.Notify(NotificationSeverity.Warning, "Warning", message); + } } diff --git a/src/FormBuilder/FormBuilder.csproj b/src/FormBuilder/FormBuilder.csproj index 5baf859..e1e65a3 100644 --- a/src/FormBuilder/FormBuilder.csproj +++ b/src/FormBuilder/FormBuilder.csproj @@ -12,6 +12,7 @@ + @@ -20,4 +21,10 @@ + + + PreserveNewest + + + diff --git a/src/FormBuilder/Models/DateField.cs b/src/FormBuilder/Models/DateField.cs index 4d1e030..bb93d4f 100644 --- a/src/FormBuilder/Models/DateField.cs +++ b/src/FormBuilder/Models/DateField.cs @@ -4,7 +4,7 @@ /// Represents a date field. /// The value is of type DateTime. /// -public class DateField : Field +public class DateField : Field { public DateField() { diff --git a/src/FormBuilder/Services/DragDropService.cs b/src/FormBuilder/Services/DragDropService.cs index 059b17b..4294ae3 100644 --- a/src/FormBuilder/Services/DragDropService.cs +++ b/src/FormBuilder/Services/DragDropService.cs @@ -3,7 +3,7 @@ /// /// Service for drag and drop operations. /// -public class DragDropService +internal class DragDropService { /// /// The data that is being dragged. diff --git a/src/FormBuilder/Services/FormService.cs b/src/FormBuilder/Services/FormService.cs index 3df0aec..4d51d85 100644 --- a/src/FormBuilder/Services/FormService.cs +++ b/src/FormBuilder/Services/FormService.cs @@ -6,10 +6,11 @@ namespace FormBuilder.Services; -public class FormService +internal class FormService { private readonly HttpClient _httpClient; - private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly JsonSerializerOptions _jsonSerializerDefaultOptions; + private readonly JsonSerializerOptions _jsonSerializerIndentedOptions; public FormService(FormBuilderOptions options) { @@ -23,9 +24,17 @@ public FormService(FormBuilderOptions options) BaseAddress = new Uri(options.FormApiUrl) }; - _jsonSerializerOptions = new JsonSerializerOptions + _jsonSerializerDefaultOptions = new JsonSerializerOptions { - Converters = { new FieldJsonConverter() } + Converters = { new FieldJsonConverter() }, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + _jsonSerializerIndentedOptions = new JsonSerializerOptions + { + Converters = { new FieldJsonConverter() }, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true }; } @@ -42,15 +51,43 @@ public FormService(FormBuilderOptions options) { try { - return JsonSerializer.Deserialize(formDesign, _jsonSerializerOptions); + return JsonSerializer.Deserialize(formDesign, _jsonSerializerDefaultOptions); } catch (JsonException e) { - Console.WriteLine("Failed to deserialize form design. Error: {0}", e.Message); + Console.WriteLine("Failed to deserialize form design. Error: {0}", e); return null; } } + /// + /// Serializes the FormDefinition object into a JSON string. + /// + /// + /// FormDefinition object to serialize. + /// + /// True to indent the JSON string; otherwise, false. + /// Serialized JSON string of the FormDefinition object. + public string SerializeFormDesign(FormDefinition formDefinition, bool indented = false) + { + return JsonSerializer.Serialize(formDefinition, indented ? _jsonSerializerIndentedOptions : _jsonSerializerDefaultOptions); + } + + /// + /// Asynchronously serializes the FormDefinition object into a JSON string. + /// + /// FormDefinition object to serialize. + /// True to indent the JSON string; otherwise, false. + /// Serialized JSON string of the FormDefinition object. + public async Task SerializeFormDesignAsync(FormDefinition formDefinition, bool indented = false) + { + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, formDefinition, indented ? _jsonSerializerIndentedOptions : _jsonSerializerDefaultOptions); + ms.Position = 0; + using var reader = new StreamReader(ms); + return await reader.ReadToEndAsync(); + } + public async Task> GetFormByIdAsync(string id) { var response = await _httpClient.GetAsync($"/api/forms/{id}"); @@ -59,7 +96,7 @@ public async Task> GetFormByIdAsync(string id) public async Task> CreateFormAsync(FormDefinition formDefinition) { - var formDesign = JsonSerializer.Serialize(formDefinition); + var formDesign = await SerializeFormDesignAsync(formDefinition); var createFormDto = new CreateFormDto { FormName = formDefinition.Name, @@ -72,7 +109,7 @@ public async Task> CreateFormAsync(FormDefinition formDefinition public async Task UpdateFormAsync(string id, FormDefinition formDefinition) { - var formDesign = JsonSerializer.Serialize(formDefinition); + var formDesign = await SerializeFormDesignAsync(formDefinition); var updateFormDto = new CreateFormDto { FormName = formDefinition.Name, diff --git a/src/FormBuilder/Utils/FieldJsonConverter.cs b/src/FormBuilder/Utils/FieldJsonConverter.cs index 612d1c6..a5d46d0 100644 --- a/src/FormBuilder/Utils/FieldJsonConverter.cs +++ b/src/FormBuilder/Utils/FieldJsonConverter.cs @@ -4,21 +4,21 @@ namespace FormBuilder.Utils; -public class FieldJsonConverter : JsonConverter +internal class FieldJsonConverter : JsonConverter { public override Field Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var jsonDoc = JsonDocument.ParseValue(ref reader); var rootElement = jsonDoc.RootElement; - if (!rootElement.TryGetProperty("Type", out var fieldTypeProperty)) + if (!rootElement.TryGetProperty("type", out var fieldTypeProperty)) { - throw new JsonException("Field type is missing"); + throw new JsonException("The property 'type' is missing"); } if (!fieldTypeProperty.TryGetInt32(out var fieldType)) { - throw new JsonException("Field type is not an integer"); + throw new JsonException("The value of the property 'type' is not an integer"); } var enumFieldType = Enum.Parse(fieldType.ToString()); @@ -35,7 +35,7 @@ public override Field Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSe if (field is null) { - throw new JsonException($"Failed to deserialize field of type {enumFieldType}"); + throw new JsonException($"Failed to deserialize field of type '{enumFieldType}'"); } return field; diff --git a/src/FormBuilder/wwwroot/fieldScheme.json b/src/FormBuilder/wwwroot/fieldScheme.json new file mode 100644 index 0000000..7cf9ae6 --- /dev/null +++ b/src/FormBuilder/wwwroot/fieldScheme.json @@ -0,0 +1,53 @@ +{ + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "number" + }, + "label": { + "type": "string" + }, + "placeholder": { + "type": "string" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "disabled": { + "type": "boolean" + }, + "value": {}, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "value": {} + }, + "required": ["text", "value"] + } + } + }, + "required": ["name", "type"] + } + } + }, + "required": ["name", "fields"] +}