In today’s post, I’d like to cover two scenarios: when we want to customize the warning messages displayed to the content authors when they are deleting any Sitecore page using Content Editor; and when we want to customize the confirm/delete dialog message when deleting a page through Content Editor.
Scenario 1 – Customizing the warning message when a content author wants to delete an item
By default, when a content author selects an item to be deleted they are prompted with the following message:
So, what if you wanted to show a warning message and have custom business logic associated with that? For example, showing a custom warning message and enabling to only delete if there are custom conditions met? We’ve run into such a scenario in one of our projects for a cluster implementation where even though the content author had permissions to delete a specific item, they could only do so if certain conditions were met. So to inform our authors, we validated such a scenario when they were trying to delete an item and displayed a warning message that was more appropriate.
In this scenario, the content authors are trying to delete a page and Sitecore has a processor which takes care of that called uiDeleteItems. That processor executes a few processors connected to it which essentially takes care of the default behavior we are used to seeing in Sitecore when deleting an item.
So in order to customize it, we need to patch a custom processor into uiDeleteItems which calls our business logic before it deletes, and in case the response returns that the item cannot be deleted we can abort the pipeline and display a custom warning. Simple right?
Here’s how we do this:
a) Patch the processor into the uiDeleteItems processor. How? By patching into a config file the definition of your custom processor, but remember it needs to happen before the delete action happens – so patch it as the first processor to execute:
<configuration> <sitecore> <processors> <uiDeleteItems> <processor type="FooNamespace.ValidateDeleteConditions, Foo.Bar" method="Confirm" mode="on" resolve="true" patch:before="*" /> </uiDeleteItems> </processors> </sitecore> </configuration>
b) Define the implementation of ValidateDeleteConditions class:
public class ValidateDeleteConditions { private IMyService MyService { get; set; } public ValidateDeleteConditions(IMyService myService) { MyService = myService; } /// <summary> /// This is the entry point method from the processor /// </summary> public void Confirm(ClientPipelineArgs args) { Log.Debug("User has triggered a delete", args); if (args?.Properties == null || args.Parameters["database"] == null || args.Parameters["items"] == null) return; //gets item being deleted var item = GetItem(args); if (item == null) return; ConfirmItem(args, item); } public void ConfirmItem(ClientPipelineArgs args, Item item) { string errorMessage; //Validates whether the item can be deleted. If not displays alert with the error message coming from the dictionary if (!args.IsPostBack && !MyService.CanDeleteItem(item, out errorMessage)) { if (HttpContext.Current != null && Sitecore.Context.ClientPage?.ClientResponse != null) Sitecore.Context.ClientPage.ClientResponse.Alert(Translate.Text(errorMessage)); args.WaitForPostBack(); } } protected Item GetItem(ClientPipelineArgs args) { //for delete command the item is passed in an array pipe separated so get the first to delete it var itemId = new ListString(args.Parameters["items"], '|').FirstOrDefault(); if (String.IsNullOrWhiteSpace(itemId)) return null; Log.Debug("Item being deleted: " + itemId, args); return Database.GetDatabase("master").GetItem(new ID(itemId)); } }
Ok, let’s understand what’s happening here.
The Confirm method is the entry point for our processor. All it does is get the item being deleted and call the ConfirmItem method. The ConfirmItem method invokes our custom service, which should return a boolean whether the item can be deleted or not. It also returns the custom dictionary error message which is captured in the errorMessage variable.
If it can’t be deleted, an alert is displayed with the error message being shown to the user and the delete is aborted. Next, we call the args.WaitForPostBack method which will prevent the pipeline from executing to the end. A similar approach could also be done by calling the args.AbortPipeline method which would stop the execution of it(see below in Scenario 2).
Scenario 2 – Customizing the confirm message when content authors want to delete an item by extending Sitecore’s processor
In this case, we will take a slightly different approach than the first scenario. Instead of creating a new processor which handles all the logic we will extend Sitecore’s processor and hook our business logic into it. In this case, we want to display a custom message for a specific section of the website. So only items within that structure get a custom error message – otherwise the default behavior needs to be displayed. It could be any business scenario, but for the sake of this discussion let’s assume that this is the requirement.
Just like in scenario 1, we will need to patch a processor, but in this case we will replace Sitecore’s processor with ours. The processor we need to replace is:
<processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="Confirm" />
To do so we need to use a patch instead approach for the type and method just like:
<processor type="FooNamespace.DeleteConfirm, Foo.Bar" method="Confirm" mode="on" resolve="true" patch:instead="processor[@type='Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel' and @method='Confirm']" <message>DeleteMaster</message> </processor>
Note how we are patching using patch instead of using the type and the method by combining the ‘and’ clause. This is important since there are other processors with the same type but different methods so this will patch the right one.
Now just like described in scenario 1, we need to implement the custom processor – but this time it will extend Sitecore’s out-of-the-box capability.
Here’s the processor implementation:
public class DeleteConfirm : Sitecore.Shell.Framework.Pipelines.DeleteItems { public string Message { get; set; } public ICustomManager CustomManager { get; set; } public DeleteConfirm(ICustomManager customManager) { CustomManager = customManager; } public override void Confirm(ClientPipelineArgs args) { Log.Debug("User has triggered a delete", args); if (args?.Properties == null || args.Parameters["database"] == null || args.Parameters["items"] == null) return; //gets item being deleted var item = GetItem(args); if (item == null) return; if (CustomManager.IsItemInSection(item)) { //confirms item is in specific section and handles through custom delete confirm message ConfirmItem(args, item); } else { //Not in custom section so fallback to default delete behavior base.Confirm(args); } } public void ConfirmItem(ClientPipelineArgs args, Item item) { if (!args.IsPostBack) { if (HttpContext.Current != null && Sitecore.Context.ClientPage?.ClientResponse != null) { Sitecore.Context.ClientPage.ClientResponse.Confirm(Translate.Text(Message)); } args.WaitForPostBack(); } else { Log.Debug("Confirm dialog response: " + args.Result); if (args.Result.Equals("no", StringComparison.InvariantCultureIgnoreCase) || args.Result == "undefined") { args.AbortPipeline(); } } } protected Item GetItem(ClientPipelineArgs args) { //for delete command the item is passed in an array pipe separated so get the first to delete it var itemId = new ListString(args.Parameters["items"], '|').FirstOrDefault(); if (String.IsNullOrWhiteSpace(itemId)) return null; Log.Debug("Item being deleted: " + itemId, args); return Database.GetDatabase("master").GetItem(new ID(itemId)); }
Ok, lets understand what’s happening here as well.
If you compare both processors implementation classes from scenarios 1 and 2, you will see that they are similar – but with a few differences:
- On the Confirm method, we check whether the item is in section and if it not we fall back to Sitecore’s out-of-the-box implementation
- The ConfirmItem method displays a confirm message instead of an alert causing the user to see “Ok” and “Cancel” buttons on the dialog
- We validate whether the execution is being done in a postback. If not, show the dialog using the message provided in the property. Read the message from the dictionary as per this implementation. We also call the WaitForPostback method. In this case, we need to use WaitForPostback as we are waiting for an action from the user: confirm or cancel
- We capture on the ConfirmItem method whether the user has clicked “Ok” or “Cancel”. If they clicked the “Cancel” button, we abort the pipeline. If they clicked the “Ok” button, we just let the pipeline execute and Sitecore will take care of deleting it 🙂
In this scenario, if I delete an item in the specific section of the site I would get a confirm message like this:
That’s all for today folks. Happy Sitecoring!