From 627224d5053c4c1899e6bf53e4a0cf43d879b5b0 Mon Sep 17 00:00:00 2001 From: Eric Dugre Date: Mon, 23 May 2022 18:24:25 -0400 Subject: [PATCH] implements #7 --- README.md | 20 ++++++- .../BreadcrumbsWidgetViewComponent.cs | 1 - .../IBreadcrumbsWidgetServiceExtensions.cs | 2 +- .../{Helpers => Services}/BreadcrumbHelper.cs | 49 +++++++---------- .../Services/DefaultBreadcrumbItemMapper.cs | 55 +++++++++++++++++++ .../DefaultBreadcrumbsRenderer.cs | 2 +- .../Interfaces/IBreadcrumbItemMapper.cs | 25 +++++++++ .../Interfaces}/IBreadcrumbsRenderer.cs | 0 .../Xperience.Core.Breadcrumbs.csproj | 2 +- 9 files changed, 120 insertions(+), 36 deletions(-) rename src/Xperience.Core.Breadcrumbs/{Helpers => }/IBreadcrumbsWidgetServiceExtensions.cs (96%) rename src/Xperience.Core.Breadcrumbs/{Helpers => Services}/BreadcrumbHelper.cs (82%) create mode 100644 src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbItemMapper.cs rename src/Xperience.Core.Breadcrumbs/{Renderer => Services}/DefaultBreadcrumbsRenderer.cs (95%) create mode 100644 src/Xperience.Core.Breadcrumbs/Services/Interfaces/IBreadcrumbItemMapper.cs rename src/Xperience.Core.Breadcrumbs/{Renderer => Services/Interfaces}/IBreadcrumbsRenderer.cs (100%) diff --git a/README.md b/README.md index 1543358..481fa99 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,11 @@ To override the properties registered during application startup, pass a new `Br }) ``` -## Custom rendering +## Customizations -The final HTML of your breadcrumbs is determined by the `IBreadcrumbsRenderer` interface, which you can find the default code for in [DefaultBreadcrumbsRenderer](/Renderer/DefaultBreadcrumbsRenderer.cs). If you'd like to customize the HTML of the breadcrumbs, you can implement your own `IBreadcrumbsRenderer` and use the `RegisterImplementation` attribute to register your code with a higher priority: +### Breadcrumb rendering + +The final HTML of your breadcrumbs is determined by the `IBreadcrumbsRenderer` interface, which you can find the default code for in [DefaultBreadcrumbsRenderer](/src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbsRenderer.cs). If you'd like to customize the HTML of the breadcrumbs, you can implement your own `IBreadcrumbsRenderer` and use the `RegisterImplementation` attribute to register your code with a higher priority: ```cs [assembly: RegisterImplementation(typeof(IBreadcrumbsRenderer), typeof(CustomBreadcrumbsRenderer), Lifestyle = Lifestyle.Singleton, Priority = RegistrationPriority.Default)] @@ -80,6 +82,20 @@ namespace MySite.Breadcrumbs { public class CustomBreadcrumbsRenderer : IBreadcrumbsRenderer { ``` +### Breadcrumb item generation + +Breadcrumb items are provided by the [DefaultBreadcrumbItemMapper](/src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbItemMapper.cs). If you would like to modify how the breadcrumb names, URLs, etc. are generated, you can implement your own `IBreadcrumbItemMapper` and use the `RegisterImplementation` attribute to register your code with a higher priority: + +```cs +[assembly: RegisterImplementation(typeof(IBreadcrumbItemMapper), typeof(CustomBreadcrumbItemMapper), Lifestyle = Lifestyle.Singleton, Priority = RegistrationPriority.Default)] +namespace MySite.Breadcrumbs +{ + /// + /// Custom implementation of . + /// + public class CustomBreadcrumbItemMapper : IBreadcrumbItemMapper { +``` + ## Compatibility This code is only available for use on Kentico Xperience 13 websites using the [.NET Core development model](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core). The website must be using the [content tree-based routing](https://docs.xperience.io/developing-websites/implementing-routing/content-tree-based-routing) model for the breadcrumbs to display properly. diff --git a/src/Xperience.Core.Breadcrumbs/BreadcrumbsWidgetViewComponent.cs b/src/Xperience.Core.Breadcrumbs/BreadcrumbsWidgetViewComponent.cs index fc41f4c..f86b002 100644 --- a/src/Xperience.Core.Breadcrumbs/BreadcrumbsWidgetViewComponent.cs +++ b/src/Xperience.Core.Breadcrumbs/BreadcrumbsWidgetViewComponent.cs @@ -2,7 +2,6 @@ using Kentico.PageBuilder.Web.Mvc; -using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewComponents; diff --git a/src/Xperience.Core.Breadcrumbs/Helpers/IBreadcrumbsWidgetServiceExtensions.cs b/src/Xperience.Core.Breadcrumbs/IBreadcrumbsWidgetServiceExtensions.cs similarity index 96% rename from src/Xperience.Core.Breadcrumbs/Helpers/IBreadcrumbsWidgetServiceExtensions.cs rename to src/Xperience.Core.Breadcrumbs/IBreadcrumbsWidgetServiceExtensions.cs index c0fcf31..f72a348 100644 --- a/src/Xperience.Core.Breadcrumbs/Helpers/IBreadcrumbsWidgetServiceExtensions.cs +++ b/src/Xperience.Core.Breadcrumbs/IBreadcrumbsWidgetServiceExtensions.cs @@ -22,7 +22,7 @@ public static IServiceCollection AddBreadcrumbs( // Register widget properties var props = new BreadcrumbsWidgetProperties(); - if (configure is object) + if (configure != null) { configure(props); } diff --git a/src/Xperience.Core.Breadcrumbs/Helpers/BreadcrumbHelper.cs b/src/Xperience.Core.Breadcrumbs/Services/BreadcrumbHelper.cs similarity index 82% rename from src/Xperience.Core.Breadcrumbs/Helpers/BreadcrumbHelper.cs rename to src/Xperience.Core.Breadcrumbs/Services/BreadcrumbHelper.cs index f996b68..7817c45 100644 --- a/src/Xperience.Core.Breadcrumbs/Helpers/BreadcrumbHelper.cs +++ b/src/Xperience.Core.Breadcrumbs/Services/BreadcrumbHelper.cs @@ -20,23 +20,23 @@ public class BreadcrumbHelper { private const string CACHE_KEY_FORMAT = "breadcrumbswidget|document|{0}|{1}|{2}"; private readonly IPageDataContextRetriever pageDataContextRetriever; - private readonly IPageUrlRetriever pageUrlRetriever; private readonly IBreadcrumbsRenderer breadcrumbsRenderer; + private readonly IBreadcrumbItemMapper breadcrumbItemMapper; private readonly IPageRetriever pageRetriever; private readonly BreadcrumbsWidgetProperties breadcrumbsWidgetProperties; public BreadcrumbHelper( IPageDataContextRetriever pageDataContextRetriever, - IPageUrlRetriever pageUrlRetriever, IBreadcrumbsRenderer breadcrumbsRenderer, IPageRetriever pageRetriever, + IBreadcrumbItemMapper breadcrumbItemMapper, BreadcrumbsWidgetProperties breadcrumbsWidgetProperties) { this.pageDataContextRetriever = pageDataContextRetriever; - this.pageUrlRetriever = pageUrlRetriever; this.breadcrumbsRenderer = breadcrumbsRenderer; this.pageRetriever = pageRetriever; + this.breadcrumbItemMapper = breadcrumbItemMapper; this.breadcrumbsWidgetProperties = breadcrumbsWidgetProperties; } @@ -76,7 +76,7 @@ public IReadOnlyList GetHierarchy(BreadcrumbsWidgetProperties pr } - private IHtmlContent GetBreadcrumbContent(BreadcrumbsWidgetProperties? props) + private IHtmlContent GetBreadcrumbContent(BreadcrumbsWidgetProperties props) { if (props == null) { @@ -98,7 +98,7 @@ private IHtmlContent GetBreadcrumbContent(BreadcrumbsWidgetProperties? props) { sb.Append(breadcrumbsRenderer.RenderSiteLink(bci, props.BreadcrumbItemClass)); } - else if (string.IsNullOrEmpty(bci.Url)) + else if (String.IsNullOrEmpty(bci.Url)) { sb.Append(breadcrumbsRenderer.RenderItemWithoutLink(bci, props.BreadcrumbItemClass)); } @@ -114,9 +114,13 @@ private IHtmlContent GetBreadcrumbContent(BreadcrumbsWidgetProperties? props) } - private string GetCacheKey(int docID, bool showSiteLink, bool showContainers) + private string GetCacheKey(int docID, BreadcrumbsWidgetProperties props) { - return string.Format(CACHE_KEY_FORMAT, docID, showSiteLink, showContainers); + return string.Format( + CACHE_KEY_FORMAT, + docID, + props.ShowSiteLink, + props.ShowContainers); } @@ -131,26 +135,21 @@ private IReadOnlyList GetHierarchyInternal(BreadcrumbsWidgetProp var hierarchy = CacheHelper.Cache((cs) => { ICollection cacheDependencies = new List(); - var list = BuildHierarchyInternal(current, props.ShowSiteLink, props.ShowContainers, ref cacheDependencies); + var list = BuildHierarchyInternal(current, props, ref cacheDependencies); cs.CacheDependency = CacheHelper.GetCacheDependency(cacheDependencies); return list; - }, new CacheSettings(120, GetCacheKey(current.DocumentID, props.ShowSiteLink, props.ShowContainers))); + }, new CacheSettings(120, GetCacheKey(current.DocumentID, props))); return hierarchy.ToList().AsReadOnly(); } - private IEnumerable BuildHierarchyInternal(TreeNode current, bool addSiteLink, bool showContainers, ref ICollection cacheDependencies) + private IEnumerable BuildHierarchyInternal(TreeNode current, BreadcrumbsWidgetProperties props, ref ICollection cacheDependencies) { // Add current page var ret = new List { - new BreadcrumbItem - { - Name = current.DocumentName, - Url = null, - IsCurrentPage = true - } + breadcrumbItemMapper.MapPage(current, true) }; cacheDependencies.Add($"documentid|{current.DocumentID}"); @@ -168,14 +167,9 @@ private IEnumerable BuildHierarchyInternal(TreeNode current, boo if (type != null) { if (type.ClassIsCoupledClass || - !type.ClassIsCoupledClass && showContainers) + !type.ClassIsCoupledClass && props.ShowContainers) { - var url = pageUrlRetriever.Retrieve(parent).AbsoluteUrl; - ret.Add(new BreadcrumbItem - { - Name = parent.DocumentName, - Url = url - }); + ret.Add(breadcrumbItemMapper.MapPage(parent)); } } cacheDependencies.Add($"documentid|{current.DocumentID}"); @@ -184,14 +178,9 @@ private IEnumerable BuildHierarchyInternal(TreeNode current, boo } // Add link to main domain if needed - if (addSiteLink) + if (props.ShowSiteLink) { - ret.Add(new BreadcrumbItem - { - IsSiteLink = true, - Name = current.Site.DisplayName, - Url = current.Site.SitePresentationURL - }); + ret.Add(breadcrumbItemMapper.MapSite(current.Site)); cacheDependencies.Add($"cms.site|byid|{current.Site.SiteID}"); } diff --git a/src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbItemMapper.cs b/src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbItemMapper.cs new file mode 100644 index 0000000..f77e736 --- /dev/null +++ b/src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbItemMapper.cs @@ -0,0 +1,55 @@ +using CMS; +using CMS.Core; +using CMS.DocumentEngine; +using CMS.SiteProvider; + +using Kentico.Content.Web.Mvc; + +using System; + +using Xperience.Core.Breadcrumbs; + +[assembly: RegisterImplementation(typeof(IBreadcrumbItemMapper), typeof(DefaultBreadcrumbItemMapper), Lifestyle = Lifestyle.Singleton, Priority = RegistrationPriority.SystemDefault)] +namespace Xperience.Core.Breadcrumbs +{ + /// + /// Default implementation of . + /// + internal class DefaultBreadcrumbItemMapper : IBreadcrumbItemMapper + { + private readonly IPageUrlRetriever pageUrlRetriever; + + + public DefaultBreadcrumbItemMapper(IPageUrlRetriever pageUrlRetriever) + { + this.pageUrlRetriever = pageUrlRetriever; + } + + + public BreadcrumbItem MapPage(TreeNode page, bool isCurrent = false) + { + var url = String.Empty; + if (!isCurrent) + { + url = pageUrlRetriever.Retrieve(page).AbsoluteUrl; + } + + return new BreadcrumbItem + { + Name = page.DocumentName, + Url = url, + IsCurrentPage = isCurrent + }; + } + + public BreadcrumbItem MapSite(SiteInfo site) + { + return new BreadcrumbItem + { + IsSiteLink = true, + Name = site.DisplayName, + Url = site.SitePresentationURL + }; + } + } +} diff --git a/src/Xperience.Core.Breadcrumbs/Renderer/DefaultBreadcrumbsRenderer.cs b/src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbsRenderer.cs similarity index 95% rename from src/Xperience.Core.Breadcrumbs/Renderer/DefaultBreadcrumbsRenderer.cs rename to src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbsRenderer.cs index 67321cc..cd8b593 100644 --- a/src/Xperience.Core.Breadcrumbs/Renderer/DefaultBreadcrumbsRenderer.cs +++ b/src/Xperience.Core.Breadcrumbs/Services/DefaultBreadcrumbsRenderer.cs @@ -9,7 +9,7 @@ namespace Xperience.Core.Breadcrumbs /// /// Default implementation of . /// - public class DefaultBreadcrumbsRenderer : IBreadcrumbsRenderer + internal class DefaultBreadcrumbsRenderer : IBreadcrumbsRenderer { public string RenderClosingTag() { diff --git a/src/Xperience.Core.Breadcrumbs/Services/Interfaces/IBreadcrumbItemMapper.cs b/src/Xperience.Core.Breadcrumbs/Services/Interfaces/IBreadcrumbItemMapper.cs new file mode 100644 index 0000000..7f8f25e --- /dev/null +++ b/src/Xperience.Core.Breadcrumbs/Services/Interfaces/IBreadcrumbItemMapper.cs @@ -0,0 +1,25 @@ +using CMS.DocumentEngine; +using CMS.SiteProvider; + +namespace Xperience.Core.Breadcrumbs +{ + /// + /// Converts Xperience objects into s. + /// + public interface IBreadcrumbItemMapper + { + /// + /// Generates a for an Xperience content tree page. + /// + /// The page to generate the breadcrumb for. + /// If true, the is what the visitor is currently viewing. + public BreadcrumbItem MapPage(TreeNode page, bool isCurrent = false); + + + /// + /// Generates a for the current site. + /// + /// The current Xperience site. + public BreadcrumbItem MapSite(SiteInfo site); + } +} diff --git a/src/Xperience.Core.Breadcrumbs/Renderer/IBreadcrumbsRenderer.cs b/src/Xperience.Core.Breadcrumbs/Services/Interfaces/IBreadcrumbsRenderer.cs similarity index 100% rename from src/Xperience.Core.Breadcrumbs/Renderer/IBreadcrumbsRenderer.cs rename to src/Xperience.Core.Breadcrumbs/Services/Interfaces/IBreadcrumbsRenderer.cs diff --git a/src/Xperience.Core.Breadcrumbs/Xperience.Core.Breadcrumbs.csproj b/src/Xperience.Core.Breadcrumbs/Xperience.Core.Breadcrumbs.csproj index a0025ca..6e92781 100644 --- a/src/Xperience.Core.Breadcrumbs/Xperience.Core.Breadcrumbs.csproj +++ b/src/Xperience.Core.Breadcrumbs/Xperience.Core.Breadcrumbs.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 true - 2.0.0 + 3.0.0 Eric Dugre Kentico A breadcrumbs widget for Xperience .NET Core websites