Extending Trees in Ucommerce Admin
In the shell application in Sitecore you have the ability to extend the tree to hook up custom pages needed by your clients to manage external administration.
You'll need to implement two interfaces to get your custom nodes working in the shell integration which is:
-
ITreeContentProvider
- Interface used to provide a given node under a given nodeType e.g the root node,settings node etc.
-
ITreeNodeTypeToUrlConverter
- Used to provide a URL for a given node type. The URL will be used to open a specific editor page in the right side of the pane.
To get started I'll extend the demo store solution with a new project called Ucommerce.DemoStore.Extensions. (you can of course use whatever project you'd like). The project will serve as my assembly where I'll place all my extensions to Ucommerce - in this case the implementation of the two bespoke interfaces.
Implementing ITreeContentProvider
I'll start of by creating a new class and deriving from the interface ITreeContentProvider
which has two methods to consider:
- Supports(string nodeType)
- GetChildren(string nodeType, string id)
namespace UCommerce.Tree { /// <summary> /// A content provider is a child node provider who can also tell if a given node type is supported. /// </summary> public interface ITreeContentProvider : ITreeChildProvider { /// <summary> /// Returns true, if the provider supports a specific tree node type. /// </summary> /// <param name="nodeType">The type of the node.</param> /// <returns>true, if the node type is supported by the provider.</returns> bool Supports(string nodeType); /// <summary> /// Given a node type and possibly an id, the provider returnes a list of child nodes. /// </summary> /// <param name="nodeType">The type of the node.</param> /// <param name="id">The id of the node.</param> /// <returns>A list of child nodes for the node.</returns> IList<ITreeNodeContent> GetChildren(string nodeType, string id); } }
namespace UCommerce.Tree { /// <summary> /// An interface used by the converters, converting data from a <see cref="ITreeNodeContent"/> giving the /// url the GUI should load, when the node is selected. /// </summary> public interface ITreeNodeTypeToUrlConverter { /// <summary> /// Converts the type, id and context id into an url. /// </summary> /// <param name="type">The type of the node.</param> /// <param name="itemId">The item id of the node.</param> /// <param name="itemContextId">The id of the context of the node.</param> /// <param name="url">The resulting URL to be used by the GUI.</param> /// <returns>true, if the data was converted to a URL.</returns> bool TryConvert(string type, string itemId, string itemContextId, out string url); } }
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using UCommerce; using UCommerce.Tree; using UCommerce.Tree.Impl; namespace Ucommerce.DemoStore.Extensions.ContentProvider { public class TrendSeamSectionsProvider : ITreeContentProvider { public TrendSeamSectionsProvider() { } public bool Supports(string nodeType) { throw new NotImplementedException(); } public IList<ITreeNodeContent> GetChildren(string nodeType, string id) { throw new NotImplementedException(); } } }
Implement Supports
You'll have to consider in which level of the tree you want to be hooked up. The framework will for each node ask the registered ITreeContentProvider
if they support a given nodeType.
To support the root we'll say yes to Constants.DataProvider.NodeType.Root.
We'll have one level of children so we'd also want to support our own node which is the nodeType we'll provide when creating the nodes.
public bool Supports(string nodeType) { return nodeType == Constants.DataProvider.NodeType.Root || nodeType == TrendSeamConstants.TrendSeam; }
The implementation will now return true for "TrendSeam" and "Root" which is the two constants.
Implement GetChildren
Now that we've specified that we support "TrendSeam" and "Root", We'll have to provide children for the two nodeTypes. For the "root" node we have the "TrendSeam" node and for the "TrendSeam" node we have the following three:
- customerStatistics
- orderStatistics
- shoppingCardStatistics
public IList<ITreeNodeContent> GetChildren(string nodeType, string id) { switch (nodeType) { case Constants.DataProvider.NodeType.Root: return BuildTrendSeamRoot(); case TrendSeamConstants.TrendSeam: return BuildTrendSeamChildren(); default: throw new NotSupportedException(nodeType); } }
It's two different lists of nodes that we'll have to provide so let's take a look at how to create the TrendSeamRoot.
When creating a TreeNodeContent you'll have to specify the following properties:
- NodeType
-
Id
- Id used to distinquish different nodes if they may be associated with a business object. In my case i just use a random integer -4 as it is just a static page.
-
Name
- the name shown in the tree
-
Icon
- the icon shown in the tree. If you're using a custom icon you need to put it here (relative to the root): 'Sitecore modules/shell/ucommerce/shell/content/images/ui'
-
HasChildren
- Does your node have any children? In the first case yes. Three as seen in the first picture.
private IList<ITreeNodeContent> BuildTrendSeamRoot() { return new List<ITreeNodeContent> { BuildTrendSeamNode(), }; } private ITreeNodeContent BuildTrendSeamNode() { var trendSeamNode = new TreeNodeContent(TrendSeamConstants.TrendSeam, -4) { Name = "TrendSeam", Icon = "TrendSeam.png", HasChildren = true }; return trendSeamNode; }
The TreeNodeContent nodes that I create will result in a node in the tree in the specific level that i provided support for. In this case the trendSeamNode is porovided for the root node.
The constructor of TreeNodeContent needs the nodeType and an id. The nodeType is the name provided in Supports(nodeType). PLEASE NOTE that the nodeType, "TrendSeam" i specified in the TreeNodeContent constructor, is the same as the nodeType i check for in Supports. This will result in the fact that GetChildren will also get called for the TrendSeam node.
Implementing the ITreeNodeTypeToUrlConverter
You'll have to provide a url to your nodes which you'll need an implementation of ITreeNodeTypeToUrlConverter to achieve. The URL will be used to open a specific editor page in the right side of the pane.
public class TrendSeamNodeToUrlConverter : ITreeNodeTypeToUrlConverter { public bool TryConvert(string type, string itemId, string itemContextId, out string url) { throw new NotImplementedException(); } }
In this case you'll only need to check the type since we only have 4 static nodes with 4 different node types. The implementation here'll be quite easy.
public class TrendSeamNodeToUrlConverter : ITreeNodeTypeToUrlConverter { public bool TryConvert(string type, string itemId, string itemContextId, out string url) { url = string.Empty; switch (type) { case TrendSeamConstants.TrendSeam: url = TrendSeamConstants.TrendSeamUrls.TrendSeamBaseUrl; break; case TrendSeamConstants.TrendSeamOrderStatistics: url = TrendSeamConstants.TrendSeamUrls.TrendSeamOrdersStatisticsUrl; break; case TrendSeamConstants.TrendSeamCustomerStatistics: url = TrendSeamConstants.TrendSeamUrls.TrendSeamCustomerStatisticsUrl; break; case TrendSeamConstants.TrendSeamShoppingCartStatistics: url = TrendSeamConstants.TrendSeamUrls.TrendSeamShoppingCartStatisticsUrl; break; default: return false; } return true; } }
Above i'm just checking the type and returning a constant equivilant to the corresponding node. My constants looks like this:
public static class TrendSeamUrls { public const string TrendSeamBaseUrl = "ucommerce/TrendSeam/TrendSeam.aspx"; public const string TrendSeamCustomerStatisticsUrl = "ucommerce/TrendSeam/Customers.aspx"; public const string TrendSeamOrdersStatisticsUrl = "ucommerce/TrendSeam/PurchaseOrders.aspx"; public const string TrendSeamShoppingCartStatisticsUrl = "ucommerce/TrendSeam/ShoppingCart.aspx"; }
Please note that it is important that your URLs start with 'ucommerce/' and your actual page is relative to the ucommerce folder.
Register the New Services.
If you do not know how to register a component, please refer to the following article on how to do so (the same way applies across all versions of Ucommerce) : http://docs.ucommerce.net/ucommerce/v6/extending-ucommerce/register-a-component.html
The special thing to be aware of here is that i need to override the standard registration of TreeServiceShell
and TreeNodeConverterService
.
This is because the list of converters and content node providers are supplied as a specific list to be able to distinquish the trees in different places of the application.
The configuration below is what needs to registered in your configration file in Ucommerce/Apps/MyApp
. This includes the following modifications:
- registering the TrendSeamSectionProvider
-
registering the TrendSeamNodeToUrlConverter
-
overriding the default implementation of the TreeServiceShell (note that i've put
<item>${TrendSeamSectionProvider}</item>
to the end of the list) - overriding the default implementation of the TreeNodeConverterService (note that i've put
<item>${TrendSeamNodeToUrlConverter}</item>
)
<configuration> <components> <component id="DemoStoreWebApi" service="UCommerce.Web.Api.IContainsWebservices, UCommerce.Web.Api" type="UCommerce.DemoStore.WebServices.AssemblyTag, UCommerce.DemoStore"/> <component id="TrendSeamSectionProvider" service="UCommerce.Tree.ITreeContentProvider, UCommerce" type="Ucommerce.DemoStore.Extensions.ContentProvider.TrendSeamSectionsProvider, Ucommerce.DemoStore.Extensions" /> <component id="TrendSeamNodeToUrlConverter" service="UCommerce.Tree.ITreeNodeTypeToUrlConverter, UCommerce" type="Ucommerce.DemoStore.Extensions.ContentProvider.TrendSeamNodeToUrlConverter, Ucommerce.DemoStore.Extensions" /> <!-- Shell services --> <component id="TreeServiceShell" service="UCommerce.Tree.ITreeContentService, UCommerce" type="UCommerce.Tree.Impl.TreeContentService, UCommerce"> <parameters> <contentProviders> <list type="UCommerce.Tree.ITreeContentProvider, UCommerce"> <item>${DefaultShellCatalogSectionProvider}</item> <item>${DefaultCatalogSearchItemProvider}</item> <item>${DefaultOrdersSectionProvider}</item> <item>${DefaultMarketingSectionProvider}</item> <item>${DefaultAnalyticsSectionProvider}</item> <item>${DefaultRootSectionProvider}</item> <item>${DefaultSettingsRootSectionProvider}</item> <item>${DefaultSettingsCatalogProvider}</item> <item>${DefaultSettingsOrdersProvider}</item> <item>${DefaultSettingsEmailsProvider}</item> <item>${DefaultSettingsDefinitionsProvider}</item> <item>${DefaultSettingsSecurityProvider}</item> <item>${TrendSeamSectionProvider}</item> </list> </contentProviders> </parameters> </component> <component id="TreeNodeConverterService" service="UCommerce.Web.Api.Services.Nodes.ITreeNodeContentToTreeNodeConverter, UCommerce.Web.Api" type="UCommerce.Web.Api.Services.Nodes.Impl.TreeNodeContentToTreeNodeConverter, UCommerce.Web.Api"> <parameters> <urlConverters> <list type="UCommerce.Tree.ITreeNodeTypeToUrlConverter, UCommerce"> <item>${DefaultUrlConverter}</item> <item>${TrendSeamNodeToUrlConverter}</item> </list> </urlConverters> <optionConverters> <list type="UCommerce.Tree.ITreeNodeOptionToOptionItemConverter, UCommerce"> <item>${DefaultOptionConverter}</item> </list> </optionConverters> </parameters> </component> </components> </configuration>
modifying routes.js
I need to do one final thing to make this work. I've specified the URL and the nodes, however working with angular i have to specify the specific routes. I need to add four (since i have four nodes with four different pages to show). They should be added to the file called 'routes.js' which can be found under "sitecore modules\Shell\ucommerce\shell\App" relative to the root of the Sitecore site.
//The trendSeam 'main url' found in the first level .when('/ucommerce/TrendSeam/TrendSeam.aspx', { redirectTo: function (routeParameters) { return buildUrl('TrendSeam/TrendSeam.aspx'); } }) //The three children found under the first TrendSeam url .when('/ucommerce/TrendSeam/Customers.aspx', { redirectTo: function (routeParameters) { return buildUrl('TrendSeam/Customers.aspx'); } }) .when('/ucommerce/TrendSeam/PurchaseOrders.aspx', { redirectTo: function (routeParameters) { return buildUrl('TrendSeam/PurchaseOrders.aspx'); } }) .when('/ucommerce/TrendSeam/ShoppingCart.aspx, { redirectTo: function() { return buildUrl('TrendSeam/ShoppingCart.aspx'); } })
That is all needed to extend the tree in Ucommerce.