Extending the BizFX Commerce Tools for #Sitecore Commerce for Simpletons

Sitecore commerce has some really great features and hidden gems. Recently I was given an assignment to create some custom forms for the BizFX tools that come with Sitecore commerce. Excited as I was to get started I found there was not too much documentation on this. I had some help thanks to Andrew Sutherlands blog. There were things though that I ran in to that I had to figure out how to get through. So I want to make sure I can help others who may get into the same situation as me and also if I ever need to to extend the tools again I can look back at this blog and it will help me figure some of the things out.

Understanding the Master Form and Children

Basically you have a master view. This is usually a summary of your records. Clicking on that master form will show its children views. The children could be any type of detail block of information. So lets say you have a summary of services as your master. The children view when breaking it down further could be notes and description, service dates, vendor information etc… The code below is an example on how to add children view(s) to a master view. You need to make sure you are on the correct master view.

[PipelineDisplayName(Constants.Blocks.GetServicesItemsBlock)]
    public class GetServicesItemsBlock : GetTableViewBlock
    {
        private readonly IServiceService _servicesService;

        public GetServicesItemsBlock(IServiceService servicesService)
        {
            _servicesService = servicesService;
        }

        public override Task<EntityView> Run(EntityView entityView, CommercePipelineExecutionContext context)
        {
            Condition.Requires(entityView).IsNotNull($"{this.Name}: The argument can not be null");

            var entityViewArgument = context.CommerceContext.GetObjects<EntityViewArgument>().FirstOrDefault();
            if (entityViewArgument == null || string.IsNullOrEmpty(entityViewArgument.EntityId)) return Task.FromResult(entityView);

            if (!EntityViewExtensions.IsOnServicesView(entityViewArgument)) //Make sure you are on the correct master view
            {
                return Task.FromResult(entityView);
            }
            
            var entityId = long.Parse(entityViewArgument.EntityId.Substring(entityViewArgument.EntityId.LastIndexOf('-') + 1));
            var services =  _servicesService.GetServiceById(entityId);

            EntityView subView = new EntityView
            {
                EntityId = entityId.ToString(), Name = Constants.Headers.Items, UiHint = "Table"
            };

            entityView.ChildViews.Add(subView);

            var servicesItemList = services.Result.ServiceItems;
            if (servicesItemList == null) return Task.FromResult(entityView);

            foreach (var subitem in servicesItemList)
            {
                EntityView lineView = new EntityView
                {
                    EntityId = subView.EntityId, ItemId = services.Result.OrderId, Name = Constants.Headers.Items
                };

                lineView.AddServiceItemChildView(subitem);
                subView.ChildViews.Add(lineView);
            }

            return Task.FromResult(entityView);
        }
    }

For the master you will have code like this if you need to change the properties:

//Current entity which is the the master view is passed in to the block.
public override async Task<EntityView> Run(EntityView arg, CommercePipelineExecutionContext context)

var status = entityViewArgument.ViewName.Replace("ServicesList-", string.Empty);
            arg.Properties.Add(new ViewProperty
            {
                Name = "ListName",
                RawValue = status,
                IsReadOnly = true,
                IsHidden = true,
                IsRequired = false
            });
            arg.UiHint = "Table";
Example Summary

To sum it up the master/child view structure is simple. You have your master view, but then you can add children views and those children can have children views.

For the children you want to generate a new view.

It would be done in this order (see code for reference):

  1. Get the master view.
  2. Create a subview (child view).
  3. Create and add children to the subview.
  4. Add subview to the master entity view.
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Commerce.Core;
using Sitecore.Commerce.EntityViews;
using Sitecore.Framework.Conditions;
using Sitecore.Framework.Pipelines;

namespace PSP.Commerce.Plugin.Service.Pipelines.Blocks
{
    ///<summary>
    ///displays the service items view in BizFx/services.
    ///</summary>
    [PipelineDisplayName(Constants.Blocks.GetServicesItemsBlock)]
    public class GetServicesItemsBlock : GetTableViewBlock
    {
        private readonly IServiceService _serviceService;

