diff --git a/16/umbraco-forms/SUMMARY.md b/16/umbraco-forms/SUMMARY.md index 4af4cd066de..cc00ceb1d9a 100644 --- a/16/umbraco-forms/SUMMARY.md +++ b/16/umbraco-forms/SUMMARY.md @@ -50,6 +50,7 @@ * [Adding A Field Type To Umbraco Forms](developer/extending/adding-a-fieldtype.md) * [Excluding a built-in field](developer/extending/excluding-a-built-in-field.md) * [Adding A Prevalue Source Type To Umbraco Forms](developer/extending/adding-a-prevaluesourcetype.md) + * [Adding a UI Builder repository as a prevalue source](developer/extending/uibuilder-repository-prevalue.md) * [Adding A Workflow Type To Umbraco Forms](developer/extending/adding-a-workflowtype.md) * [Adding An Export Type To Umbraco Forms](developer/extending/adding-a-exporttype.md) * [Adding a Magic String Format Function](developer/extending/adding-a-magic-string-format-function.md) diff --git a/16/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-configuration.png b/16/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-configuration.png new file mode 100644 index 00000000000..b84a2ee51ea Binary files /dev/null and b/16/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-configuration.png differ diff --git a/16/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-source.png b/16/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-source.png new file mode 100644 index 00000000000..e75a7ce431b Binary files /dev/null and b/16/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-source.png differ diff --git a/16/umbraco-forms/developer/extending/uibuilder-repository-prevalue.md b/16/umbraco-forms/developer/extending/uibuilder-repository-prevalue.md new file mode 100644 index 00000000000..f2be7ce77b6 --- /dev/null +++ b/16/umbraco-forms/developer/extending/uibuilder-repository-prevalue.md @@ -0,0 +1,228 @@ +# Adding a UI Builder repository as a prevalue source + +When using [Umbraco UI Builder](https://docs.umbraco.com/umbraco-ui-builder/) alongside Umbraco Forms, you can use a configured UI Builder repository as a prevalue source. + +To do this, you will need to create a custom `FieldPreValueSourceType` source that uses UI Builder's `SectionConfiguration` property editor. Once configured, users will be able to select a repository and fetch the prevalues from there. + +## Example + +The following class shows how to create a `UIBuilderRepository` prevalue source type. + +```csharp +using System.Text.Json; +using System.Text.Json.Serialization; +using Umbraco.Cms.Core.Models; +using Umbraco.Forms.Core; +using Umbraco.Forms.Core.Attributes; +using Umbraco.Forms.Core.Models; +using Umbraco.UIBuilder.Configuration; +using Umbraco.UIBuilder.Services; + +namespace MyFormsExtensions +{ + public class UIBuilderRepository : FieldPreValueSourceType + { + private readonly UIBuilderConfig _config; + private readonly EntityService _entityService; + + public UIBuilderRepository( + UIBuilderConfig config, + EntityService entityService) + { + Id = new Guid("ED56A31B-56FD-41EA-9D21-755750879D13"); + Name = "UI Builder Repository"; + Alias = "uiBuilderRepository"; + Description = "Use a UI Builder repository as a prevalue source."; + Icon = "icon-file-cabinet"; + + _config = config; + _entityService = entityService; + } + + [Setting("Source", Description = "Select the source section, collection and data view", View = "UiBuilder.PropertyEditorUi.EntityPicker.SectionConfiguration", IsMandatory = true, DisplayOrder = 10)] + public virtual string Source { get; set; } = string.Empty; + + public override Task> GetPreValuesAsync(Field? field, Form? form) + { + var result = new List(); + + UIBuilderSectionConfiguration? configuration = ParseSourceConfiguration(); + if (configuration != null) + { + if (!string.IsNullOrEmpty(configuration.Collection)) + { + CollectionConfig collectionConfig = _config.Collections[configuration.Collection]; + IEnumerable entities; + + if (string.IsNullOrEmpty(configuration.DataView)) + { + entities = _entityService.GetAllEntities(collectionConfig); + } + else + { + var allEntities = new List(); + const int pageSize = 100; + int pageNumber = 1; + long totalPages = 0; + + while (pageNumber == 1 || pageNumber <= totalPages) + { + PagedResult pagedResult = _entityService.FindEntities( + collectionConfig, + pageNumber: pageNumber, + pageSize: pageSize, + dataViewAlias: configuration.DataView); + + if (pagedResult.Items != null) + { + allEntities.AddRange(pagedResult.Items); + } + + if (pageNumber == 1) + { + totalPages = pagedResult.TotalPages; + } + + pageNumber++; + } + + entities = allEntities; + } + + foreach (var entity in entities) + { + var id = GetPropertyValue(collectionConfig.IdProperty, entity); + var name = GetPropertyValue(collectionConfig.NameProperty, entity); + + result.Add(new PreValue() + { + Id = id?.ToString() ?? string.Empty, + Caption = name?.ToString() ?? string.Empty, + Value = id?.ToString() ?? string.Empty + }); + } + } + } + + result = result.OrderBy(x => x.Id).ToList(); + return Task.FromResult(result); + } + + public override List ValidateSettings() + { + var exceptions = new List(); + + UIBuilderSectionConfiguration? configuration = ParseSourceConfiguration(); + if (configuration != null) + { + if (string.IsNullOrEmpty(configuration.Section)) + { + exceptions.Add(new Exception("'Section' setting has not been set")); + } + + if (string.IsNullOrEmpty(configuration.Collection)) + { + exceptions.Add(new Exception("'Collection' setting has not been set")); + } + } + + return exceptions; + } + + private UIBuilderSectionConfiguration? ParseSourceConfiguration() + { + if (string.IsNullOrEmpty(Source)) + { + return null; + } + + List? config = JsonSerializer.Deserialize>(Source); + var result = new UIBuilderSectionConfiguration(); + + if (config != null) + { + foreach (UIBuilderSectionConfigItem item in config) + { + switch (item.Alias) + { + case "section": + result.Section = item.Value; + break; + case "collection": + result.Collection = item.Value; + break; + case "dataView": + result.DataView = item.Value; + break; + case "labels": + if (!string.IsNullOrEmpty(item.Value)) + { + result.Labels = JsonSerializer.Deserialize(item.Value); + } + break; + } + } + } + + return result; + } + + private static object? GetPropertyValue(PropertyConfig? propertyConfig, object entity) + { + if (propertyConfig == null) + { + return null; + } + + return propertyConfig.PropertyGetter?.Invoke(entity) ?? propertyConfig.PropertyExpression.Compile().DynamicInvoke(entity); + } + } + + internal class UIBuilderSectionConfigItem + { + [JsonPropertyName("alias")] + public string? Alias { get; set; } + + [JsonPropertyName("value")] + public string? Value { get; set; } + } + + internal class UIBuilderSectionConfiguration + { + public string? Section { get; set; } + public string? Collection { get; set; } + public string? DataView { get; set; } + public UIBuilderLabels? Labels { get; set; } + } + + internal class UIBuilderLabels + { + [JsonPropertyName("plural")] + public string? Plural { get; set; } + + [JsonPropertyName("singular")] + public string? Singular { get; set; } + } +} +``` + +And registered with: + +```csharp +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.WithCollectionBuilder() + .Add(); + } +} +``` + +This will then make the "UI Builder Repository" available when creating a new prevalue source: + +![UI Builder Repository available as a prevalue source](./images/uibuilder-repository-prevalue-source.png) + +Once selected, a user can pick a section, collection, and data view from the configured UI Builder repositories: + +![Selecting a UI Builder repository as a prevalue source](./images/uibuilder-repository-prevalue-configuration.png) diff --git a/17/umbraco-forms/SUMMARY.md b/17/umbraco-forms/SUMMARY.md index 4af4cd066de..cc00ceb1d9a 100644 --- a/17/umbraco-forms/SUMMARY.md +++ b/17/umbraco-forms/SUMMARY.md @@ -50,6 +50,7 @@ * [Adding A Field Type To Umbraco Forms](developer/extending/adding-a-fieldtype.md) * [Excluding a built-in field](developer/extending/excluding-a-built-in-field.md) * [Adding A Prevalue Source Type To Umbraco Forms](developer/extending/adding-a-prevaluesourcetype.md) + * [Adding a UI Builder repository as a prevalue source](developer/extending/uibuilder-repository-prevalue.md) * [Adding A Workflow Type To Umbraco Forms](developer/extending/adding-a-workflowtype.md) * [Adding An Export Type To Umbraco Forms](developer/extending/adding-a-exporttype.md) * [Adding a Magic String Format Function](developer/extending/adding-a-magic-string-format-function.md) diff --git a/17/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-configuration.png b/17/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-configuration.png new file mode 100644 index 00000000000..b84a2ee51ea Binary files /dev/null and b/17/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-configuration.png differ diff --git a/17/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-source.png b/17/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-source.png new file mode 100644 index 00000000000..e75a7ce431b Binary files /dev/null and b/17/umbraco-forms/developer/extending/images/uibuilder-repository-prevalue-source.png differ diff --git a/17/umbraco-forms/developer/extending/uibuilder-repository-prevalue.md b/17/umbraco-forms/developer/extending/uibuilder-repository-prevalue.md new file mode 100644 index 00000000000..f2be7ce77b6 --- /dev/null +++ b/17/umbraco-forms/developer/extending/uibuilder-repository-prevalue.md @@ -0,0 +1,228 @@ +# Adding a UI Builder repository as a prevalue source + +When using [Umbraco UI Builder](https://docs.umbraco.com/umbraco-ui-builder/) alongside Umbraco Forms, you can use a configured UI Builder repository as a prevalue source. + +To do this, you will need to create a custom `FieldPreValueSourceType` source that uses UI Builder's `SectionConfiguration` property editor. Once configured, users will be able to select a repository and fetch the prevalues from there. + +## Example + +The following class shows how to create a `UIBuilderRepository` prevalue source type. + +```csharp +using System.Text.Json; +using System.Text.Json.Serialization; +using Umbraco.Cms.Core.Models; +using Umbraco.Forms.Core; +using Umbraco.Forms.Core.Attributes; +using Umbraco.Forms.Core.Models; +using Umbraco.UIBuilder.Configuration; +using Umbraco.UIBuilder.Services; + +namespace MyFormsExtensions +{ + public class UIBuilderRepository : FieldPreValueSourceType + { + private readonly UIBuilderConfig _config; + private readonly EntityService _entityService; + + public UIBuilderRepository( + UIBuilderConfig config, + EntityService entityService) + { + Id = new Guid("ED56A31B-56FD-41EA-9D21-755750879D13"); + Name = "UI Builder Repository"; + Alias = "uiBuilderRepository"; + Description = "Use a UI Builder repository as a prevalue source."; + Icon = "icon-file-cabinet"; + + _config = config; + _entityService = entityService; + } + + [Setting("Source", Description = "Select the source section, collection and data view", View = "UiBuilder.PropertyEditorUi.EntityPicker.SectionConfiguration", IsMandatory = true, DisplayOrder = 10)] + public virtual string Source { get; set; } = string.Empty; + + public override Task> GetPreValuesAsync(Field? field, Form? form) + { + var result = new List(); + + UIBuilderSectionConfiguration? configuration = ParseSourceConfiguration(); + if (configuration != null) + { + if (!string.IsNullOrEmpty(configuration.Collection)) + { + CollectionConfig collectionConfig = _config.Collections[configuration.Collection]; + IEnumerable entities; + + if (string.IsNullOrEmpty(configuration.DataView)) + { + entities = _entityService.GetAllEntities(collectionConfig); + } + else + { + var allEntities = new List(); + const int pageSize = 100; + int pageNumber = 1; + long totalPages = 0; + + while (pageNumber == 1 || pageNumber <= totalPages) + { + PagedResult pagedResult = _entityService.FindEntities( + collectionConfig, + pageNumber: pageNumber, + pageSize: pageSize, + dataViewAlias: configuration.DataView); + + if (pagedResult.Items != null) + { + allEntities.AddRange(pagedResult.Items); + } + + if (pageNumber == 1) + { + totalPages = pagedResult.TotalPages; + } + + pageNumber++; + } + + entities = allEntities; + } + + foreach (var entity in entities) + { + var id = GetPropertyValue(collectionConfig.IdProperty, entity); + var name = GetPropertyValue(collectionConfig.NameProperty, entity); + + result.Add(new PreValue() + { + Id = id?.ToString() ?? string.Empty, + Caption = name?.ToString() ?? string.Empty, + Value = id?.ToString() ?? string.Empty + }); + } + } + } + + result = result.OrderBy(x => x.Id).ToList(); + return Task.FromResult(result); + } + + public override List ValidateSettings() + { + var exceptions = new List(); + + UIBuilderSectionConfiguration? configuration = ParseSourceConfiguration(); + if (configuration != null) + { + if (string.IsNullOrEmpty(configuration.Section)) + { + exceptions.Add(new Exception("'Section' setting has not been set")); + } + + if (string.IsNullOrEmpty(configuration.Collection)) + { + exceptions.Add(new Exception("'Collection' setting has not been set")); + } + } + + return exceptions; + } + + private UIBuilderSectionConfiguration? ParseSourceConfiguration() + { + if (string.IsNullOrEmpty(Source)) + { + return null; + } + + List? config = JsonSerializer.Deserialize>(Source); + var result = new UIBuilderSectionConfiguration(); + + if (config != null) + { + foreach (UIBuilderSectionConfigItem item in config) + { + switch (item.Alias) + { + case "section": + result.Section = item.Value; + break; + case "collection": + result.Collection = item.Value; + break; + case "dataView": + result.DataView = item.Value; + break; + case "labels": + if (!string.IsNullOrEmpty(item.Value)) + { + result.Labels = JsonSerializer.Deserialize(item.Value); + } + break; + } + } + } + + return result; + } + + private static object? GetPropertyValue(PropertyConfig? propertyConfig, object entity) + { + if (propertyConfig == null) + { + return null; + } + + return propertyConfig.PropertyGetter?.Invoke(entity) ?? propertyConfig.PropertyExpression.Compile().DynamicInvoke(entity); + } + } + + internal class UIBuilderSectionConfigItem + { + [JsonPropertyName("alias")] + public string? Alias { get; set; } + + [JsonPropertyName("value")] + public string? Value { get; set; } + } + + internal class UIBuilderSectionConfiguration + { + public string? Section { get; set; } + public string? Collection { get; set; } + public string? DataView { get; set; } + public UIBuilderLabels? Labels { get; set; } + } + + internal class UIBuilderLabels + { + [JsonPropertyName("plural")] + public string? Plural { get; set; } + + [JsonPropertyName("singular")] + public string? Singular { get; set; } + } +} +``` + +And registered with: + +```csharp +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.WithCollectionBuilder() + .Add(); + } +} +``` + +This will then make the "UI Builder Repository" available when creating a new prevalue source: + +![UI Builder Repository available as a prevalue source](./images/uibuilder-repository-prevalue-source.png) + +Once selected, a user can pick a section, collection, and data view from the configured UI Builder repositories: + +![Selecting a UI Builder repository as a prevalue source](./images/uibuilder-repository-prevalue-configuration.png)