diff --git a/src/FormBuilder/Components/FieldPropertyEditor.razor b/src/FormBuilder/Components/FieldPropertyEditor.razor
new file mode 100644
index 0000000..158c0d6
--- /dev/null
+++ b/src/FormBuilder/Components/FieldPropertyEditor.razor
@@ -0,0 +1,89 @@
+@using FormBuilder.Models
+
+@if (Field is not null)
+{
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (Field is TextField textField)
+ {
+
+
+
+ }
+
+ @if (Field is NumericField numericIntField)
+ {
+
+ }
+ @if (Field is NumericField numericDecimalField)
+ {
+
+ }
+
+ @if (Field is DateField dateField)
+ {
+
+
+
+ }
+
+ @if (Field is SelectField)
+ {
+
+
+
+
+
+ @if (SelectedListId is not null)
+ {
+
+
+
+
+ }
+ }
+
+
+
+}
diff --git a/src/FormBuilder/Components/PropertyEditor.razor.cs b/src/FormBuilder/Components/FieldPropertyEditor.razor.cs
similarity index 61%
rename from src/FormBuilder/Components/PropertyEditor.razor.cs
rename to src/FormBuilder/Components/FieldPropertyEditor.razor.cs
index cf8887f..65b342b 100644
--- a/src/FormBuilder/Components/PropertyEditor.razor.cs
+++ b/src/FormBuilder/Components/FieldPropertyEditor.razor.cs
@@ -8,12 +8,13 @@ namespace FormBuilder.Components;
///
/// Component that allows editing of field properties in the form builder.
///
-public partial class PropertyEditor : ComponentBase
+public partial class FieldPropertyEditor : ComponentBase
{
private DropDownEnumItem[] _inputTypes = DropDownEnumItem.CreateItems();
private IEnumerable _listValues = [];
private IEnumerable _listIds = [];
private int _listIdCount;
+ private bool _fetchedInitialListIds;
#region Injected Services
@@ -31,13 +32,13 @@ public partial class PropertyEditor : ComponentBase
/// The currently selected field to edit.
///
[Parameter]
- public Field? SelectedField { get; set; }
+ public Field? Field { get; set; }
///
/// Event that is triggered when the selected field's property changes.
///
[Parameter]
- public EventCallback SelectedFieldChanged { get; set; }
+ public EventCallback FieldChanged { get; set; }
///
/// Event that is triggered when a field property changes such as label, placeholder, etc.
@@ -52,110 +53,93 @@ public partial class PropertyEditor : ComponentBase
private string? Label
{
- get => SelectedField?.Label;
+ get => Field?.Label;
set
{
- if (SelectedField is null || SelectedField.Label == value)
+ if (Field is null || Field.Label == value)
{
return;
}
- SelectedField.Label = value;
- SelectedFieldChanged.InvokeAsync(SelectedField);
- PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(SelectedField, nameof(Field.Label), value));
+ Field.Label = value;
+ FieldChanged.InvokeAsync(Field);
+ PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(Field, nameof(Models.Field.Label), value));
}
}
private string? Placeholder
{
- get => SelectedField?.Placeholder;
+ get => Field?.Placeholder;
set
{
- if (SelectedField is null || SelectedField.Placeholder == value)
+ if (Field is null || Field.Placeholder == value)
{
return;
}
- SelectedField.Placeholder = value;
- SelectedFieldChanged.InvokeAsync(SelectedField);
- PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(SelectedField, nameof(Field.Placeholder), value));
+ Field.Placeholder = value;
+ FieldChanged.InvokeAsync(Field);
+ PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(Field, nameof(Models.Field.Placeholder), value));
}
}
private FieldType InputType
{
- get => SelectedField?.Type ?? FieldType.Text;
+ get => Field?.Type ?? FieldType.Text;
set
{
- if (SelectedField is null || SelectedField.Type == value)
+ if (Field is null || Field.Type == value)
{
return;
}
- SelectedField.Type = value;
- SelectedFieldChanged.InvokeAsync(SelectedField);
- PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(SelectedField, nameof(Field.Type), value));
- }
- }
-
- private bool Required
- {
- get => SelectedField?.Required ?? false;
- set
- {
- if (SelectedField is null || SelectedField.Required == value)
- {
- return;
- }
-
- SelectedField.Required = value;
- SelectedFieldChanged.InvokeAsync(SelectedField);
- PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(SelectedField, nameof(Field.Required), value));
+ FieldChanged.InvokeAsync(Field);
+ PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(Field, nameof(Models.Field.Type), value));
}
}
private bool ReadOnly
{
- get => SelectedField?.ReadOnly ?? false;
+ get => Field?.ReadOnly ?? false;
set
{
- if (SelectedField is null || SelectedField.ReadOnly == value)
+ if (Field is null || Field.ReadOnly == value)
{
return;
}
- SelectedField.ReadOnly = value;
- SelectedFieldChanged.InvokeAsync(SelectedField);
- PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(SelectedField, nameof(Field.ReadOnly), value));
+ Field.ReadOnly = value;
+ FieldChanged.InvokeAsync(Field);
+ PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(Field, nameof(Models.Field.ReadOnly), value));
}
}
private bool Disabled
{
- get => SelectedField?.Disabled ?? false;
+ get => Field?.Disabled ?? false;
set
{
- if (SelectedField is null || SelectedField.Disabled == value)
+ if (Field is null || Field.Disabled == value)
{
return;
}
- SelectedField.Disabled = value;
- SelectedFieldChanged.InvokeAsync(SelectedField);
- PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(SelectedField, nameof(Field.Disabled), value));
+ Field.Disabled = value;
+ FieldChanged.InvokeAsync(Field);
+ PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(Field, nameof(Models.Field.Disabled), value));
}
}
private int? SelectedListId
{
- get => (SelectedField as SelectField)?.ListId;
+ get => (Field as SelectField)?.ListId;
set
{
- if (SelectedField is SelectField selectField && selectField.ListId != value)
+ if (Field is SelectField selectField && selectField.ListId != value)
{
selectField.ListId = value;
- SelectedFieldChanged.InvokeAsync(SelectedField);
- PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(SelectedField, nameof(SelectField.ListId), value));
+ FieldChanged.InvokeAsync(Field);
+ PropertyChanged.InvokeAsync(new FieldPropertyChangedArgs(Field, nameof(SelectField.ListId), value));
_ = FetchListValuesAsync(value);
}
}
@@ -179,10 +163,14 @@ private bool ListValuesLoading
#endregion
- protected override async Task OnInitializedAsync()
+ protected override async Task OnParametersSetAsync()
{
- // Load the first 10 list IDs
- await LoadListIdValuesAsync(new LoadDataArgs {Top = 10});
+ if (Field is SelectField && !_fetchedInitialListIds)
+ {
+ // Load the first 10 list IDs after the field is set and only once
+ await LoadListIdValuesAsync(new LoadDataArgs {Top = 10});
+ _fetchedInitialListIds = true;
+ }
}
private async Task LoadListIdValuesAsync(LoadDataArgs args)
diff --git a/src/FormBuilder/Components/FormEditor.razor b/src/FormBuilder/Components/FormEditor.razor
index a5a55ea..23c7b23 100644
--- a/src/FormBuilder/Components/FormEditor.razor
+++ b/src/FormBuilder/Components/FormEditor.razor
@@ -1,4 +1,4 @@
-@using global::FormBuilder.Models
+@using FormBuilder.Models
@@ -56,8 +56,8 @@
-
-
+
+
@@ -69,7 +69,10 @@
Properties
-
+
+
diff --git a/src/FormBuilder/Components/FormEditor.razor.cs b/src/FormBuilder/Components/FormEditor.razor.cs
index c7abec1..44cf00d 100644
--- a/src/FormBuilder/Components/FormEditor.razor.cs
+++ b/src/FormBuilder/Components/FormEditor.razor.cs
@@ -92,7 +92,7 @@ private Task HandleFieldPropertyChanged(FieldPropertyChangedArgs args)
{
if (args is { PropertyName: nameof(Field.Type), NewValue: FieldType fieldType })
{
- ChangeFieldType(args.Field, fieldType);
+ ChangeFieldType(fieldType, args.Field);
}
return UpdateFormDesignJsonAsync();
@@ -102,18 +102,12 @@ private Task HandleFieldPropertyChanged(FieldPropertyChangedArgs args)
/// Changes the field type if the new field type is different from the current one.
/// Creates a new field with the new type and copies the properties from the old field.
///
- ///
+ ///
///
- private void ChangeFieldType(Field field, FieldType newType)
+ private void ChangeFieldType(FieldType newType, Field oldField)
{
- var newField = FieldFactory.CreateField(newType);
- newField.Label = field.Label;
- newField.Placeholder = field.Placeholder;
- newField.Required = field.Required;
- newField.ReadOnly = field.ReadOnly;
- newField.Disabled = field.Disabled;
-
- var index = _formDefinition.Fields.IndexOf(field);
+ var newField = FieldFactory.CreateFieldFrom(newType, oldField);
+ var index = _formDefinition.Fields.IndexOf(oldField);
_formDefinition.Fields[index] = newField; // Replace the old field with the new one, old one will be deleted by GC
SelectedField = newField;
}
diff --git a/src/FormBuilder/Components/FormField.razor b/src/FormBuilder/Components/FormField.razor
index d34ef79..76efa4d 100644
--- a/src/FormBuilder/Components/FormField.razor
+++ b/src/FormBuilder/Components/FormField.razor
@@ -1,4 +1,4 @@
-@using global::FormBuilder.Models
+@using FormBuilder.Models
@Prepend
@@ -10,6 +10,7 @@
Name="@textField.Name"
@bind-Value="textField.Value"
Placeholder="@textField.Placeholder"
+ MaxLength="@textField.MaxLength"
ReadOnly="Disabled">
break;
@@ -22,19 +23,29 @@
ReadOnly="Disabled">
break;
- case NumericIntField numericIntField:
+ case NumericField numericIntField:
break;
- case NumericDoubleField numericDoubleField:
+ case NumericField numericField:
break;
@@ -43,6 +54,7 @@
Name="@dateField.Name"
@bind-Value="dateField.Value"
Placeholder="@dateField.Placeholder"
+ DateFormat="@dateField.DateFormat"
ReadOnly="Disabled">
break;
@@ -50,12 +62,54 @@
@Append
- @if (Field.Required)
+ @if (!string.IsNullOrEmpty(Field.Hint))
{
-
- * required
-
-
+ @Field.Hint
+ }
+
+ @foreach(var validator in Field.Validators)
+ {
+ switch (validator)
+ {
+ case RequiredValidator requiredValidator:
+ @if (requiredValidator.ShowRequiredHint)
+ {
+
+ * required
+
+ }
+
+
+ break;
+ case EmailValidator emailValidator:
+
+
+ break;
+ case LengthValidator lengthValidator:
+
+
+ break;
+ case RangeValidator rangeValidator:
+
+
+ break;
+ }
}
diff --git a/src/FormBuilder/Components/FormField.razor.cs b/src/FormBuilder/Components/FormField.razor.cs
index a2227ca..16654e0 100644
--- a/src/FormBuilder/Components/FormField.razor.cs
+++ b/src/FormBuilder/Components/FormField.razor.cs
@@ -1,4 +1,5 @@
-using FormBuilder.Models;
+using System.Globalization;
+using FormBuilder.Models;
using Microsoft.AspNetCore.Components;
namespace FormBuilder.Components;
diff --git a/src/FormBuilder/Components/PropertyEditor.razor b/src/FormBuilder/Components/PropertyEditor.razor
deleted file mode 100644
index b83a01e..0000000
--- a/src/FormBuilder/Components/PropertyEditor.razor
+++ /dev/null
@@ -1,67 +0,0 @@
-@using global::FormBuilder.Models
-
-@if (SelectedField is not null)
-{
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @if (SelectedField is SelectField)
- {
-
-
-
-
-
- @if (SelectedListId is not null)
- {
-
-
-
-
- }
- }
-
-
-}
diff --git a/src/FormBuilder/Utils/FieldJsonConverter.cs b/src/FormBuilder/Converters/FieldJsonConverter.cs
similarity index 88%
rename from src/FormBuilder/Utils/FieldJsonConverter.cs
rename to src/FormBuilder/Converters/FieldJsonConverter.cs
index a5d46d0..42b60ec 100644
--- a/src/FormBuilder/Utils/FieldJsonConverter.cs
+++ b/src/FormBuilder/Converters/FieldJsonConverter.cs
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
using FormBuilder.Models;
-namespace FormBuilder.Utils;
+namespace FormBuilder.Converters;
internal class FieldJsonConverter : JsonConverter
{
@@ -26,8 +26,8 @@ public override Field Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSe
Field? field = enumFieldType switch
{
FieldType.Text => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
- FieldType.NumericInt => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
- FieldType.NumericDouble => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
+ FieldType.NumericInt => JsonSerializer.Deserialize>(rootElement.GetRawText(), options),
+ FieldType.NumericDecimal => JsonSerializer.Deserialize>(rootElement.GetRawText(), options),
FieldType.Select => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
FieldType.Date => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
_ => throw new NotSupportedException($"The value of the field type '{enumFieldType}' is not supported"),
diff --git a/src/FormBuilder/Converters/ValidatorJsonConverter.cs b/src/FormBuilder/Converters/ValidatorJsonConverter.cs
new file mode 100644
index 0000000..9638655
--- /dev/null
+++ b/src/FormBuilder/Converters/ValidatorJsonConverter.cs
@@ -0,0 +1,47 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using FormBuilder.Models;
+
+namespace FormBuilder.Converters;
+
+internal class ValidatorJsonConverter : JsonConverter
+{
+ public override Validator 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 validatorTypeProperty))
+ {
+ throw new JsonException("The property 'type' is missing from the validator");
+ }
+
+ if (!validatorTypeProperty.TryGetInt32(out var validatorType))
+ {
+ throw new JsonException("The value of the property 'type' is not an integer");
+ }
+
+ var enumValidatorType = Enum.Parse(validatorType.ToString());
+
+ Validator? validator = enumValidatorType switch
+ {
+ ValidatorType.Required => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
+ ValidatorType.Length => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
+ ValidatorType.Email => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
+ ValidatorType.Range => JsonSerializer.Deserialize(rootElement.GetRawText(), options),
+ _ => throw new NotSupportedException($"The value of the validator type '{enumValidatorType}' is not supported"),
+ };
+
+ if (validator is null)
+ {
+ throw new JsonException($"Failed to deserialize field of type '{enumValidatorType}'");
+ }
+
+ return validator;
+ }
+
+ public override void Write(Utf8JsonWriter writer, Validator value, JsonSerializerOptions options)
+ {
+ JsonSerializer.Serialize(writer, value, value.GetType(), options);
+ }
+}
diff --git a/src/FormBuilder/Factories/FieldFactory.cs b/src/FormBuilder/Factories/FieldFactory.cs
index 2b9b71b..c88a3c9 100644
--- a/src/FormBuilder/Factories/FieldFactory.cs
+++ b/src/FormBuilder/Factories/FieldFactory.cs
@@ -20,11 +20,31 @@ public static Field CreateField(FieldType fieldType)
return fieldType switch
{
FieldType.Text => new TextField(),
- FieldType.NumericInt => new NumericIntField(),
- FieldType.NumericDouble => new NumericDoubleField(),
+ FieldType.NumericInt => new NumericField(),
+ FieldType.NumericDecimal => new NumericField(),
FieldType.Select => new SelectField(),
FieldType.Date => new DateField(),
_ => new TextField()
};
}
+
+ ///
+ /// Creates a new field model based on the provided fieldType.
+ /// Copies the properties from the old field to the new field.
+ ///
+ /// The type of the field to create.
+ ///
+ /// A generic instance of the field based on the provided fieldType.
+ ///
+ public static Field CreateFieldFrom(FieldType newFieldType, Field oldField)
+ {
+ var newField = CreateField(newFieldType);
+ newField.Label = oldField.Label;
+ newField.Placeholder = oldField.Placeholder;
+ newField.ReadOnly = oldField.ReadOnly;
+ newField.Disabled = oldField.Disabled;
+ newField.Hint = oldField.Hint;
+ newField.Validators = oldField.Validators;
+ return newField;
+ }
}
diff --git a/src/FormBuilder/FormBuilderOptions.cs b/src/FormBuilder/FormBuilderOptions.cs
index ed6c0ad..c74373a 100644
--- a/src/FormBuilder/FormBuilderOptions.cs
+++ b/src/FormBuilder/FormBuilderOptions.cs
@@ -1,6 +1,10 @@
-namespace FormBuilder;
+using FormBuilder.Shared.Models;
+
+namespace FormBuilder;
public record FormBuilderOptions
{
public string? FormApiHost { get; init; }
+
+ public Func>? GetFormById { get; init; }
}
diff --git a/src/FormBuilder/Models/DateField.cs b/src/FormBuilder/Models/DateField.cs
index bb93d4f..6cddab6 100644
--- a/src/FormBuilder/Models/DateField.cs
+++ b/src/FormBuilder/Models/DateField.cs
@@ -6,8 +6,6 @@
///
public class DateField : Field
{
- public DateField()
- {
- Type = FieldType.Date;
- }
+ public override FieldType Type => FieldType.Date;
+ public string? DateFormat { get; set; }
}
diff --git a/src/FormBuilder/Models/EmailValidator.cs b/src/FormBuilder/Models/EmailValidator.cs
new file mode 100644
index 0000000..e54036a
--- /dev/null
+++ b/src/FormBuilder/Models/EmailValidator.cs
@@ -0,0 +1,7 @@
+namespace FormBuilder.Models;
+
+public class EmailValidator : Validator
+{
+ public override ValidatorType Type => ValidatorType.Email;
+ public override string Text { get; set; } = "Invalid email address";
+}
diff --git a/src/FormBuilder/Models/Field.cs b/src/FormBuilder/Models/Field.cs
index b117760..b500f12 100644
--- a/src/FormBuilder/Models/Field.cs
+++ b/src/FormBuilder/Models/Field.cs
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
+using FormBuilder.Converters;
using FormBuilder.Utils;
namespace FormBuilder.Models;
@@ -7,21 +8,27 @@ namespace FormBuilder.Models;
/// Represents a model for the form field.
///
[JsonConverter(typeof(FieldJsonConverter))]
-public class Field
+public abstract class Field
{
- public Field()
+ private string? _name;
+
+ ///
+ /// Field name. If not provided, it will be generated.
+ ///
+ public string Name
{
- if (string.IsNullOrEmpty(Name))
+ get
{
- Name = Generator.GenerateShortId($"{Type}_".ToLower());
+ if (string.IsNullOrEmpty(_name))
+ {
+ _name = Generator.GenerateShortId($"{Type}_".ToLower());
+ }
+
+ return _name;
}
+ set => _name = value;
}
- ///
- /// Field name. If not provided, it will be generated.
- ///
- public string Name { get; set; }
-
///
/// Field label.
///
@@ -33,14 +40,9 @@ public Field()
public string? Placeholder { get; set; }
///
- /// Field type such as TextField, NumericIntField, NumericDoubleField, SelectField, DateField.
+ /// Field type such as TextField, NumericIntField, NumericDecimalField, SelectField, DateField.
///
- public FieldType Type { get; set; }
-
- ///
- /// Determines if the field is required to be filled.
- ///
- public bool Required { get; set; }
+ public abstract FieldType Type { get; }
///
/// Whether the field is read-only.
@@ -51,13 +53,23 @@ public Field()
/// Whether the field is disabled.
///
public bool Disabled { get; set; }
+
+ ///
+ /// The hint text to be displayed below the field.
+ ///
+ public string? Hint { get; set; }
+
+ ///
+ /// List of validators to be applied to the field.
+ ///
+ public List Validators { get; set; } = [];
}
///
/// Generic version of the field model with a value of type T.
///
/// The type of the field value.
-public class Field : Field
+public abstract class Field : Field
{
public T? Value { get; set; }
}
diff --git a/src/FormBuilder/Models/FieldType.cs b/src/FormBuilder/Models/FieldType.cs
index 7328b7a..96cd591 100644
--- a/src/FormBuilder/Models/FieldType.cs
+++ b/src/FormBuilder/Models/FieldType.cs
@@ -13,8 +13,8 @@ public enum FieldType
[Description("Numeric (Int)")]
NumericInt = 2,
- [Description("Numeric (Double)")]
- NumericDouble = 3,
+ [Description("Numeric (Decimal)")]
+ NumericDecimal = 3,
[Description("Date")]
Date = 4,
diff --git a/src/FormBuilder/Models/LengthValidator.cs b/src/FormBuilder/Models/LengthValidator.cs
new file mode 100644
index 0000000..4a01278
--- /dev/null
+++ b/src/FormBuilder/Models/LengthValidator.cs
@@ -0,0 +1,9 @@
+namespace FormBuilder.Models;
+
+public class LengthValidator : Validator
+{
+ public override ValidatorType Type => ValidatorType.Length;
+ public override string Text { get; set; } = "Invalid length";
+ public int? MinLength { get; set; }
+ public int? MaxLength { get; set; }
+}
diff --git a/src/FormBuilder/Models/NumericDoubleField.cs b/src/FormBuilder/Models/NumericDoubleField.cs
deleted file mode 100644
index 5c8d2c7..0000000
--- a/src/FormBuilder/Models/NumericDoubleField.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace FormBuilder.Models;
-
-public class NumericDoubleField : Field
-{
- public NumericDoubleField()
- {
- Type = FieldType.NumericDouble;
- }
-}
diff --git a/src/FormBuilder/Models/NumericField.cs b/src/FormBuilder/Models/NumericField.cs
new file mode 100644
index 0000000..2f710e6
--- /dev/null
+++ b/src/FormBuilder/Models/NumericField.cs
@@ -0,0 +1,32 @@
+namespace FormBuilder.Models;
+
+public class NumericField : Field where T : struct
+{
+ public NumericField()
+ {
+ if (typeof(T) == typeof(int) || typeof(T) == typeof(long) || typeof(T) == typeof(short))
+ {
+ Type = FieldType.NumericInt;
+ }
+ else if (typeof(T) == typeof(uint) || typeof(T) == typeof(ulong) || typeof(T) == typeof(ushort))
+ {
+ Type = FieldType.NumericInt;
+ Min = 0;
+ }
+ else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(double) || typeof(T) == typeof(float))
+ {
+ Type = FieldType.NumericDecimal;
+ }
+ else
+ {
+ throw new InvalidOperationException("Unsupported numeric type.");
+ }
+ }
+
+ public override FieldType Type { get; }
+ public decimal? Min { get; set; }
+ public decimal? Max { get; set; }
+ public string Step { get; set; } = "1";
+ public bool ShowUpDown { get; set; } = true;
+ public string? Format { get; set; }
+}
diff --git a/src/FormBuilder/Models/NumericIntField.cs b/src/FormBuilder/Models/NumericIntField.cs
deleted file mode 100644
index 1c76139..0000000
--- a/src/FormBuilder/Models/NumericIntField.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace FormBuilder.Models;
-
-public class NumericIntField : Field
-{
- public NumericIntField()
- {
- Type = FieldType.NumericInt;
- }
-}
diff --git a/src/FormBuilder/Models/NumericRangeValidator.cs b/src/FormBuilder/Models/NumericRangeValidator.cs
new file mode 100644
index 0000000..883f704
--- /dev/null
+++ b/src/FormBuilder/Models/NumericRangeValidator.cs
@@ -0,0 +1,9 @@
+namespace FormBuilder.Models;
+
+public class RangeValidator : Validator
+{
+ public override ValidatorType Type => ValidatorType.Range;
+ public override string Text { get; set; } = "Not in the valid range";
+ public int Min { get; set; }
+ public int Max { get; set; }
+}
diff --git a/src/FormBuilder/Models/RequiredValidator.cs b/src/FormBuilder/Models/RequiredValidator.cs
new file mode 100644
index 0000000..5e521d9
--- /dev/null
+++ b/src/FormBuilder/Models/RequiredValidator.cs
@@ -0,0 +1,9 @@
+namespace FormBuilder.Models;
+
+public class RequiredValidator : Validator
+{
+ public override ValidatorType Type => ValidatorType.Required;
+ public override string Text { get; set; } = "Required";
+ public bool IsRequired { get; set; } = true;
+ public bool ShowRequiredHint { get; set; } = true;
+}
diff --git a/src/FormBuilder/Models/SelectField.cs b/src/FormBuilder/Models/SelectField.cs
index 27af5de..a13d0e2 100644
--- a/src/FormBuilder/Models/SelectField.cs
+++ b/src/FormBuilder/Models/SelectField.cs
@@ -6,10 +6,7 @@
///
public class SelectField : Field
{
- public SelectField()
- {
- Type = FieldType.Select;
- }
+ public override FieldType Type => FieldType.Select;
///
/// The list ID that corresponds to the list of options for this field.
diff --git a/src/FormBuilder/Models/SelectOption.cs b/src/FormBuilder/Models/SelectOption.cs
deleted file mode 100644
index e10804f..0000000
--- a/src/FormBuilder/Models/SelectOption.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace FormBuilder.Models;
-
-///
-/// Represents a select option for a select field.
-///
-public class SelectOption
-{
- public string? Text { get; set; }
- public int? Value { get; set; }
-}
diff --git a/src/FormBuilder/Models/TextField.cs b/src/FormBuilder/Models/TextField.cs
index 63658c2..b6da344 100644
--- a/src/FormBuilder/Models/TextField.cs
+++ b/src/FormBuilder/Models/TextField.cs
@@ -2,8 +2,6 @@
public class TextField : Field
{
- public TextField()
- {
- Type = FieldType.Text;
- }
+ public override FieldType Type => FieldType.Text;
+ public long? MaxLength { get; set; }
}
diff --git a/src/FormBuilder/Models/Validator.cs b/src/FormBuilder/Models/Validator.cs
new file mode 100644
index 0000000..2be6067
--- /dev/null
+++ b/src/FormBuilder/Models/Validator.cs
@@ -0,0 +1,26 @@
+using System.Text.Json.Serialization;
+using FormBuilder.Converters;
+
+namespace FormBuilder.Models;
+
+///
+/// Represents a validator that can be applied to a form field.
+///
+[JsonConverter(typeof(ValidatorJsonConverter))]
+public abstract class Validator
+{
+ ///
+ /// The type of validator.
+ ///
+ public abstract ValidatorType Type { get; }
+
+ ///
+ /// The error text to be displayed when the validation fails.
+ ///
+ public abstract string Text { get; set; }
+
+ ///
+ /// Whether the validation message should be shown as a popup instead of inline.
+ ///
+ public bool ShowAsPopup { get; set; }
+}
diff --git a/src/FormBuilder/Models/ValidatorType.cs b/src/FormBuilder/Models/ValidatorType.cs
new file mode 100644
index 0000000..39f4681
--- /dev/null
+++ b/src/FormBuilder/Models/ValidatorType.cs
@@ -0,0 +1,9 @@
+namespace FormBuilder.Models;
+
+public enum ValidatorType
+{
+ Required = 1,
+ Length = 2,
+ Email = 3,
+ Range = 4,
+}
diff --git a/src/FormBuilder/Services/FormService.cs b/src/FormBuilder/Services/FormService.cs
index 1dda014..7f041f7 100644
--- a/src/FormBuilder/Services/FormService.cs
+++ b/src/FormBuilder/Services/FormService.cs
@@ -2,14 +2,14 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
+using FormBuilder.Converters;
using FormBuilder.Models;
using FormBuilder.Shared.Models;
-using FormBuilder.Utils;
using Microsoft.Extensions.Caching.Memory;
namespace FormBuilder.Services;
-public class FormService
+internal class FormService
{
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonSerializerDefaultOptions;
diff --git a/src/FormBuilder/Services/IFormService.cs b/src/FormBuilder/Services/IFormService.cs
deleted file mode 100644
index 44c61f7..0000000
--- a/src/FormBuilder/Services/IFormService.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace FormBuilder.Services;
-
-public interface IFormService
-{
-
-}