Skip to content

Commit

Permalink
added generated JSON textbox, implemented async serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
suxrobGM committed Jul 23, 2024
1 parent 7a49493 commit 540e6c3
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 40 deletions.
2 changes: 0 additions & 2 deletions src/FormBuilder.DesignerApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

// builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

var formBuilderOptions = new FormBuilderOptions
{
FormApiUrl = builder.Configuration["FormBuilderOptions:FormApiUrl"]
Expand Down
11 changes: 8 additions & 3 deletions src/FormBuilder/Components/FormBuilder.razor
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
<RadzenTextBox @bind-Value="_formDefinition.Name"/>
</RadzenFormField>

<DropZone TData="Action" Drop="(e) => HandleDropField(e)" Zone="FieldsZone">
<RadzenFieldset Text="Fields">
<DropZone TData="Func<Task>" Drop="@((func) => func())" Zone="FieldsZone">
<RadzenFieldset Text="Fields" Style="max-height: 600px; overflow-y: auto">
<RadzenStack Orientation="Orientation.Vertical" Gap="0.5rem">
@foreach (var field in _formDefinition.Fields)
{
Expand All @@ -40,11 +40,15 @@
</RadzenStack>
</RadzenFieldset>
</DropZone>

<RadzenFormField Text="Generated JSON">
<RadzenTextArea @bind-Value="_formDesignJson" Rows="20" ReadOnly="true"/>
</RadzenFormField>
</RadzenStack>
</RadzenColumn>

<RadzenColumn Class="border bg-light px-1" Size="12" SizeMD="3" SizeLG="2">
<RadzenStack Class="border-bottom mt-2 pb-2" Orientation="Orientation.Vertical">
<RadzenStack Class="pt-2 pb-2" Orientation="Orientation.Vertical">
<RadzenText TextStyle="TextStyle.H5" TextAlign="TextAlign.Center">Fields</RadzenText>
<Dragable Data="() => AddField(FieldType.Text)" Zone="FieldsZone">
<RadzenButton Class="w-100" Text="Text Field" Click="@(() => AddField(FieldType.Text))"/>
Expand All @@ -61,6 +65,7 @@
<Dragable Data="() => AddField(FieldType.Select)" Zone="FieldsZone">
<RadzenButton Class="w-100" Text="Select Field" Click="() => AddField(FieldType.Select)"/>
</Dragable>
<hr/>
</RadzenStack>
<RadzenStack Class="mt-3" Orientation="Orientation.Vertical" Gap="1rem">
<RadzenText TextStyle="TextStyle.H5" TextAlign="TextAlign.Center">Properties</RadzenText>
Expand Down
42 changes: 30 additions & 12 deletions src/FormBuilder/Components/FormBuilder.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
/// <summary>
/// Updates the form design JSON string when the form definition changes.
/// </summary>
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)
Expand All @@ -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.
/// </summary>
/// <param name="args">Event parameters</param>
private void HandleFieldTypeChanged(FieldTypeChangedArgs args)
private Task HandleFieldTypeChanged(FieldTypeChangedArgs args)
{
var field = args.Field;
var newField = FieldFactory.CreateField(args.NewType);
Expand All @@ -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;

Expand Down Expand Up @@ -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();
}
}
9 changes: 1 addition & 8 deletions src/FormBuilder/Components/PropertyEditor.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ public partial class PropertyEditor : ComponentBase


#region Binding Properties

private string? _label;

private string? Label
{
get => SelectedField?.Label;
Expand All @@ -47,7 +46,6 @@ private string? Label
}
}

private string? _placeholder;
private string? Placeholder
{
get => SelectedField?.Placeholder;
Expand All @@ -63,7 +61,6 @@ private string? Placeholder
}
}

private FieldType _inputType;
private FieldType InputType
{
get => SelectedField?.Type ?? FieldType.Text;
Expand All @@ -80,7 +77,6 @@ private FieldType InputType
}
}

private bool _required;
private bool Required
{
get => SelectedField?.Required ?? false;
Expand All @@ -96,7 +92,6 @@ private bool Required
}
}

