How to Extend the Promotions View
Promotions: Customize Master view by using Entity Views and Action API
October 19th, 2021
This blog provides a step-by-step guide on how to customize a Master view for a promotion item programmatically.
Promotion defines a set of qualifying rules that must be met in order to apply benefits at the cart level or for specific sellable items within a cart. We can create a promotion after associating a promotion book with a catalog. The master view of a promotion item looks like the image below:
Let s assume that we have to meet a specific set of requirements, shown below:
- Extend the master view to show additional information of a promotion. The additional view should include the following fields:
o ? Disclaimer: Single Line Text
o ? Promotion Type: Drop Down
o ? Combinable: Checkbox
- If a user chooses to click the tick button, then save information to the database
- Display the information once saved
Expected Result:
Implementation Approach
1. ? Add a new Blocks folder in your custom Plugin.
2. ? Create a new view and add additional properties to the view inside an EntityView class defined under the Blocks folder.
3. ? Define an action policy to populate the new view and its settings.
4. ? Execute DoAction on click of the tick button which performs a transaction and validates and stores the information in the database.
5. ? Display the saved information by entity view class defined in step 1.
Code
Create a new view and add additional properties to the view inside an EntityView class defined under Blocks folder:
namespace Plugin.Demo.Promotion.Pipelines.Blocks.EntityViews
{
[PipelineDisplayName(PromotionConstants.Pipelines.Blocks.GetPromotionAdditionalInformationViewBlock)]
public class GetPromotionAdditionalInformationViewBlock : PipelineBlock
{
private readonly ViewCommander _viewCommander;
public GetPromotionAdditionalInformationViewBlock(ViewCommander viewCommander)
{
this._viewCommander = viewCommander;
}
public override Task Run(EntityView arg, CommercePipelineExecutionContext context)
{
Condition.Requires(arg).IsNotNull($"{Name}: The argument cannot be null.");
var request = this._viewCommander.CurrentEntityViewArgument(context.CommerceContext);
KnownPromotionsViewsPolicy promotionsViewsPolicy = context.GetPolicy();
// Only proceed if the current entity is a promotion item
if (!(request.Entity is Sitecore.Commerce.Plugin.Promotions.Promotion))
{
return Task.FromResult(arg);
}
var promotionItem = (Sitecore.Commerce.Plugin.Promotions.Promotion)request.Entity;
var component = promotionItem.GetComponent();
var promotionAdditionalInformationViewsPolicy = context.GetPolicy();
var targetView = arg;
// Check if the add action was requested
var isAddView = !string.IsNullOrEmpty(arg.Action) && arg.Action.Equals("AddAdditionalInformation", StringComparison.OrdinalIgnoreCase);
// Create a new view and add it to the current entity view.
if (!isAddView && arg?.ChildViews.Where(cv => cv.Name.Equals(additionalInformationViewsPolicy.Name)).Any() == false)
{
var view = new EntityView
{
Name = additionalInformationViewsPolicy.Name,
DisplayName = additionalInformationViewsPolicy.DisplayName,
EntityId = arg.EntityId,
ItemId = arg.ItemId,
EntityVersion = arg.EntityVersion
};
if (component.Disclaimer == string.Empty)
{
view.UiHint = "Table";
}
arg.ChildViews.Add(view);
targetView = view;
AddPropertiesToView(targetView, component, true);
}
if(isAddView && promotionItem != null)
{
AddPropertiesToView(targetView, component, true);
}
return Task.FromResult(arg);
}
private void AddPropertiesToView(EntityView entityView, PromotionAdditionalInformationComponent component, bool isReadOnly)
{
if (!entityView.ContainsProperty(nameof(PromotionAdditionalInformationComponent.Disclaimer))
&& !entityView.ContainsProperty(nameof(PromotionAdditionalInformationComponent.Combinable)))
{
entityView.Properties.Add(
new ViewProperty
{
Name = nameof(PromotionAdditionalInformationComponent.Disclaimer),
RawValue = component.Disclaimer,
IsReadOnly = false,
IsRequired = true,
OriginalType = "string"
});
ViewProperty promotionTypes =
new ViewProperty
{
Name = nameof(PromotionAdditionalInformationComponent.PromotionType),
RawValue = component.PromotionType.ToString(),
IsReadOnly = false,
IsRequired = true,
UiType = "Dropdown",
DisplayName = component.GetDisplayName(nameof(PromotionAdditionalInformationComponent.PromotionType))
};
var promotionTypeList = new List();
promotionTypeList.Add(new Selection() { Name = "None", IsDefault = true, DisplayName = "None" });
promotionTypeList.Add(new Selection() { Name = "Default", IsDefault = false, DisplayName = "Default" });
promotionTypeList.Add(new Selection() { Name = "Shipping", IsDefault = false, DisplayName = "Shipping" });
promotionTypeList.Add(new Selection() { Name = "Autoshipment", IsDefault = false, DisplayName = "Autoshipment" });
promotionTypes.Policies = new List()
{
new AvailableSelectionsPolicy()
{
List = promotionTypeList
}
};
entityView.Properties.Add(promotionTypes);
entityView.Properties.Add(
new ViewProperty
{
Name = nameof(PromotionAdditionalInformationComponent.Combinable),
RawValue = component.Combinable,
IsReadOnly = false,
IsRequired = false,
});
}
}
}
}
Define an action policy to populate the new view and its settings:
namespace Plugin.Demo.Promotion.Pipelines.Blocks.EntityViews
{
[PipelineDisplayName(PromotionConstants.Pipelines.Blocks.PopulatePromotionAdditionalInformationActionsBlock)]
public class PopulatePromotionAdditionalInformationActionsBlock: PipelineBlock
{
public override Task Run(EntityView arg, CommercePipelineExecutionContext context)
{
Condition.Requires(arg).IsNotNull($"{Name}: The argument cannot be null.");
var viewsPolicy = context.GetPolicy();
if (string.IsNullOrEmpty(arg?.Name) ||
!arg.Name.Equals(viewsPolicy.Name, StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(arg);
}
var actionPolicy = arg.GetPolicy();
actionPolicy.Actions.Add(
new EntityActionView
{
Name = context.GetPolicy().Name,
DisplayName = context.GetPolicy().DisplayName,
Description = context.GetPolicy().Description,
IsEnabled = true,
EntityView = arg.Name,
Icon = context.GetPolicy().Icon
});
return Task.FromResult(arg);
}
}
}
Execute DoAction on click of the tick button which performs a transaction and validates and stores the information in the database:
namespace Plugin.Demo.Promotion.Pipelines.Blocks.DoActions
{
public class DoActionAddPromotionAdditionalInformationBlock :
PipelineBlock
{
private readonly Commands.AddPromotionAdditionalInformationCommand _addPromotionAdditionalInformationCommand;
public DoActionAddPromotionAdditionalInformationBlock(AddPromotionAdditionalInformationCommand addPromotionAdditionalInformationCommand)
: base()
{
this._addPromotionAdditionalInformationCommand = addPromotionAdditionalInformationCommand;
}
public override async Task Run(EntityView arg, CommercePipelineExecutionContext context)
{
var prmotionActionsPolicy = context.GetPolicy();
// Only proceed if the right action was invoked
if (string.IsNullOrEmpty(arg.Action) || !arg.Action.Equals("AddAdditionalInformation", StringComparison.OrdinalIgnoreCase))
{
return arg;
}
// Get the promotion item from the context
Sitecore.Commerce.Plugin.Promotions.Promotion promotion1 = context.CommerceContext.GetObject(x => x.Id.Equals(arg.EntityId) && x.EntityVersion == arg.EntityVersion);
if (promotion1 == null)
{
return arg;
}
component.Disclaimer =
arg.Properties.FirstOrDefault(x =>
x.Name.Equals(nameof(PromotionAdditionalInformationComponent.Disclaimer), StringComparison.OrdinalIgnoreCase))?.Value;
PromotionType tempPromotionType = PromotionType.None;
viewProperty =
arg.Properties.FirstOrDefault(x =>
x.Name.Equals(nameof(PromotionAdditionalInformationComponent.PromotionType), StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(viewProperty?.Value) && Enum.TryParse(viewProperty?.Value, out tempPromotionType))
component.PromotionType = tempPromotionType;
Sitecore.Commerce.Plugin.Promotions.Promotion promotion2 = await this._addPromotionAdditionalInformationCommand.Process(context.CommerceContext, promotion1,
, component.Disclaimer, component.PromotionType, component.Combinable
);
return arg;
}
}
}
namespace Plugin.Demo.Promotion.Commands
{
public class AddPromotionAdditionalInformationCommand: PromotionsCommerceCommand
{
private readonly IAddPromotionAdditionalInformationPipeline _addPromotionAdditionalInformationPipeline;
public AddPromotionAdditionalInformationCommand(
IAddPromotionAdditionalInformationPipeline addPromotionAdditionalInformationPipeline,
IFindEntityPipeline findEntityPipeline,
IServiceProvider serviceProvider)
: base(findEntityPipeline, serviceProvider)
{
this._addPromotionAdditionalInformationPipeline = addPromotionAdditionalInformationPipeline;
}
public virtual async Task Process(
CommerceContext commerceContext,
Sitecore.Commerce.Plugin.Promotions.Promotion promotion,
string disclaimer="",
PromotionType promotionType = PromotionType.None,
bool combinable = false)
{
AddPromotionAdditionalInformationCommand addPromotionAdditionalInformationCommand = this;
Sitecore.Commerce.Plugin.Promotions.Promotion result = (Sitecore.Commerce.Plugin.Promotions.Promotion)null;
Sitecore.Commerce.Plugin.Promotions.Promotion promotion1;
using (CommandActivity.Start(commerceContext, (CommerceCommand)addPromotionAdditionalInformationCommand))
{
CommercePipelineExecutionContextOptions contextOptions = commerceContext.GetPipelineContextOptions();
using (CommandActivity.Start(commerceContext, (CommerceCommand)addPromotionAdditionalInformationCommand))
{
await addPromotionAdditionalInformationCommand.PerformTransaction(commerceContext, (Func)(async () =>
{
CommercePipelineExecutionContextOptions pipelineContextOptions = commerceContext.GetPipelineContextOptions();
result = await this._addPromotionAdditionalInformationPipeline.Run(new PromotionAdditionalInformationArgument(promotion, disclaimer, promotionType, combinable), (IPipelineExecutionContextOptions)pipelineContextOptions);
}));
promotion1 = result;
}
return promotion1;
}
}
}
}
namespace Plugin.Demo.Promotion.Pipelines.Blocks
{
public class AddAdditionalPromotionInformationBlock: PipelineBlock
{
public override async Task Run(
PromotionAdditionalInformationArgument arg,
CommercePipelineExecutionContext context)
{
AddAdditionalPromotionInformationBlock additionalPromotionInformationBlock = this;
// ISSUE: explicit non-virtual call
Condition.Requires(arg).IsNotNull((additionalPromotionInformationBlock.Name) + ": The block argument cannot be null.");
// ISSUE: explicit non-virtual call
Condition.Requires(arg.Promotion).IsNotNull((additionalPromotionInformationBlock.Name) + ": The promotion cannot be null.");
// ISSUE: explicit non-virtual call
Condition.Requires(arg.Disclaimer).IsNotNullOrEmpty((additionalPromotionInformationBlock.Name) + ": The disclaimer cannot be null or empty.");
Sitecore.Commerce.Plugin.Promotions.Promotion promotion = arg.Promotion;
CommercePipelineExecutionContext executionContext;
if (!promotion.IsDraft(context.CommerceContext))
{
executionContext = context;
CommerceContext commerceContext = context.CommerceContext;
string error = context.GetPolicy().Error;
object[] args = new object[1]
{
(object) promotion.FriendlyId
};
string defaultMessage = "'" + promotion.FriendlyId + "' cannot be edited or deleted because is approved.";
executionContext.Abort(await commerceContext.AddMessage(error, "EntityIsApproved", args, defaultMessage), (object)context);
executionContext = (CommercePipelineExecutionContext)null;
return (Sitecore.Commerce.Plugin.Promotions.Promotion)null;
}
PromotionAdditionalInformationComponent promotionAdditionalInformationComponent = promotion.GetComponent();
return promotion;
}
}
}