From 12e4850490b62af1d4ef874780299a7bab8516fd Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Thu, 22 Aug 2024 18:51:16 +0800 Subject: [PATCH] Add `NumberBox`, `Toggle`, `TimePicker`, and `ColorPicker`. --- docs/README.md | 7 - .../DynamicFormBlazorHostModule.cs | 7 - .../DynamicFormHttpApiHostModule.cs | 7 - .../DynamicFormWebUnifiedModule.cs | 7 - .../DynamicForm/DynamicFormCoreErrorCodes.cs | 8 +- .../DynamicFormDomainCoreModule.cs | 7 +- .../ColorPickerFormItemConfigurations.cs | 9 ++ .../ColorPickerFormItemProvider.cs | 87 ++++++++++ .../FileBox/FileBoxFormItemProvider.cs | 5 +- .../FormItemTypes/FormItemProviderBase.cs | 8 +- .../NumberBoxFormItemConfigurations.cs | 12 ++ .../NumberBox/NumberBoxFormItemProvider.cs | 84 ++++++++++ .../FormItemTypes/NumberBox/NumberUi.cs | 23 +++ .../OptionButtonsFormItemProvider.cs | 5 - .../TextBox/TextBoxFormItemConfigurations.cs | 13 +- .../TextBox/TextBoxFormItemProvider.cs | 27 ++-- .../FormItemTypes/TextBox/TextFormat.cs | 8 + .../FormItemTypes/TimePicker/TimeDimension.cs | 31 ++++ .../TimePickerFormItemConfigurations.cs | 15 ++ .../TimePicker/TimePickerFormItemProvider.cs | 61 +++++++ .../FormItemTypes/TimePicker/TimePrecision.cs | 26 +++ .../Toggle/ToggleFormItemConfigurations.cs | 5 + .../Toggle/ToggleFormItemProvider.cs | 68 ++++++++ .../DynamicFormCoreOptionsExtensions.cs | 63 +++++++- .../EasyAbp/DynamicForm/Localization/en.json | 96 ++++++----- .../DynamicForm/Localization/zh-Hans.json | 96 ++++++----- .../DynamicForm/Localization/zh-Hant.json | 96 ++++++----- .../DynamicFormCore/FormItemTemplateTests.cs | 107 +++++++++++- .../DynamicFormCore/FormItemTests.cs | 153 ++++++++++++++++++ .../DynamicFormDataSeedContributor.cs | 55 ++++++- .../DynamicFormTestBaseModule.cs | 7 - .../FormItemTestHelper.cs | 4 + 32 files changed, 1000 insertions(+), 207 deletions(-) create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/ColorPicker/ColorPickerFormItemConfigurations.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/ColorPicker/ColorPickerFormItemProvider.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberBoxFormItemConfigurations.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberBoxFormItemProvider.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberUi.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextFormat.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimeDimension.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePickerFormItemConfigurations.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePickerFormItemProvider.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePrecision.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/Toggle/ToggleFormItemConfigurations.cs create mode 100644 src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/Toggle/ToggleFormItemProvider.cs diff --git a/docs/README.md b/docs/README.md index 4157cf8..66bcd42 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,13 +39,6 @@ An ABP module helps users to define and use dynamic forms at runtime. { options.AddOrUpdateFormDefinition(new FormDefinition("InternalForm", "Internal Form")); }); - - Configure(options => - { - options.AddTextBoxFormItemType(); - options.AddOptionButtonsFormItemType(); - // Add any type you want, including your custom types.... - }); ``` 2. (Optional) Create a custom `FormTemplateOperationAuthorizationHandler` to determine who can create/read/update/delete diff --git a/host/EasyAbp.DynamicForm.Blazor.Server.Host/DynamicFormBlazorHostModule.cs b/host/EasyAbp.DynamicForm.Blazor.Server.Host/DynamicFormBlazorHostModule.cs index 79a8ef0..98d766d 100644 --- a/host/EasyAbp.DynamicForm.Blazor.Server.Host/DynamicFormBlazorHostModule.cs +++ b/host/EasyAbp.DynamicForm.Blazor.Server.Host/DynamicFormBlazorHostModule.cs @@ -121,13 +121,6 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.AddOrUpdateFormDefinition(new FormDefinition("InternalForm", "Internal Form")); }); - Configure(options => - { - options.AddTextBoxFormItemType(); - options.AddOptionButtonsFormItemType(); - options.AddFileBoxFormItemType(); - }); - Configure(options => { options.UseSqlServer(); diff --git a/host/EasyAbp.DynamicForm.HttpApi.Host/DynamicFormHttpApiHostModule.cs b/host/EasyAbp.DynamicForm.HttpApi.Host/DynamicFormHttpApiHostModule.cs index 316bc70..717989d 100644 --- a/host/EasyAbp.DynamicForm.HttpApi.Host/DynamicFormHttpApiHostModule.cs +++ b/host/EasyAbp.DynamicForm.HttpApi.Host/DynamicFormHttpApiHostModule.cs @@ -61,13 +61,6 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.AddOrUpdateFormDefinition(new FormDefinition("InternalForm", "Internal Form")); }); - Configure(options => - { - options.AddTextBoxFormItemType(); - options.AddOptionButtonsFormItemType(); - options.AddFileBoxFormItemType(); - }); - Configure(options => { options.UseSqlServer(); diff --git a/host/EasyAbp.DynamicForm.Web.Unified/DynamicFormWebUnifiedModule.cs b/host/EasyAbp.DynamicForm.Web.Unified/DynamicFormWebUnifiedModule.cs index b1ebd7f..f839ac1 100644 --- a/host/EasyAbp.DynamicForm.Web.Unified/DynamicFormWebUnifiedModule.cs +++ b/host/EasyAbp.DynamicForm.Web.Unified/DynamicFormWebUnifiedModule.cs @@ -86,13 +86,6 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.AddOrUpdateFormDefinition(new FormDefinition("InternalForm", "Internal Form")); }); - Configure(options => - { - options.AddTextBoxFormItemType(); - options.AddOptionButtonsFormItemType(); - options.AddFileBoxFormItemType(); - }); - Configure(options => { options.UseSqlServer(); diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/DynamicFormCoreErrorCodes.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/DynamicFormCoreErrorCodes.cs index 0730108..861db28 100644 --- a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/DynamicFormCoreErrorCodes.cs +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/DynamicFormCoreErrorCodes.cs @@ -8,9 +8,15 @@ public static class DynamicFormCoreErrorCodes public const string InvalidFormItemValue = "EasyAbp.DynamicForm:InvalidFormItemValue"; public const string FormItemValueIsRequired = "EasyAbp.DynamicForm:FormItemValueIsRequired"; public const string TextBoxInvalidMaxLength = "EasyAbp.DynamicForm:TextBoxInvalidMaxLength"; - public const string TextBoxInvalidRegexPattern = "EasyAbp.DynamicForm:TextBoxInvalidRegexPattern"; + public const string InvalidRegexPattern = "EasyAbp.DynamicForm:InvalidRegexPattern"; public const string TextBoxInvalidValueLength = "EasyAbp.DynamicForm:TextBoxInvalidValueLength"; + public const string NumberBoxInvalidValue = "EasyAbp.DynamicForm:NumberBoxInvalidValue"; + public const string NumberBoxInvalidMaxValue = "EasyAbp.DynamicForm:NumberBoxInvalidMaxValue"; + public const string NumberBoxInvalidNumericValue = "EasyAbp.DynamicForm:NumberBoxInvalidNumericValue"; public const string OptionButtonsInvalidMaxSelection = "EasyAbp.DynamicForm:OptionButtonsInvalidMaxSelection"; public const string OptionButtonsInvalidOptionQuantitySelected = "EasyAbp.DynamicForm:OptionButtonsInvalidOptionQuantitySelected"; public const string FileBoxInvalidUrls = "EasyAbp.DynamicForm:FileBoxInvalidUrls"; + public const string ToggleIsOptional = "EasyAbp.DynamicForm:ToggleIsOptional"; + public const string TimePickerInvalidDateTime = "EasyAbp.DynamicForm:TimePickerInvalidDateTime"; + public const string ColorPickerInvalidHexValue = "EasyAbp.DynamicForm:ColorPickerInvalidHexValue"; } \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/DynamicFormDomainCoreModule.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/DynamicFormDomainCoreModule.cs index b778d02..709eb0d 100644 --- a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/DynamicFormDomainCoreModule.cs +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/DynamicFormDomainCoreModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Json; +using EasyAbp.DynamicForm.Options; +using Volo.Abp.Json; using Volo.Abp.Modularity; namespace EasyAbp.DynamicForm; @@ -9,4 +10,8 @@ namespace EasyAbp.DynamicForm; )] public class DynamicFormDomainCoreModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => { options.AddBuiltInFormItemTypes(); }); + } } \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/ColorPicker/ColorPickerFormItemConfigurations.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/ColorPicker/ColorPickerFormItemConfigurations.cs new file mode 100644 index 0000000..98a7ff5 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/ColorPicker/ColorPickerFormItemConfigurations.cs @@ -0,0 +1,9 @@ +using JetBrains.Annotations; + +namespace EasyAbp.DynamicForm.FormItemTypes.ColorPicker; + +public class ColorPickerFormItemConfigurations +{ + [CanBeNull] + public string RegexPattern { get; set; } +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/ColorPicker/ColorPickerFormItemProvider.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/ColorPicker/ColorPickerFormItemProvider.cs new file mode 100644 index 0000000..23c5b72 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/ColorPicker/ColorPickerFormItemProvider.cs @@ -0,0 +1,87 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using EasyAbp.DynamicForm.Shared; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.DynamicForm.FormItemTypes.ColorPicker; + +public partial class ColorPickerFormItemProvider : FormItemProviderBase, IScopedDependency +{ + public static string Name { get; set; } = "ColorPicker"; + public static string LocalizationItemKey { get; set; } = "FormItemType.ColorPicker"; + + public override Task ValidateTemplateAsync(IFormItemMetadata metadata) + { + var configurations = GetConfigurations(metadata); + + if (!configurations.RegexPattern.IsNullOrWhiteSpace()) + { + try + { + _ = Regex.Match(string.Empty, configurations.RegexPattern!); + } + catch + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidRegexPattern) + .WithData("item", metadata.Name); + } + } + + if (metadata.AvailableValues.Any(x => !HexRegex().IsMatch(x))) + { + throw new BusinessException(DynamicFormCoreErrorCodes.ColorPickerInvalidHexValue) + .WithData("item", metadata.Name); + } + + return Task.CompletedTask; + } + + public override Task ValidateValueAsync(IFormItemMetadata metadata, string value) + { + var isEmptyValue = value.IsNullOrWhiteSpace(); + var configurations = GetConfigurations(metadata); + + if (!metadata.Optional && isEmptyValue) + { + throw new BusinessException(DynamicFormCoreErrorCodes.FormItemValueIsRequired) + .WithData("item", metadata.Name); + } + + if (isEmptyValue) + { + return Task.CompletedTask; + } + + if (!HexRegex().IsMatch(value)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.ColorPickerInvalidHexValue) + .WithData("item", metadata.Name); + } + + if (!configurations.RegexPattern.IsNullOrWhiteSpace() && !Regex.IsMatch(value!, configurations.RegexPattern!)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + + if (metadata.AvailableValues.Any() && + !metadata.AvailableValues.Contains(value, StringComparer.InvariantCultureIgnoreCase)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + + return Task.CompletedTask; + } + + public override Task CreateConfigurationsObjectOrNullAsync() + { + return Task.FromResult(new ColorPickerFormItemConfigurations()); + } + + [GeneratedRegex("^#(?:[0-9a-fA-F]{3,4}){1,2}$")] + private static partial Regex HexRegex(); +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/FileBox/FileBoxFormItemProvider.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/FileBox/FileBoxFormItemProvider.cs index 458807f..b8149a1 100644 --- a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/FileBox/FileBoxFormItemProvider.cs +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/FileBox/FileBoxFormItemProvider.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using EasyAbp.DynamicForm.Shared; using Volo.Abp.DependencyInjection; -using Volo.Abp.Json; namespace EasyAbp.DynamicForm.FormItemTypes.FileBox; @@ -13,9 +12,7 @@ public class FileBoxFormItemProvider : FormItemProviderBase, IScopedDependency protected IEnumerable ValueValidators { get; } - public FileBoxFormItemProvider( - IJsonSerializer jsonSerializer, - IEnumerable valueValidators) : base(jsonSerializer) + public FileBoxFormItemProvider(IEnumerable valueValidators) { ValueValidators = valueValidators; } diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/FormItemProviderBase.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/FormItemProviderBase.cs index 7f6b4ae..1651750 100644 --- a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/FormItemProviderBase.cs +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/FormItemProviderBase.cs @@ -1,18 +1,16 @@ using System; using System.Threading.Tasks; using EasyAbp.DynamicForm.Shared; +using Volo.Abp.DependencyInjection; using Volo.Abp.Json; namespace EasyAbp.DynamicForm.FormItemTypes; public abstract class FormItemProviderBase : IFormItemProvider { - protected IJsonSerializer JsonSerializer { get; } + public IAbpLazyServiceProvider LazyServiceProvider { get; set; } - public FormItemProviderBase(IJsonSerializer jsonSerializer) - { - JsonSerializer = jsonSerializer; - } + protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService(); public abstract Task ValidateTemplateAsync(IFormItemMetadata metadata); diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberBoxFormItemConfigurations.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberBoxFormItemConfigurations.cs new file mode 100644 index 0000000..c87c2a3 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberBoxFormItemConfigurations.cs @@ -0,0 +1,12 @@ +namespace EasyAbp.DynamicForm.FormItemTypes.NumberBox; + +public class NumberBoxFormItemConfigurations +{ + public byte DecimalPlaces { get; set; } + + public long? MaxValue { get; set; } + + public long? MinValue { get; set; } + + public NumberUi NumberUi { get; set; } +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberBoxFormItemProvider.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberBoxFormItemProvider.cs new file mode 100644 index 0000000..37ddb6b --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberBoxFormItemProvider.cs @@ -0,0 +1,84 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using EasyAbp.DynamicForm.Shared; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.DynamicForm.FormItemTypes.NumberBox; + +public class NumberBoxFormItemProvider : FormItemProviderBase, IScopedDependency +{ + public static string Name { get; set; } = "NumberBox"; + public static string LocalizationItemKey { get; set; } = "FormItemType.NumberBox"; + + public override Task ValidateTemplateAsync(IFormItemMetadata metadata) + { + var configurations = GetConfigurations(metadata); + + if (configurations.MinValue.HasValue && configurations.MaxValue.HasValue && + configurations.MinValue > configurations.MaxValue) + { + throw new BusinessException(DynamicFormCoreErrorCodes.NumberBoxInvalidMaxValue) + .WithData("item", metadata.Name); + } + + if (metadata.AvailableValues.Any(x => !decimal.TryParse(x, out _))) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + + return Task.CompletedTask; + } + + public override Task ValidateValueAsync(IFormItemMetadata metadata, string value) + { + var isEmptyValue = value.IsNullOrWhiteSpace(); + + if (!metadata.Optional && isEmptyValue) + { + throw new BusinessException(DynamicFormCoreErrorCodes.FormItemValueIsRequired) + .WithData("item", metadata.Name); + } + + if (isEmptyValue) + { + return Task.CompletedTask; + } + + if (!decimal.TryParse(value, out var numericValue)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.NumberBoxInvalidNumericValue) + .WithData("item", metadata.Name); + } + + var configurations = GetConfigurations(metadata); + + int decimalPlaces = BitConverter.GetBytes(decimal.GetBits(numericValue)[3])[2]; + + if (configurations.DecimalPlaces < decimalPlaces || + configurations.MinValue.HasValue && numericValue < configurations.MinValue || + (configurations.MaxValue.HasValue && numericValue > configurations.MaxValue)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.NumberBoxInvalidValue) + .WithData("item", metadata.Name) + .WithData("decimalPlaces", configurations.DecimalPlaces) + .WithData("min", configurations.MinValue.HasValue ? configurations.MinValue.Value : "-∞") + .WithData("max", configurations.MaxValue.HasValue ? configurations.MaxValue.Value : "∞"); + } + + if (metadata.AvailableValues.Any() && !metadata.AvailableValues.Select(decimal.Parse).Contains(numericValue)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + + return Task.CompletedTask; + } + + public override Task CreateConfigurationsObjectOrNullAsync() + { + return Task.FromResult(new NumberBoxFormItemConfigurations()); + } +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberUi.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberUi.cs new file mode 100644 index 0000000..8ed7091 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/NumberBox/NumberUi.cs @@ -0,0 +1,23 @@ +namespace EasyAbp.DynamicForm.FormItemTypes.NumberBox; + +public enum NumberUi +{ + Default = 0, + + /// + /// Normal numerical input box. + /// + NumberInput = 1, + + /// + /// The provides a ranged number selection. + /// High-precision decimals or a wider range of numbers may not be well-supported. + /// + Slider = 2, + + /// + /// The provides a simple stars selection, usually integers like 1 to 5. + /// High-precision decimals or a wider range of numbers may not be well-supported. + /// + Rating = 3 +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/OptionButtons/OptionButtonsFormItemProvider.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/OptionButtons/OptionButtonsFormItemProvider.cs index 2f1d101..5c43220 100644 --- a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/OptionButtons/OptionButtonsFormItemProvider.cs +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/OptionButtons/OptionButtonsFormItemProvider.cs @@ -4,7 +4,6 @@ using EasyAbp.DynamicForm.Shared; using Volo.Abp; using Volo.Abp.DependencyInjection; -using Volo.Abp.Json; namespace EasyAbp.DynamicForm.FormItemTypes.OptionButtons; @@ -14,10 +13,6 @@ public class OptionButtonsFormItemProvider : FormItemProviderBase, IScopedDepend public static string LocalizationItemKey { get; set; } = "FormItemType.OptionButtons"; public static string SelectionSeparator { get; set; } = ","; - public OptionButtonsFormItemProvider(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } - public override Task ValidateTemplateAsync(IFormItemMetadata metadata) { var configurations = GetConfigurations(metadata); diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextBoxFormItemConfigurations.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextBoxFormItemConfigurations.cs index a6c6a95..6fbd4c4 100644 --- a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextBoxFormItemConfigurations.cs +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextBoxFormItemConfigurations.cs @@ -4,11 +4,18 @@ namespace EasyAbp.DynamicForm.FormItemTypes.TextBox; public class TextBoxFormItemConfigurations { - public int TextBoxRows { get; set; } = 1; + public int Rows { get; set; } = 1; - public int? MaxLength { get; set; } + /// + /// This option works when ` == 1` and ` == `. + /// + public bool IsSecret { get; set; } - public int? MinLength { get; set; } + public uint? MaxLength { get; set; } + + public uint? MinLength { get; set; } + + public TextFormat TextFormat { get; set; } [CanBeNull] public string RegexPattern { get; set; } diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextBoxFormItemProvider.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextBoxFormItemProvider.cs index f136439..420d378 100644 --- a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextBoxFormItemProvider.cs +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextBoxFormItemProvider.cs @@ -5,7 +5,6 @@ using EasyAbp.DynamicForm.Shared; using Volo.Abp; using Volo.Abp.DependencyInjection; -using Volo.Abp.Json; namespace EasyAbp.DynamicForm.FormItemTypes.TextBox; @@ -14,10 +13,6 @@ public class TextBoxFormItemProvider : FormItemProviderBase, IScopedDependency public static string Name { get; set; } = "TextBox"; public static string LocalizationItemKey { get; set; } = "FormItemType.TextBox"; - public TextBoxFormItemProvider(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } - public override Task ValidateTemplateAsync(IFormItemMetadata metadata) { var configurations = GetConfigurations(metadata); @@ -33,11 +28,11 @@ public override Task ValidateTemplateAsync(IFormItemMetadata metadata) { try { - var _ = Regex.Match(string.Empty, configurations.RegexPattern!); + _ = Regex.Match(string.Empty, configurations.RegexPattern!); } catch { - throw new BusinessException(DynamicFormCoreErrorCodes.TextBoxInvalidRegexPattern) + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidRegexPattern) .WithData("item", metadata.Name); } } @@ -55,16 +50,10 @@ public override Task ValidateValueAsync(IFormItemMetadata metadata, string value .WithData("item", metadata.Name); } - if (!isEmptyValue && metadata.AvailableValues.Any() && !metadata.AvailableValues.Contains(value)) - { - throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) - .WithData("item", metadata.Name); - } - var configurations = GetConfigurations(metadata); - if (configurations.MinLength.HasValue && !value.IsNullOrEmpty() && value!.Length < configurations.MinLength || - (configurations.MaxLength.HasValue && value?.Length > configurations.MaxLength)) + if (configurations.MinLength.HasValue && !isEmptyValue && value.Length < configurations.MinLength || + (configurations.MaxLength.HasValue && !isEmptyValue && value.Length > configurations.MaxLength)) { throw new BusinessException(DynamicFormCoreErrorCodes.TextBoxInvalidValueLength) .WithData("item", metadata.Name) @@ -72,13 +61,19 @@ public override Task ValidateValueAsync(IFormItemMetadata metadata, string value .WithData("max", configurations.MaxLength.HasValue ? configurations.MaxLength.Value : "∞"); } - if (!value.IsNullOrWhiteSpace() && !configurations.RegexPattern.IsNullOrWhiteSpace() && + if (!isEmptyValue && !configurations.RegexPattern.IsNullOrWhiteSpace() && !Regex.IsMatch(value!, configurations.RegexPattern!)) { throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) .WithData("item", metadata.Name); } + if (!isEmptyValue && metadata.AvailableValues.Any() && !metadata.AvailableValues.Contains(value)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + return Task.CompletedTask; } diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextFormat.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextFormat.cs new file mode 100644 index 0000000..0a121b2 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TextBox/TextFormat.cs @@ -0,0 +1,8 @@ +namespace EasyAbp.DynamicForm.FormItemTypes.TextBox; + +public enum TextFormat +{ + Default = 0, + PlainText = 1, + Markdown = 2 +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimeDimension.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimeDimension.cs new file mode 100644 index 0000000..0deed5f --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimeDimension.cs @@ -0,0 +1,31 @@ +namespace EasyAbp.DynamicForm.FormItemTypes.TimePicker; + +public enum TimeDimension +{ + Default = 0, + + /// + /// 2024-08-21T12:55:20.132Z + /// + DateTime = 1, + + /// + /// 2024-01-01 + /// + Year = 2, + + /// + /// 2024-08-01 + /// + Month = 3, + + /// + /// 2024-08-21 + /// + Day = 4, + + /// + /// 12:55:20.132 + /// + Time = 5 +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePickerFormItemConfigurations.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePickerFormItemConfigurations.cs new file mode 100644 index 0000000..49e6fc6 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePickerFormItemConfigurations.cs @@ -0,0 +1,15 @@ +namespace EasyAbp.DynamicForm.FormItemTypes.TimePicker; + +public class TimePickerFormItemConfigurations +{ + public TimeDimension TimeDimension { get; set; } + + public TimePrecision TimePrecision { get; set; } + + /// + /// If true, the value represents a time range (including start and end times). + /// Example for a single time: 2024-08-21T12:55:20.132Z. + /// Example for a ranged time: 2024-08-21T12:55:20.132Z;2024-08-22T12:55:20.132Z. + /// + public bool IsTimeRange { get; set; } +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePickerFormItemProvider.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePickerFormItemProvider.cs new file mode 100644 index 0000000..c7291c6 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePickerFormItemProvider.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using EasyAbp.DynamicForm.Shared; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.DynamicForm.FormItemTypes.TimePicker; + +public class TimePickerFormItemProvider : FormItemProviderBase, IScopedDependency +{ + public static string Name { get; set; } = "TimePicker"; + public static string LocalizationItemKey { get; set; } = "FormItemType.TimePicker"; + + public override Task ValidateTemplateAsync(IFormItemMetadata metadata) + { + if (metadata.AvailableValues.Any(x => !DateTime.TryParse(x, out _))) + { + throw new BusinessException(DynamicFormCoreErrorCodes.TimePickerInvalidDateTime) + .WithData("item", metadata.Name); + } + + return Task.CompletedTask; + } + + public override Task ValidateValueAsync(IFormItemMetadata metadata, string value) + { + var isEmptyValue = value.IsNullOrWhiteSpace(); + + if (!metadata.Optional && isEmptyValue) + { + throw new BusinessException(DynamicFormCoreErrorCodes.FormItemValueIsRequired) + .WithData("item", metadata.Name); + } + + if (isEmptyValue) + { + return Task.CompletedTask; + } + + + if (!DateTime.TryParse(value, out var dateTimeValue)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.TimePickerInvalidDateTime) + .WithData("item", metadata.Name); + } + + if (metadata.AvailableValues.Any() && !metadata.AvailableValues.Select(DateTime.Parse).Contains(dateTimeValue)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + + return Task.CompletedTask; + } + + public override Task CreateConfigurationsObjectOrNullAsync() + { + return Task.FromResult(new TimePickerFormItemConfigurations()); + } +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePrecision.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePrecision.cs new file mode 100644 index 0000000..76fd26f --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/TimePicker/TimePrecision.cs @@ -0,0 +1,26 @@ +namespace EasyAbp.DynamicForm.FormItemTypes.TimePicker; + +public enum TimePrecision +{ + Default = 0, + + /// + /// 1970-01-01T12:55:20.132Z + /// + Full = 1, + + /// + /// 1970-01-01T12:55:20.000Z + /// + Seconds = 2, + + /// + /// 1970-01-01T12:55:00.000Z + /// + Minutes = 3, + + /// + /// 1970-01-01T12:00:00.000Z + /// + Hours = 4 +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/Toggle/ToggleFormItemConfigurations.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/Toggle/ToggleFormItemConfigurations.cs new file mode 100644 index 0000000..391c970 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/Toggle/ToggleFormItemConfigurations.cs @@ -0,0 +1,5 @@ +namespace EasyAbp.DynamicForm.FormItemTypes.Toggle; + +public class ToggleFormItemConfigurations +{ +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/Toggle/ToggleFormItemProvider.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/Toggle/ToggleFormItemProvider.cs new file mode 100644 index 0000000..74243b2 --- /dev/null +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/FormItemTypes/Toggle/ToggleFormItemProvider.cs @@ -0,0 +1,68 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using EasyAbp.DynamicForm.Shared; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.DynamicForm.FormItemTypes.Toggle; + +public class ToggleFormItemProvider : FormItemProviderBase, IScopedDependency +{ + public static string Name { get; set; } = "Toggle"; + public static string LocalizationItemKey { get; set; } = "FormItemType.Toggle"; + + public static string TrueValue { get; set; } = true.ToString(); + public static string FalseValue { get; set; } = false.ToString(); + + public override Task ValidateTemplateAsync(IFormItemMetadata metadata) + { + if (metadata.Optional) + { + throw new BusinessException(DynamicFormCoreErrorCodes.ToggleIsOptional) + .WithData("item", metadata.Name); + } + + if (metadata.AvailableValues.Any(x => + !x.Equals(TrueValue, StringComparison.InvariantCultureIgnoreCase) && + !x.Equals(FalseValue, StringComparison.InvariantCultureIgnoreCase))) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + + return Task.CompletedTask; + } + + public override Task ValidateValueAsync(IFormItemMetadata metadata, string value) + { + var isEmptyValue = value.IsNullOrWhiteSpace(); + + if (isEmptyValue) + { + throw new BusinessException(DynamicFormCoreErrorCodes.FormItemValueIsRequired) + .WithData("item", metadata.Name); + } + + if (metadata.AvailableValues.Any() && + !metadata.AvailableValues.Contains(value, StringComparer.InvariantCultureIgnoreCase)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + + if (value.Equals(TrueValue, StringComparison.InvariantCultureIgnoreCase) && + value.Equals(FalseValue, StringComparison.InvariantCultureIgnoreCase)) + { + throw new BusinessException(DynamicFormCoreErrorCodes.InvalidFormItemValue) + .WithData("item", metadata.Name); + } + + return Task.CompletedTask; + } + + public override Task CreateConfigurationsObjectOrNullAsync() + { + return Task.FromResult(new ToggleFormItemConfigurations()); + } +} \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/Options/DynamicFormCoreOptionsExtensions.cs b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/Options/DynamicFormCoreOptionsExtensions.cs index fa7965c..c00fb55 100644 --- a/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/Options/DynamicFormCoreOptionsExtensions.cs +++ b/src/EasyAbp.DynamicForm.Domain.Core/EasyAbp/DynamicForm/Options/DynamicFormCoreOptionsExtensions.cs @@ -1,11 +1,28 @@ -using EasyAbp.DynamicForm.FormItemTypes.FileBox; +using EasyAbp.DynamicForm.FormItemTypes.ColorPicker; +using EasyAbp.DynamicForm.FormItemTypes.FileBox; +using EasyAbp.DynamicForm.FormItemTypes.NumberBox; using EasyAbp.DynamicForm.FormItemTypes.OptionButtons; using EasyAbp.DynamicForm.FormItemTypes.TextBox; +using EasyAbp.DynamicForm.FormItemTypes.TimePicker; +using EasyAbp.DynamicForm.FormItemTypes.Toggle; namespace EasyAbp.DynamicForm.Options; public static class DynamicFormCoreOptionsExtensions { + public static DynamicFormCoreOptions AddBuiltInFormItemTypes(this DynamicFormCoreOptions coreOptions) + { + AddTextBoxFormItemType(coreOptions); + AddOptionButtonsFormItemType(coreOptions); + AddFileBoxFormItemType(coreOptions); + AddNumberBoxFormItemType(coreOptions); + AddToggleFormItemType(coreOptions); + AddTimePickerFormItemType(coreOptions); + AddColorPickerFormItemType(coreOptions); + + return coreOptions; + } + public static DynamicFormCoreOptions AddTextBoxFormItemType(this DynamicFormCoreOptions coreOptions) { coreOptions.AddOrUpdateFormItemTypeDefinition(new FormItemTypeDefinition( @@ -38,4 +55,48 @@ public static DynamicFormCoreOptions AddFileBoxFormItemType(this DynamicFormCore return coreOptions; } + + public static DynamicFormCoreOptions AddNumberBoxFormItemType(this DynamicFormCoreOptions coreOptions) + { + coreOptions.AddOrUpdateFormItemTypeDefinition(new FormItemTypeDefinition( + NumberBoxFormItemProvider.Name, + NumberBoxFormItemProvider.LocalizationItemKey, + typeof(NumberBoxFormItemProvider) + )); + + return coreOptions; + } + + public static DynamicFormCoreOptions AddToggleFormItemType(this DynamicFormCoreOptions coreOptions) + { + coreOptions.AddOrUpdateFormItemTypeDefinition(new FormItemTypeDefinition( + ToggleFormItemProvider.Name, + ToggleFormItemProvider.LocalizationItemKey, + typeof(ToggleFormItemProvider) + )); + + return coreOptions; + } + + public static DynamicFormCoreOptions AddTimePickerFormItemType(this DynamicFormCoreOptions coreOptions) + { + coreOptions.AddOrUpdateFormItemTypeDefinition(new FormItemTypeDefinition( + TimePickerFormItemProvider.Name, + TimePickerFormItemProvider.LocalizationItemKey, + typeof(TimePickerFormItemProvider) + )); + + return coreOptions; + } + + public static DynamicFormCoreOptions AddColorPickerFormItemType(this DynamicFormCoreOptions coreOptions) + { + coreOptions.AddOrUpdateFormItemTypeDefinition(new FormItemTypeDefinition( + ColorPickerFormItemProvider.Name, + ColorPickerFormItemProvider.LocalizationItemKey, + typeof(ColorPickerFormItemProvider) + )); + + return coreOptions; + } } \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/en.json b/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/en.json index 18d887a..4c7fd52 100644 --- a/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/en.json +++ b/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/en.json @@ -1,64 +1,74 @@ { "culture": "en", "texts": { - "EasyAbp.DynamicForm:DuplicateFormItemTemplate": "Duplicate form item template.", + "CreateForm": "Create", + "CreateFormItemTemplate": "Create", + "CreateFormTemplate": "Create", + "EasyAbp.DynamicForm:ColorPickerInvalidHexValue": "{item}: Invalid HEX color code.", "EasyAbp.DynamicForm:DuplicateFormItem": "Duplicate form item.", - "EasyAbp.DynamicForm:MissingFormItem": "{item}: Missing required form item.", - "EasyAbp.DynamicForm:InvalidFormItemValue": "{item}: Invalid form item value.", + "EasyAbp.DynamicForm:DuplicateFormItemTemplate": "Duplicate form item template.", + "EasyAbp.DynamicForm:FileBoxInvalidUrls": "{item}: Invalid file URL format.", "EasyAbp.DynamicForm:FormItemValueIsRequired": "{item}: The form item value is required.", - "EasyAbp.DynamicForm:TextBoxInvalidMaxLength": "{item}: The max length should be greater than the min length.", - "EasyAbp.DynamicForm:TextBoxInvalidRegexPattern": "{item}: Invalid regex pattern.", - "EasyAbp.DynamicForm:TextBoxInvalidValueLength": "The text length of {item} should be between {min} and {max}.", + "EasyAbp.DynamicForm:InvalidFormItemValue": "{item}: Invalid form item value.", + "EasyAbp.DynamicForm:InvalidRegexPattern": "{item}: Invalid regex pattern.", + "EasyAbp.DynamicForm:MissingFormItem": "{item}: Missing required form item.", + "EasyAbp.DynamicForm:NumberBoxInvalidMaxValue": "{item}:The max value should be greater than the min value.", + "EasyAbp.DynamicForm:NumberBoxInvalidNumericValue": "{item}:Invalid numeric value.", + "EasyAbp.DynamicForm:NumberBoxInvalidValue": "The value of {item} should be between {min} and {max}; the number of decimal places is {decimalPlaces}.", "EasyAbp.DynamicForm:OptionButtonsInvalidMaxSelection": "{item}: The max selection should be greater than the min selection.", "EasyAbp.DynamicForm:OptionButtonsInvalidOptionQuantitySelected": "The selected option quantity of {item} should be between {min} and {max}.", - "EasyAbp.DynamicForm:FileBoxInvalidUrls": "{item}: Invalid file URL format.", - "Permission:Form": "Form", - "Permission:Manage": "Manage", - "Permission:Create": "Create", - "Permission:Update": "Update", - "Permission:Delete": "Delete", - "Menu:DynamicForm": "Dynamic form", - "Menu:Form": "Form", + "EasyAbp.DynamicForm:TextBoxInvalidMaxLength": "{item}: The max length should be greater than the min length.", + "EasyAbp.DynamicForm:TextBoxInvalidValueLength": "The text length of {item} should be between {min} and {max}.", + "EasyAbp.DynamicForm:TimePickerInvalidDateTime": "{item}: Invalid date time format.", + "EasyAbp.DynamicForm:ToggleIsOptional": "{item}: Toggle cannot be optional.", + "EditForm": "Edit", + "EditFormItemTemplate": "Edit", + "EditFormTemplate": "Edit", "Form": "Form", - "FormFormDefinitionName": "Definition name", "FormCreationTime": "Created", "FormCreator": "Created by", - "FormFormTemplateName": "Form template name", + "FormDeletionConfirmationMessage": "Are you sure to delete the form {0}?", + "FormFormDefinitionName": "Definition name", "FormFormItemTemplates": "Form item templates", "FormFormItems": "Form items", - "CreateForm": "Create", - "EditForm": "Edit", - "FormDeletionConfirmationMessage": "Are you sure to delete the form {0}?", - "SuccessfullyDeleted": "Successfully deleted", - "TableFilter": "Table filter", - "Permission:FormTemplate": "Form template", - "Menu:FormTemplate": "Form template", - "FormTemplate": "Form template", - "FormTemplateFormDefinitionName": "Definition name", - "FormTemplateName": "Name", - "FormTemplateCustomTag": "Custom tag", - "FormTemplateFormItemTemplates": "Form items", - "CreateFormTemplate": "Create", - "EditFormTemplate": "Edit", - "FormTemplateDeletionConfirmationMessage": "Are you sure to delete the form template {0}?", + "FormFormTemplateName": "Form template name", "FormItemTemplate": "Form item template", - "FormItemTemplateName": "Name", - "FormItemTemplateGroup": "Group", - "FormItemTemplateInfoText": "Input info text", - "FormItemTemplateType": "Type", - "FormItemTemplateOptional": "Optional", - "FormItemTemplateConfigurations": "Configurations", "FormItemTemplateAvailableValues": "Radio values", "FormItemTemplateAvailableValuesInfo": "Split available values with commas.", - "FormItemTemplateDisplayOrder": "Display order", + "FormItemTemplateConfigurations": "Configurations", + "FormItemTemplateDeletionConfirmationMessage": "Are you sure to delete the form item {0}?", "FormItemTemplateDisabled": "Disabled", - "FormItemType.TextBox": "Text box", + "FormItemTemplateDisplayOrder": "Display order", + "FormItemTemplateGroup": "Group", + "FormItemTemplateInfoText": "Input info text", + "FormItemTemplateName": "Name", + "FormItemTemplateOptional": "Optional", + "FormItemTemplateType": "Type", "FormItemType.CheckBox": "Check box", - "FormItemType.OptionButtons": "Option buttons", + "FormItemType.ColorPicker": "Color picker", "FormItemType.DropdownListBox": "Drop-down list box", "FormItemType.FileBox": "File upload", - "CreateFormItemTemplate": "Create", - "EditFormItemTemplate": "Edit", - "FormItemTemplateDeletionConfirmationMessage": "Are you sure to delete the form item {0}?" + "FormItemType.NumberBox": "Number box", + "FormItemType.OptionButtons": "Option buttons", + "FormItemType.TextBox": "Text box", + "FormItemType.TimePicker": "Time picker", + "FormItemType.Toggle": "Toggle", + "FormTemplate": "Form template", + "FormTemplateCustomTag": "Custom tag", + "FormTemplateDeletionConfirmationMessage": "Are you sure to delete the form template {0}?", + "FormTemplateFormDefinitionName": "Definition name", + "FormTemplateFormItemTemplates": "Form items", + "FormTemplateName": "Name", + "Menu:DynamicForm": "Dynamic form", + "Menu:Form": "Form", + "Menu:FormTemplate": "Form template", + "Permission:Create": "Create", + "Permission:Delete": "Delete", + "Permission:Form": "Form", + "Permission:FormTemplate": "Form template", + "Permission:Manage": "Manage", + "Permission:Update": "Update", + "SuccessfullyDeleted": "Successfully deleted", + "TableFilter": "Table filter" } } \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/zh-Hans.json b/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/zh-Hans.json index b03a196..e6479e8 100644 --- a/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/zh-Hans.json +++ b/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/zh-Hans.json @@ -1,64 +1,74 @@ { "culture": "zh-Hans", "texts": { - "EasyAbp.DynamicForm:DuplicateFormItemTemplate": "表单项重复", + "CreateForm": "新建", + "CreateFormItemTemplate": "新建", + "CreateFormTemplate": "新建", + "EasyAbp.DynamicForm:ColorPickerInvalidHexValue": "{item}: 错误的 HEX 颜色代码格式", "EasyAbp.DynamicForm:DuplicateFormItem": "表单项重复", - "EasyAbp.DynamicForm:MissingFormItem": "{item}:缺少必须的表单项", - "EasyAbp.DynamicForm:InvalidFormItemValue": "{item}:不正确的表单项值", + "EasyAbp.DynamicForm:DuplicateFormItemTemplate": "表单项重复", + "EasyAbp.DynamicForm:FileBoxInvalidUrls": "{item}:不正确的文件地址格式", "EasyAbp.DynamicForm:FormItemValueIsRequired": "{item}:表单项的值不可为空", - "EasyAbp.DynamicForm:TextBoxInvalidMaxLength": "{item}:最大长度应大于最小长度", - "EasyAbp.DynamicForm:TextBoxInvalidRegexPattern": "{item}:不正确的正规表达式", - "EasyAbp.DynamicForm:TextBoxInvalidValueLength": "{item}的文本长度应在 {min} 到 {max} 字符之间", + "EasyAbp.DynamicForm:InvalidFormItemValue": "{item}:不正确的表单项值", + "EasyAbp.DynamicForm:InvalidRegexPattern": "{item}:不正确的正规表达式", + "EasyAbp.DynamicForm:MissingFormItem": "{item}:缺少必须的表单项", + "EasyAbp.DynamicForm:NumberBoxInvalidMaxValue": "{item}:最大值应大于最小值", + "EasyAbp.DynamicForm:NumberBoxInvalidNumericValue": "{item}:不正确的数字格式", + "EasyAbp.DynamicForm:NumberBoxInvalidValue": "{item}的值应在 {min} 到 {max} 之间;小数位数为 {decimalPlaces} 位", "EasyAbp.DynamicForm:OptionButtonsInvalidMaxSelection": "{item}:最大选择数量应小于最小选择数量", "EasyAbp.DynamicForm:OptionButtonsInvalidOptionQuantitySelected": "{item}的选择数量应在 {min} 个到 {max} 个之间", - "EasyAbp.DynamicForm:FileBoxInvalidUrls": "{item}:不正确的文件地址格式", - "Permission:Form": "表单", - "Permission:Manage": "管理", - "Permission:Create": "新建", - "Permission:Update": "编辑", - "Permission:Delete": "删除", - "Menu:DynamicForm": "动态表单", - "Menu:Form": "表单", + "EasyAbp.DynamicForm:TextBoxInvalidMaxLength": "{item}:最大长度应大于最小长度", + "EasyAbp.DynamicForm:TextBoxInvalidValueLength": "{item}的文本长度应在 {min} 到 {max} 字符之间", + "EasyAbp.DynamicForm:TimePickerInvalidDateTime": "{item}: 错误的日期时间格式", + "EasyAbp.DynamicForm:ToggleIsOptional": "{item}: 开关型表单项不能设置为可选的表单项", + "EditForm": "编辑", + "EditFormItemTemplate": "编辑", + "EditFormTemplate": "编辑", "Form": "表单", - "FormFormDefinitionName": "表单定义", "FormCreationTime": "创建时间", "FormCreator": "创建人", - "FormFormTemplateName": "表单模板名", + "FormDeletionConfirmationMessage": "确认删除表单 {0}?", + "FormFormDefinitionName": "表单定义", "FormFormItemTemplates": "表单项模板", "FormFormItems": "表单项", - "CreateForm": "新建", - "EditForm": "编辑", - "FormDeletionConfirmationMessage": "确认删除表单 {0}?", - "SuccessfullyDeleted": "删除成功", - "TableFilter": "表格过滤器", - "Permission:FormTemplate": "表单模板", - "Menu:FormTemplate": "表单模板", - "FormTemplate": "表单模板", - "FormTemplateFormDefinitionName": "表单定义", - "FormTemplateName": "名称", - "FormTemplateCustomTag": "自定义标签", - "FormTemplateFormItemTemplates": "表单项", - "CreateFormTemplate": "新建", - "EditFormTemplate": "编辑", - "FormTemplateDeletionConfirmationMessage": "确认删除表单模板 {0}?", + "FormFormTemplateName": "表单模板名", "FormItemTemplate": "表单项模板", - "FormItemTemplateName": "名称", - "FormItemTemplateGroup": "组名", - "FormItemTemplateInfoText": "输入信息提示", - "FormItemTemplateType": "类型", - "FormItemTemplateOptional": "选填", - "FormItemTemplateConfigurations": "配置", "FormItemTemplateAvailableValues": "可选值", "FormItemTemplateAvailableValuesInfo": "使用半角逗号(,)隔离可用值.", - "FormItemTemplateDisplayOrder": "显示排序", + "FormItemTemplateConfigurations": "配置", + "FormItemTemplateDeletionConfirmationMessage": "确认删除表单项 {0}?", "FormItemTemplateDisabled": "停用", - "FormItemType.TextBox": "文本框", + "FormItemTemplateDisplayOrder": "显示排序", + "FormItemTemplateGroup": "组名", + "FormItemTemplateInfoText": "输入信息提示", + "FormItemTemplateName": "名称", + "FormItemTemplateOptional": "选填", + "FormItemTemplateType": "类型", "FormItemType.CheckBox": "勾选框", - "FormItemType.OptionButtons": "单选按钮", + "FormItemType.ColorPicker": "颜色选择器", "FormItemType.DropdownListBox": "下拉列表框", "FormItemType.FileBox": "文件上传", - "CreateFormItemTemplate": "新建", - "EditFormItemTemplate": "编辑", - "FormItemTemplateDeletionConfirmationMessage": "确认删除表单项 {0}?" + "FormItemType.NumberBox": "数字框", + "FormItemType.OptionButtons": "单选按钮", + "FormItemType.TextBox": "文本框", + "FormItemType.TimePicker": "时间选择器", + "FormItemType.Toggle": "开关", + "FormTemplate": "表单模板", + "FormTemplateCustomTag": "自定义标签", + "FormTemplateDeletionConfirmationMessage": "确认删除表单模板 {0}?", + "FormTemplateFormDefinitionName": "表单定义", + "FormTemplateFormItemTemplates": "表单项", + "FormTemplateName": "名称", + "Menu:DynamicForm": "动态表单", + "Menu:Form": "表单", + "Menu:FormTemplate": "表单模板", + "Permission:Create": "新建", + "Permission:Delete": "删除", + "Permission:Form": "表单", + "Permission:FormTemplate": "表单模板", + "Permission:Manage": "管理", + "Permission:Update": "编辑", + "SuccessfullyDeleted": "删除成功", + "TableFilter": "表格过滤器" } } \ No newline at end of file diff --git a/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/zh-Hant.json b/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/zh-Hant.json index 1dc21c6..183ae89 100644 --- a/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/zh-Hant.json +++ b/src/EasyAbp.DynamicForm.Domain.Shared/EasyAbp/DynamicForm/Localization/zh-Hant.json @@ -1,64 +1,74 @@ { "culture": "zh-Hant", "texts": { - "EasyAbp.DynamicForm:DuplicateFormItemTemplate": "表單項重複", + "CreateForm": "新建", + "CreateFormItemTemplate": "新建", + "CreateFormTemplate": "新建", + "EasyAbp.DynamicForm:ColorPickerInvalidHexValue": "{item}: 錯誤的 HEX 顏色代碼格式", "EasyAbp.DynamicForm:DuplicateFormItem": "表單項重複", - "EasyAbp.DynamicForm:MissingFormItem": "{item}:缺少必須的表單項", - "EasyAbp.DynamicForm:InvalidFormItemValue": "{item}:不正確的表單項值", + "EasyAbp.DynamicForm:DuplicateFormItemTemplate": "表單項重複", + "EasyAbp.DynamicForm:FileBoxInvalidUrls": "{item}:不正確的文件地址格式", "EasyAbp.DynamicForm:FormItemValueIsRequired": "{item}:表單項的值不可為空", - "EasyAbp.DynamicForm:TextBoxInvalidMaxLength": "{item}:最大長度應大於最小長度", - "EasyAbp.DynamicForm:TextBoxInvalidRegexPattern": "{item}:不正確的正規表達式", - "EasyAbp.DynamicForm:TextBoxInvalidValueLength": "{item}的文本長度應在 {min} 到 {max} 字符之間", + "EasyAbp.DynamicForm:InvalidFormItemValue": "{item}:不正確的表單項值", + "EasyAbp.DynamicForm:InvalidRegexPattern": "{item}:不正確的正規表達式", + "EasyAbp.DynamicForm:MissingFormItem": "{item}:缺少必須的表單項", + "EasyAbp.DynamicForm:NumberBoxInvalidMaxValue": "{item}:最大值應大於最小值", + "EasyAbp.DynamicForm:NumberBoxInvalidNumericValue": "{item}:不正確的數字格式", + "EasyAbp.DynamicForm:NumberBoxInvalidValue": "{item}的值應在 {min} 到 {max} 之間;小數位數為 {decimalPlaces} 位", "EasyAbp.DynamicForm:OptionButtonsInvalidMaxSelection": "{item}:最大選擇數量應小於最小選擇數量", "EasyAbp.DynamicForm:OptionButtonsInvalidOptionQuantitySelected": "{item}的選擇數量應在 {min} 個到 {max} 個之間", - "EasyAbp.DynamicForm:FileBoxInvalidUrls": "{item}:不正確的文件地址格式", - "Permission:Form": "表單", - "Permission:Manage": "管理", - "Permission:Create": "新建", - "Permission:Update": "編輯", - "Permission:Delete": "刪除", - "Menu:DynamicForm": "動態表單", - "Menu:Form": "表單", + "EasyAbp.DynamicForm:TextBoxInvalidMaxLength": "{item}:最大長度應大於最小長度", + "EasyAbp.DynamicForm:TextBoxInvalidValueLength": "{item}的文本長度應在 {min} 到 {max} 字符之間", + "EasyAbp.DynamicForm:TimePickerInvalidDateTime": "{item}: 錯誤的日期時間格式", + "EasyAbp.DynamicForm:ToggleIsOptional": "{item}: 開關型表單項目不能設定為可選的表單項目", + "EditForm": "編輯", + "EditFormItemTemplate": "編輯", + "EditFormTemplate": "編輯", "Form": "表單", - "FormFormDefinitionName": "表單定義", "FormCreationTime": "創建時間", "FormCreator": "創建人", - "FormFormTemplateName": "表單模板名", + "FormDeletionConfirmationMessage": "確認刪除表單 {0}?", + "FormFormDefinitionName": "表單定義", "FormFormItemTemplates": "表單項模板", "FormFormItems": "表單項", - "CreateForm": "新建", - "EditForm": "編輯", - "FormDeletionConfirmationMessage": "確認刪除表單 {0}?", - "SuccessfullyDeleted": "刪除成功", - "TableFilter": "表格過濾器", - "Permission:FormTemplate": "表單模板", - "Menu:FormTemplate": "表單模板", - "FormTemplate": "表單模板", - "FormTemplateFormDefinitionName": "表單定義", - "FormTemplateName": "名稱", - "FormTemplateCustomTag": "自定義標籤", - "FormTemplateFormItemTemplates": "表單項", - "CreateFormTemplate": "新建", - "EditFormTemplate": "編輯", - "FormTemplateDeletionConfirmationMessage": "確認刪除表單模板 {0}?", + "FormFormTemplateName": "表單模板名", "FormItemTemplate": "表單項模板", - "FormItemTemplateName": "名稱", - "FormItemTemplateGroup": "組名", - "FormItemTemplateInfoText": "輸入信息提示", - "FormItemTemplateType": "類型", - "FormItemTemplateOptional": "選填", - "FormItemTemplateConfigurations": "配置", "FormItemTemplateAvailableValues": "可選值", "FormItemTemplateAvailableValuesInfo": "使用半角逗號(,)隔離可用值.", - "FormItemTemplateDisplayOrder": "顯示排序", + "FormItemTemplateConfigurations": "配置", + "FormItemTemplateDeletionConfirmationMessage": "確認刪除表單项 {0}?", "FormItemTemplateDisabled": "停用", - "FormItemType.TextBox": "文本框", + "FormItemTemplateDisplayOrder": "顯示排序", + "FormItemTemplateGroup": "組名", + "FormItemTemplateInfoText": "輸入信息提示", + "FormItemTemplateName": "名稱", + "FormItemTemplateOptional": "選填", + "FormItemTemplateType": "類型", "FormItemType.CheckBox": "勾選框", - "FormItemType.OptionButtons": "單選按鈕", + "FormItemType.ColorPicker": "顏色選擇器", "FormItemType.DropdownListBox": "下拉列錶框", "FormItemType.FileBox": "文件上傳", - "CreateFormItemTemplate": "新建", - "EditFormItemTemplate": "編輯", - "FormItemTemplateDeletionConfirmationMessage": "確認刪除表單项 {0}?" + "FormItemType.NumberBox": "數字框", + "FormItemType.OptionButtons": "單選按鈕", + "FormItemType.TextBox": "文本框", + "FormItemType.TimePicker": "時間選擇器", + "FormItemType.Toggle": "開關", + "FormTemplate": "表單模板", + "FormTemplateCustomTag": "自定義標籤", + "FormTemplateDeletionConfirmationMessage": "確認刪除表單模板 {0}?", + "FormTemplateFormDefinitionName": "表單定義", + "FormTemplateFormItemTemplates": "表單項", + "FormTemplateName": "名稱", + "Menu:DynamicForm": "動態表單", + "Menu:Form": "表單", + "Menu:FormTemplate": "表單模板", + "Permission:Create": "新建", + "Permission:Delete": "刪除", + "Permission:Form": "表單", + "Permission:FormTemplate": "表單模板", + "Permission:Manage": "管理", + "Permission:Update": "編輯", + "SuccessfullyDeleted": "刪除成功", + "TableFilter": "表格過濾器" } } \ No newline at end of file diff --git a/test/EasyAbp.DynamicForm.Domain.Tests/DynamicFormCore/FormItemTemplateTests.cs b/test/EasyAbp.DynamicForm.Domain.Tests/DynamicFormCore/FormItemTemplateTests.cs index d9bffe2..e51915f 100644 --- a/test/EasyAbp.DynamicForm.Domain.Tests/DynamicFormCore/FormItemTemplateTests.cs +++ b/test/EasyAbp.DynamicForm.Domain.Tests/DynamicFormCore/FormItemTemplateTests.cs @@ -2,10 +2,15 @@ using System.Threading.Tasks; using EasyAbp.DynamicForm.BookRentals; using EasyAbp.DynamicForm.FormItemTypes; +using EasyAbp.DynamicForm.FormItemTypes.ColorPicker; using EasyAbp.DynamicForm.FormItemTypes.FileBox; +using EasyAbp.DynamicForm.FormItemTypes.NumberBox; using EasyAbp.DynamicForm.FormItemTypes.OptionButtons; using EasyAbp.DynamicForm.FormItemTypes.TextBox; +using EasyAbp.DynamicForm.FormItemTypes.TimePicker; +using EasyAbp.DynamicForm.FormItemTypes.Toggle; using EasyAbp.DynamicForm.FormTemplates; +using EasyAbp.DynamicForm.Shared; using Shouldly; using Volo.Abp; using Volo.Abp.Json; @@ -86,7 +91,107 @@ public async Task Should_Validate_TextBox() (await Should.ThrowAsync(() => _formTemplateManager.AddFormItemAsync( formTemplate, "Content", "group1", null, TextBoxFormItemProvider.Name, false, _jsonSerializer.Serialize(configurations), null, 0, false))) - .Code.ShouldBe("EasyAbp.DynamicForm:TextBoxInvalidRegexPattern"); + .Code.ShouldBe("EasyAbp.DynamicForm:InvalidRegexPattern"); + } + + [Fact] + public async Task Should_Validate_NumberBox() + { + var formTemplate = await CreateFormTemplateAsync(); + var provider = GetFormItemProvider(NumberBoxFormItemProvider.Name); + var configurations = (NumberBoxFormItemConfigurations)await provider.CreateConfigurationsObjectOrNullAsync(); + + configurations!.MinValue = 2; + configurations!.MaxValue = 1; // should not be less than the min value + + (await Should.ThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, NumberBoxFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations), null, 0, false))) + .Code.ShouldBe("EasyAbp.DynamicForm:NumberBoxInvalidMaxValue"); + + configurations!.MaxValue = 3; + + await Should.NotThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, NumberBoxFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations), null, 0, false)); + + (await Should.ThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, NumberBoxFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations), ["abc"], 0, false))) // abc is not a numeric value + .Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); + } + + [Fact] + public async Task Should_Validate_Toggle() + { + var formTemplate = await CreateFormTemplateAsync(); + var provider = GetFormItemProvider(ToggleFormItemProvider.Name); + var configurations = (ToggleFormItemConfigurations)await provider.CreateConfigurationsObjectOrNullAsync(); + + const bool optional = true; // cannot be optional. + + (await Should.ThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, ToggleFormItemProvider.Name, optional, + _jsonSerializer.Serialize(configurations!), null, 0, false))) + .Code.ShouldBe("EasyAbp.DynamicForm:ToggleIsOptional"); + + AvailableValues availableValues = ["abc"]; // valid available values: true, false. + + (await Should.ThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, ToggleFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations!), availableValues, 0, false))) // abc is not a numeric value + .Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); + + await Should.NotThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, ToggleFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations!), null, 0, false)); + } + + [Fact] + public async Task Should_Validate_TimePicker() + { + var formTemplate = await CreateFormTemplateAsync(); + var provider = GetFormItemProvider(TimePickerFormItemProvider.Name); + var configurations = (TimePickerFormItemConfigurations)await provider.CreateConfigurationsObjectOrNullAsync(); + + AvailableValues availableValues = ["abc"]; // invalid date time value + + (await Should.ThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, TimePickerFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations!), availableValues, 0, false))) // abc is not a numeric value + .Code.ShouldBe("EasyAbp.DynamicForm:TimePickerInvalidDateTime"); + + await Should.NotThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, TimePickerFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations!), null, 0, false)); + } + + [Fact] + public async Task Should_Validate_ColorPicker() + { + var formTemplate = await CreateFormTemplateAsync(); + var provider = GetFormItemProvider(ColorPickerFormItemProvider.Name); + var configurations = (ColorPickerFormItemConfigurations)await provider.CreateConfigurationsObjectOrNullAsync(); + + AvailableValues availableValues = ["abc"]; // invalid color value + + (await Should.ThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, ColorPickerFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations!), availableValues, 0, false))) // abc is not a numeric value + .Code.ShouldBe("EasyAbp.DynamicForm:ColorPickerInvalidHexValue"); + + configurations!.RegexPattern = "*****"; // invalid regex pattern + + (await Should.ThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, ColorPickerFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations!), null, 0, false))) // abc is not a numeric value + .Code.ShouldBe("EasyAbp.DynamicForm:InvalidRegexPattern"); + + configurations!.RegexPattern = null; + + await Should.NotThrowAsync(() => _formTemplateManager.AddFormItemAsync( + formTemplate, "Content", "group1", null, ColorPickerFormItemProvider.Name, false, + _jsonSerializer.Serialize(configurations!), null, 0, false)); } [Fact] diff --git a/test/EasyAbp.DynamicForm.Domain.Tests/DynamicFormCore/FormItemTests.cs b/test/EasyAbp.DynamicForm.Domain.Tests/DynamicFormCore/FormItemTests.cs index df57178..22d25d2 100644 --- a/test/EasyAbp.DynamicForm.Domain.Tests/DynamicFormCore/FormItemTests.cs +++ b/test/EasyAbp.DynamicForm.Domain.Tests/DynamicFormCore/FormItemTests.cs @@ -34,6 +34,10 @@ await Should.NotThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( new("Gender", "Male"), new("Requirements", "Urgent"), new("Images", "[]"), + new("Score", "100"), + new("VIP", "false"), + new("Birthday", "2024-08-21"), + new("Color", "#FFAABBCC") })); } @@ -104,6 +108,155 @@ await Should.NotThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); } + [Fact] + public async Task Should_Validate_NumberBox() + { + var formTemplate = await _formTemplateRepository.GetAsync(DynamicFormTestConsts.FormTemplate1Id); + + var formItems = FormItemTestHelper.CreateStandardFormItems(); + + formItems.First(x => x.Name == "Score").Value = null; // "Score" is not optional + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:FormItemValueIsRequired"); + + var numberBoxFormItemTemplate = formTemplate.FormItemTemplates.First(x => x.Name == "Score"); + numberBoxFormItemTemplate.GetType().GetProperty("Optional")!.SetValue(numberBoxFormItemTemplate, true); + + await Should.NotThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems)); // "Score" is null but optional + + numberBoxFormItemTemplate.GetType().GetProperty("Optional")!.SetValue(numberBoxFormItemTemplate, false); + + formItems.First(x => x.Name == "Score").Value = "9"; // too small + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:NumberBoxInvalidValue"); + + formItems.First(x => x.Name == "Score").Value = "999"; // too large + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:NumberBoxInvalidValue"); + + formItems.First(x => x.Name == "Score").Value = "100.111"; // decimal places > 2 + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:NumberBoxInvalidValue"); + + formItems.First(x => x.Name == "Score").Value = "101"; // not in available values + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); + + formItems.First(x => x.Name == "Score").Value = "100.11"; + + await Should.NotThrowAsync(() => + _dynamicFormValidator.ValidateValuesAsync(formTemplate.FormItemTemplates, formItems)); + } + + [Fact] + public async Task Should_Validate_Toggle() + { + var formTemplate = await _formTemplateRepository.GetAsync(DynamicFormTestConsts.FormTemplate1Id); + + var formItems = FormItemTestHelper.CreateStandardFormItems(); + + formItems.First(x => x.Name == "VIP").Value = null; // "VIP" is not optional + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:FormItemValueIsRequired"); + + formItems.First(x => x.Name == "VIP").Value = "abc"; // not bool value + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); + + formItems.First(x => x.Name == "VIP").Value = "true"; // not in available values + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); + + formItems.First(x => x.Name == "VIP").Value = "false"; + + await Should.NotThrowAsync(() => + _dynamicFormValidator.ValidateValuesAsync(formTemplate.FormItemTemplates, formItems)); + } + + [Fact] + public async Task Should_Validate_TimePicker() + { + var formTemplate = await _formTemplateRepository.GetAsync(DynamicFormTestConsts.FormTemplate1Id); + + var formItems = FormItemTestHelper.CreateStandardFormItems(); + + formItems.First(x => x.Name == "Birthday").Value = null; // "Birthday" is not optional + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:FormItemValueIsRequired"); + + var timePickerFormItemTemplate = formTemplate.FormItemTemplates.First(x => x.Name == "Birthday"); + timePickerFormItemTemplate.GetType().GetProperty("Optional")!.SetValue(timePickerFormItemTemplate, true); + + await Should.NotThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems)); // "Birthday" is null but optional + + formItems.First(x => x.Name == "Birthday").Value = "abc"; // invalid date time value + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:TimePickerInvalidDateTime"); + + formItems.First(x => x.Name == "Birthday").Value = "2024-08-20"; // not in available values + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); + + formItems.First(x => x.Name == "Birthday").Value = "2024-08-21"; + + await Should.NotThrowAsync(() => + _dynamicFormValidator.ValidateValuesAsync(formTemplate.FormItemTemplates, formItems)); + } + + [Fact] + public async Task Should_Validate_ColorPicker() + { + var formTemplate = await _formTemplateRepository.GetAsync(DynamicFormTestConsts.FormTemplate1Id); + + var formItems = FormItemTestHelper.CreateStandardFormItems(); + + formItems.First(x => x.Name == "Color").Value = null; // "Color" is not optional + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:FormItemValueIsRequired"); + + var colorPickerFormItemTemplate = formTemplate.FormItemTemplates.First(x => x.Name == "Color"); + colorPickerFormItemTemplate.GetType().GetProperty("Optional")!.SetValue(colorPickerFormItemTemplate, true); + + await Should.NotThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems)); // "Color" is null but optional + + formItems.First(x => x.Name == "Color").Value = "abc"; // invalid HEX value + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code + .ShouldBe("EasyAbp.DynamicForm:ColorPickerInvalidHexValue"); + + formItems.First(x => x.Name == "Color").Value = + "#000"; // should not contain 0-9 (Color property regex: "^[^0-9]*$") + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); + + formItems.First(x => x.Name == "Color").Value = "#fff"; // not in available values + + (await Should.ThrowAsync(() => _dynamicFormValidator.ValidateValuesAsync( + formTemplate.FormItemTemplates, formItems))).Code.ShouldBe("EasyAbp.DynamicForm:InvalidFormItemValue"); + + formItems.First(x => x.Name == "Color").Value = "#FFAABBCC"; + + await Should.NotThrowAsync(() => + _dynamicFormValidator.ValidateValuesAsync(formTemplate.FormItemTemplates, formItems)); + } + [Fact] public async Task Should_Validate_OptionButtons() { diff --git a/test/EasyAbp.DynamicForm.TestBase/DynamicFormDataSeedContributor.cs b/test/EasyAbp.DynamicForm.TestBase/DynamicFormDataSeedContributor.cs index c765b2f..e7a3c79 100644 --- a/test/EasyAbp.DynamicForm.TestBase/DynamicFormDataSeedContributor.cs +++ b/test/EasyAbp.DynamicForm.TestBase/DynamicFormDataSeedContributor.cs @@ -1,8 +1,12 @@ using System.Threading.Tasks; using EasyAbp.DynamicForm.FormItemTypes; +using EasyAbp.DynamicForm.FormItemTypes.ColorPicker; using EasyAbp.DynamicForm.FormItemTypes.FileBox; +using EasyAbp.DynamicForm.FormItemTypes.NumberBox; using EasyAbp.DynamicForm.FormItemTypes.OptionButtons; using EasyAbp.DynamicForm.FormItemTypes.TextBox; +using EasyAbp.DynamicForm.FormItemTypes.TimePicker; +using EasyAbp.DynamicForm.FormItemTypes.Toggle; using EasyAbp.DynamicForm.FormTemplates; using EasyAbp.DynamicForm.Shared; using Volo.Abp.Data; @@ -48,6 +52,10 @@ private async Task SeedFormTemplatesAsync() var textBoxFormItemProvider = _formItemProviderResolver.Resolve(TextBoxFormItemProvider.Name); var optionButtonsFormItemProvider = _formItemProviderResolver.Resolve(OptionButtonsFormItemProvider.Name); var fileBoxFormItemProvider = _formItemProviderResolver.Resolve(FileBoxFormItemProvider.Name); + var numberBoxFormItemProvider = _formItemProviderResolver.Resolve(NumberBoxFormItemProvider.Name); + var toggleFormItemProvider = _formItemProviderResolver.Resolve(ToggleFormItemProvider.Name); + var timePickerFormItemProvider = _formItemProviderResolver.Resolve(TimePickerFormItemProvider.Name); + var colorPickerFormItemProvider = _formItemProviderResolver.Resolve(ColorPickerFormItemProvider.Name); var formTemplate1 = await _formTemplateManager.CreateAsync(DynamicFormTestConsts.TestFormDefinitionName, DynamicFormTestConsts.FormTemplate1Name, "my-custom-tag"); @@ -74,7 +82,7 @@ await _formTemplateManager.AddFormItemAsync( await _formTemplateManager.AddFormItemAsync( formTemplate1, "Dept", "group1", "Your department name.", TextBoxFormItemProvider.Name, true, - _jsonSerializer.Serialize(formItemTemplate2Configurations), new AvailableValues { "Dept 1", "Dept 2" }, 1, + _jsonSerializer.Serialize(formItemTemplate2Configurations!), ["Dept 1", "Dept 2"], 1, false); var formItemTemplate3Configurations = @@ -83,7 +91,7 @@ await _formTemplateManager.AddFormItemAsync( await _formTemplateManager.AddFormItemAsync( formTemplate1, "Gender", "group1", "Your gender.", OptionButtonsFormItemProvider.Name, false, - _jsonSerializer.Serialize(formItemTemplate3Configurations), new AvailableValues { "Male", "Female" }, 2, + _jsonSerializer.Serialize(formItemTemplate3Configurations!), ["Male", "Female"], 2, false); var formItemTemplate4Configurations = @@ -96,10 +104,8 @@ await _formTemplateManager.AddFormItemAsync( await _formTemplateManager.AddFormItemAsync( formTemplate1, "Requirements", "group1", "Other requirements.", OptionButtonsFormItemProvider.Name, true, - _jsonSerializer.Serialize(formItemTemplate4Configurations), new AvailableValues - { - "Use annual leave", "Urgent", "Remote standby" - }, 3, false); + _jsonSerializer.Serialize(formItemTemplate4Configurations), + ["Use annual leave", "Urgent", "Remote standby"], 3, false); var formItemTemplate5Configurations = @@ -113,6 +119,43 @@ await _formTemplateManager.AddFormItemAsync( formTemplate1, "Images", "group1", "Images upload.", FileBoxFormItemProvider.Name, true, _jsonSerializer.Serialize(formItemTemplate5Configurations), null, 4, false); + var formItemTemplate6Configurations = + (NumberBoxFormItemConfigurations)await numberBoxFormItemProvider.CreateConfigurationsObjectOrNullAsync(); + + formItemTemplate6Configurations!.MinValue = 10; + formItemTemplate6Configurations!.MaxValue = 120; + formItemTemplate6Configurations!.DecimalPlaces = 2; + + await _formTemplateManager.AddFormItemAsync( + formTemplate1, "Score", "group1", "Your score.", NumberBoxFormItemProvider.Name, false, + _jsonSerializer.Serialize(formItemTemplate6Configurations), ["100", "100.1", "100.11"], 0, false); + + var formItemTemplate7Configurations = + (ToggleFormItemConfigurations)await toggleFormItemProvider.CreateConfigurationsObjectOrNullAsync(); + + await _formTemplateManager.AddFormItemAsync( + formTemplate1, "VIP", "group1", "Are you a VIP?", ToggleFormItemProvider.Name, false, + _jsonSerializer.Serialize(formItemTemplate7Configurations!), ["false"], 0, false); + + var formItemTemplate8Configurations = + (TimePickerFormItemConfigurations)await timePickerFormItemProvider.CreateConfigurationsObjectOrNullAsync(); + + formItemTemplate8Configurations!.TimeDimension = TimeDimension.Day; + + await _formTemplateManager.AddFormItemAsync( + formTemplate1, "Birthday", "group1", "Your birthday", TimePickerFormItemProvider.Name, false, + _jsonSerializer.Serialize(formItemTemplate8Configurations!), ["2024-08-21"], 0, false); + await _formTemplateRepository.InsertAsync(formTemplate1, true); + + var formItemTemplate9Configurations = + (ColorPickerFormItemConfigurations)await colorPickerFormItemProvider + .CreateConfigurationsObjectOrNullAsync(); + + formItemTemplate9Configurations!.RegexPattern = "^[^0-9]*$"; // should not contain 0-9 + + await _formTemplateManager.AddFormItemAsync( + formTemplate1, "Color", "group1", "Your favorite color", ColorPickerFormItemProvider.Name, false, + _jsonSerializer.Serialize(formItemTemplate9Configurations!), ["#FFAABBCC", "#000"], 0, false); } } \ No newline at end of file diff --git a/test/EasyAbp.DynamicForm.TestBase/DynamicFormTestBaseModule.cs b/test/EasyAbp.DynamicForm.TestBase/DynamicFormTestBaseModule.cs index 40a920c..34e0a9b 100644 --- a/test/EasyAbp.DynamicForm.TestBase/DynamicFormTestBaseModule.cs +++ b/test/EasyAbp.DynamicForm.TestBase/DynamicFormTestBaseModule.cs @@ -26,13 +26,6 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.AddOrUpdateFormDefinition(new FormDefinition( DynamicFormTestConsts.TestFormDefinitionName, DynamicFormTestConsts.TestFormDefinitionDisplayName)); }); - - Configure(options => - { - options.AddTextBoxFormItemType(); - options.AddOptionButtonsFormItemType(); - options.AddFileBoxFormItemType(); - }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/test/EasyAbp.DynamicForm.TestBase/FormItemTestHelper.cs b/test/EasyAbp.DynamicForm.TestBase/FormItemTestHelper.cs index 03ae0c0..6b499e6 100644 --- a/test/EasyAbp.DynamicForm.TestBase/FormItemTestHelper.cs +++ b/test/EasyAbp.DynamicForm.TestBase/FormItemTestHelper.cs @@ -15,6 +15,10 @@ public static List CreateStandardFormItems() new("Gender", "Male"), new("Requirements", "Use annual leave,Urgent"), new("Images", "[\"https://my-fake-site1.com/1.png\", \"https://my-fake-site1.com/2.png\"]"), + new("Score", "100.1"), + new("VIP", "false"), + new("Color", "#FFAABBCC"), + new("Birthday", "2024-08-21"), }; } } \ No newline at end of file