private bool _readOnly;
private bool ReadOnly
{
get => SelectedField?.ReadOnly ?? false;
Expand All @@ -112,7 +107,6 @@ private bool ReadOnly
}
}

private bool _disabled;
private bool Disabled
{
get => SelectedField?.Disabled ?? false;
Expand All @@ -128,7 +122,6 @@ private bool Disabled
}
}

private int? _selectedListValue;
private int? SelectedListValue
{
get => (SelectedField as SelectField)?.Value;
Expand Down
5 changes: 5 additions & 0 deletions src/FormBuilder/Extensions/NotificationServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
7 changes: 7 additions & 0 deletions src/FormBuilder/FormBuilder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="JsonSchema.Net" Version="7.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.5"/>
<PackageReference Include="Radzen.Blazor" Version="4.34.1" />
</ItemGroup>
Expand All @@ -20,4 +21,10 @@
<ProjectReference Include="..\FormBuilder.Shared\FormBuilder.Shared.csproj" />
</ItemGroup>

<ItemGroup>
<Content Update="wwwroot\fieldScheme.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/FormBuilder/Models/DateField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/// Represents a date field.
/// The value is of type DateTime.
/// </summary>
public class DateField : Field<DateTime>
public class DateField : Field<DateTime?>
{
public DateField()
{
Expand Down
2 changes: 1 addition & 1 deletion src/FormBuilder/Services/DragDropService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// <summary>
/// Service for drag and drop operations.
/// </summary>
public class DragDropService
internal class DragDropService
{
/// <summary>
/// The data that is being dragged.
Expand Down
53 changes: 45 additions & 8 deletions src/FormBuilder/Services/FormService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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
};
}

Expand All @@ -42,15 +51,43 @@ public FormService(FormBuilderOptions options)
{
try
{
return JsonSerializer.Deserialize<FormDefinition>(formDesign, _jsonSerializerOptions);
return JsonSerializer.Deserialize<FormDefinition>(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;
}
}

/// <summary>
/// Serializes the FormDefinition object into a JSON string.
/// </summary>
/// <param name="formDefinition">
/// FormDefinition object to serialize.
/// </param>
/// <param name="indented">True to indent the JSON string; otherwise, false.</param>
/// <returns>Serialized JSON string of the FormDefinition object.</returns>
public string SerializeFormDesign(FormDefinition formDefinition, bool indented = false)
{
return JsonSerializer.Serialize(formDefinition, indented ? _jsonSerializerIndentedOptions : _jsonSerializerDefaultOptions);
}

/// <summary>
/// Asynchronously serializes the FormDefinition object into a JSON string.
/// </summary>
/// <param name="formDefinition">FormDefinition object to serialize.</param>
/// <param name="indented">True to indent the JSON string; otherwise, false.</param>
/// <returns>Serialized JSON string of the FormDefinition object.</returns>
public async Task<string> 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<Result<FormDto>> GetFormByIdAsync(string id)
{
var response = await _httpClient.GetAsync($"/api/forms/{id}");
Expand All @@ -59,7 +96,7 @@ public async Task<Result<FormDto>> GetFormByIdAsync(string id)

public async Task<Result<FormDto>> CreateFormAsync(FormDefinition formDefinition)
{
var formDesign = JsonSerializer.Serialize(formDefinition);
var formDesign = await SerializeFormDesignAsync(formDefinition);
var createFormDto = new CreateFormDto
{
FormName = formDefinition.Name,
Expand All @@ -72,7 +109,7 @@ public async Task<Result<FormDto>> CreateFormAsync(FormDefinition formDefinition

public async Task<Result> UpdateFormAsync(string id, FormDefinition formDefinition)
{
var formDesign = JsonSerializer.Serialize(formDefinition);
var formDesign = await SerializeFormDesignAsync(formDefinition);
var updateFormDto = new CreateFormDto
{
FormName = formDefinition.Name,
Expand Down
10 changes: 5 additions & 5 deletions src/FormBuilder/Utils/FieldJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@

namespace FormBuilder.Utils;

public class FieldJsonConverter : JsonConverter<Field>
internal class FieldJsonConverter : JsonConverter<Field>
{
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>(fieldType.ToString());
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 540e6c3

Please sign in to comment.