By Nele Debrouwer, 25-10-2024
A common and essential concept in web design is dividing a webpage into multiple sections, often using the <section /> tag inside the <main /> tag. This approach helps organize content into distinct areas. To further optimize this concept, a key strategy in content management is to offer configurable options for these sections. This might include background colors, spacing, simple text fields for CSS classes, or entirely new inner structures.
In modern Digital Experience Platforms (DXPs), where multiple websites or channels (not limited to just websites) are managed, it's crucial to make rich text agnostic to its design. By configuring basic design principles in your Content Management System (CMS), you provide tremendous flexibility for content authors and webmasters.
In Xperience by Kentico, "section properties" are a powerful tool for this purpose. They offer a comprehensive set of configurations that manage the content, appearance, behavior, and accessibility of different sections across website channels.
When you want to reuse these section properties with different values across multiple website channels, you need to write a custom visibility condition. Note that if your staging and production environments are set up to deploy without the admin interface for security reasons, you must create the custom visibility condition in a separate admin project.
The VisibilityConditionAttribute type is found in the Kentico.Xperience.Admin.Base.Shared assembly, a special assembly shared between Kentico.Xperience.WebApp and Kentico.Xperience.Admin. This assembly is not available as a separate NuGet package and must be accessed via Kentico.Xperience.WebApp or Kentico.Xperience.Admin.
As a result, your VisibilityCondition and VisibilityConditionProperties classes need to be placed in a separate "admin" .NET project, included only during administration deployments.
By creating a "shared" Admin project that references the Kentico.Xperience.WebApp NuGet package and is referenced by both your ASP.NET Core project and your "admin" .NET project, you can now leverage the benefit of the VisibilityConditionAttribute class.
This project references the Kentico.Xperience.WebApp NuGet package.
public class ChannelVisibilityConditionAttribute(string channelName) : VisibilityConditionAttribute { public string ChannelName { get; set; } = channelName ?? ""; }
This project references the Kentico.Xperience.WebApp NuGet package and the Shared project.
public class SectionProperties : ISectionProperties { [ChannelVisibilityCondition("Channel1")] [DropDownComponent( Label = "Channel 2 Section Shape", Options = "bg-shape triangles-bottom-right;Triangles bottom right\n" + "bg-shape triangles-left;Triangles left\n" + "bg-shape triangle-right;Triangle right\n" )] public string SectionShape { get; set; } = ""; }
This project references the Shared project and the Kentico.Xperience.Admin NuGet package. By referencing the Shared project, the ChannelVisibilityConditionAttribute can be assigned to the ChannelVisibilityCondition class, gluing them together for the administration/Page Builder UI.
[VisibilityConditionAttribute(typeof(ChannelVisibilityConditionAttribute))] public class ChannelIsEqualToVisibilityCondition(IPageBuilderUtilityContextRetriever websiteChannelContext) : VisibilityCondition<ChannelVisibilityConditionProperties> { private readonly IPageBuilderUtilityContextRetriever websiteChannelContext = websiteChannelContext; public override bool Evaluate(IFormFieldValueProvider formFieldValueProvider) { var channel = websiteChannelContext.Retrieve().Result; return string.Equals( channel?.WebsiteChannelName, Properties.ChannelName, StringComparison.OrdinalIgnoreCase ); } }
During development we encountered an issue when switching between the website channels. The website channel name did not get properly resolved. After contacting Kentico community through their Q&A, Sean Wright gave us a solution.
“I also acknowledge we have a gap in the product right now. You can't use IWebsiteChannelContext or IPageBuilderDataContextRetriever in Page Builder UI Form Components or related types (visibility conditions, custom options providers). We have some internal services to access this context, but they aren't available for you to use. We'll look at exposing this information in the future. In the meantime, here's a ... "workaround" that you can use:”
public interface IPageBuilderUtilityContextRetriever { Task<PageBuilderUtilityContext> Retrieve(); } public record PageBuilderUtilityContext( string WebsiteChannelName, int WebsiteChannelID, IWebPageFieldsSource Webpage ); public class PageBuilderUtilityContextRetriever( IHttpContextAccessor contextAccessor, IInfoProvider<ChannelInfo> channelInfoProvider, IContentQueryExecutor queryExecutor ) : IPageBuilderUtilityContextRetriever { private readonly IHttpContextAccessor contextAccessor = contextAccessor; private readonly IInfoProvider<ChannelInfo> channelInfoProvider = channelInfoProvider; private readonly IContentQueryExecutor queryExecutor = queryExecutor; private PageBuilderUtilityContext? context = null; public async Task<PageBuilderUtilityContext> Retrieve() { if (context is not null) { return context; } int websiteChannelID = 0; int webPageID = 0; var httpContext = contextAccessor.HttpContext; string path = httpContext.Request.Form["path"].FirstOrDefault() ?? ""; string pattern = @"webpages-(\d+)/([^_/]+)_(\d+)"; var match = Regex.Match(path, pattern); if (match.Success) { websiteChannelID = int.TryParse(match.Groups[1].Value, out int channelID) ? channelID : 0; webPageID = int.TryParse(match.Groups[3].Value, out int pageID) ? pageID : 0; } var channels = await channelInfoProvider.Get() .Source(s => s.Join(nameof(ChannelInfo.ChannelID), nameof(WebsiteChannelInfo.WebsiteChannelChannelID))) .Where(w => w.WhereEquals(nameof(WebsiteChannelInfo.WebsiteChannelID), websiteChannelID)) .Columns(nameof(ChannelInfo.ChannelName)) .GetEnumerableTypedResultAsync(); string websiteChannelName = channels .Select(s => s.ChannelName) .FirstOrDefault() ?? ""; var query = new ContentItemQueryBuilder() .ForContentTypes(q => q.ForWebsite([webPageID], false)); var pages = await queryExecutor.GetMappedWebPageResult(query); var webPage = pages.FirstOrDefault(); context = new(websiteChannelName, websiteChannelID, webPage); return context; } }
After registering the service and injecting it in our visibility condition class, we used the same section properties but with different values between our website channels.
By following these steps, you can effectively manage section properties across multiple website channels in Xperience by Kentico, providing content authors and webmasters with the flexibility they need while maintaining a consistent design framework.
Ready to take your digital experience to the next level? Feel free to contact us to learn more about our services and how we can help you leverage the full potential of your digital marketing.
Get in touch with one of our consultants to find the perfect match that fits your needs and enables you to grow.