        public GetServicessItemsBlock(IServiceService serviceService)
        {
            _serviceService = serviceService;
        }

        public override Task<EntityView> Run(EntityView entityView, CommercePipelineExecutionContext context)
        {
            Condition.Requires(entityView).IsNotNull($"{this.Name}: The argument can not be null");

            var entityViewArgument = context.CommerceContext.GetObjects<EntityViewArgument>().FirstOrDefault();
            if (entityViewArgument == null || string.IsNullOrEmpty(entityViewArgument.EntityId)) return Task.FromResult(entityView);

            if (!EntityViewExtensions.IsOnSubcriptionsView(entityViewArgument))
            {
                return Task.FromResult(entityView);
            }
            
            var entityId = long.Parse(entityViewArgument.EntityId.Substring(entityViewArgument.EntityId.LastIndexOf('-') + 1));
            var service=  _serviceService.GetServiceById(entityId);

            EntityView subView = new EntityView
            {
                EntityId = entityId.ToString(), Name = Constants.Headers.Items, UiHint = "Table"
            };

            entityView.ChildViews.Add(subView);

            var serviceItemList = service.Result.ServiceItems;
            if (serviceItemList == null) return Task.FromResult(entityView);

            foreach (var subitem in serviceItemList)
            {
                EntityView lineView = new EntityView
                {
                    EntityId = subView.EntityId, ItemId = service.Result.OrderId, Name = Constants.Headers.Items
                };

                lineView.AddServiceItemChildView(subitem);
                subView.ChildViews.Add(lineView); 
            }

            return Task.FromResult(entityView);
        }
    }
}

Example child item.

There are many field types you can use for views, but two of them that stood out for me was the EntityLink and HTML field types.

Below is an example of an link type. Which we can create to navigate to other types of views. The Id for instance could be used to retrieve records for the child views.

var idViewProperty = new ViewProperty
                {
                    Name = "Id",
                    RawValue = service.Id,
                    IsReadOnly = true,
                    UiType = "EntityLink"
                };
                entityView.Properties.Add(idViewProperty);

Below is an example of using a UitType of Html to create an image column value.

  var imageLinkProperty = new ViewProperty
            {
                Name = "Image",
                RawValue =  "<a><img src="+ serviceItem.ImageUrl +" alt=" + serviceItem.Name + " width=\"50\" height=\"50\"></a>", //MediaManager.GetMediaUrl(serviceItem.ImageUrl),
                IsReadOnly = true,
                UiType = "Html"
            };
            properties.Add(imageLinkProperty);

Last thing I want to mention is action view buttons. You can easily defined them (see below).

            ActionsPolicy actionsPolicy = entityView.GetPolicy<ActionsPolicy>();
            List<EntityActionView> entityActionView = actionsPolicy.Actions;
            EntityActionView skipEntityActionView = new EntityActionView
            {
                Name = context.GetPolicy<KnownServiceCancelPolicy>().CancelService,
                DisplayName = Constants.Actions.CancelService,
                Description = Constants.Actions.CancelService,
                IsEnabled = true,
                RequiresConfirmation = true,
                EntityView = string.Empty,
                Icon = "delete"
            };
            entityActionView.Add(skipEntityActionView);

            List<EntityActionView> actions2 = actionsPolicy.Actions;
            EntityActionView cancelEntityActionView = new EntityActionView
            {
                Name = context.GetPolicy<KnownServiceSkipPolicy>().SkipService,
                DisplayName = Constants.Actions.SkipService,
                Description = Constants.Actions.SkipService,
                IsEnabled = true,
                RequiresConfirmation = true,
                EntityView = string.Empty,
                Icon = "hand_stop2"
            };
            actions2.Add(cancelEntityActionView);

Here are the results you will see in the view.

Hope this helps you if you are finding this blog. If not please contact me. Thanks.