Second Life of a Hungarian SharePoint Geek

October 16, 2011

Disabling item deletion at the SharePoint user interface

Filed under: Custom forms, ECB menu, JavaScript, Ribbon, SP 2010 — Tags: , , , , — Peter Holpar @ 21:58

Recently I worked on more projects where it was a nice-to-have requirement to disable deletion of specific items from a list.

Well, if you are at least a bit familiar with SharePoint your first idea might be the same as mine, that is to create a list item event receiver to capture ItemDeleting event and cancel it for your specific items. You are right, that should do the job, however I find this approach a bit ugly, as the users still have the option on the user interface to press the Delete Item button on the ribbon or select the Delete Item menu from the Edit Control Block (ECB) just to get that user-unfriendly warning from the event receiver cancellation to their face.

Wouldn’t it be much better to disable the corresponding user interface elements for the specific list items to prohibit deletion? As a safeguard, you can still apply an event receiver, but that would not be the standard way to stop users deleting the items from the browser.

That sounds great, but how to achieve the goal?

First, let’s see where that deletion options are for the user in a standard SharePoint web UI. The individual item can be deleted from the display and edit form ribbon and using the ECB menu of the selected item in a list view. Additionally, there is a Delete Item button on the ribbon of the list view that is to delete selected items.

In fact, disabling deletion on the UI of the display and edit form ribbon is pretty easy. You just need to use the TrimById method of the SPRibbon class in your form component passing the ID of the Delete Item button as the parameter, that is Ribbon.ListForm.Display.Manage.DeleteItem for the display form and  Ribbon.ListForm.Edit.Actions.DeleteItem for the edit form. If you need more info about how to create and inject your custom solution into the standard SharePoint form, read my former post.

In the overridden OnPreRender method of my customized ListFieldIterator I simply check if Delete Item button should be disabled, and if it should, then call the TrimById method using the right control ID.

  1. protected override void OnPreRender(EventArgs e)
  2. {
  3.     SPFormContext formContext = SPContext.Current.FormContext;
  4.  
  5.     if ((formContext.FormMode != SPControlMode.New) && (!IsDeletionEnabled))
  6.     {
  7.         String deleteButton = (formContext.FormMode == SPControlMode.Display) ?
  8.             "Ribbon.ListForm.Display.Manage.DeleteItem" : "Ribbon.ListForm.Edit.Actions.DeleteItem";
  9.         SPRibbon.GetCurrent(this.Page).TrimById(deleteButton);
  10.     }
  11. }

The IsDeletionEnabled is a custom property where we check (for example, based on list or list item properties or user permissions) if we would like to allow deletion or not.

  1. protected bool IsDeletionEnabled
  2. {
  3.     get
  4.     {
  5.         bool result = true;
  6.  
  7.         // your custom logic comes here
  8.         if (((String)Item["Title"]) == "Do not delete")
  9.         {
  10.             result = false;
  11.         }
  12.  
  13.         return result;
  14.     }
  15. }

That’s about the display / edit forms, let’s see the list view form, harder part of our goal.

As I’ve mentioned above there are two UI items to disable in the list view form, one is on the ribbon and another one in the ECB menu.

A serious difference between the UI components on the display / edit forms and the ones on the list view item forms is that in the first case we can check the condition and disable controls on the server side (we know which item the form is displayed for), while in the second case, user can select any items (or none of them) on the client side, so we should act accordingly on the client side without any postback of the page.

Unfortunately, I’ve started with the ribbon part and it caused me some headache and long hours spent unsuccessfully to play with ribbon extensions to check conditions and interact with the existing controls using jQuery and other tricks as well.

As none of my solutions for the list view ribbon worked perfectly, I switched to ECB.

When you need to work with the ECB menu, this series of post from Jan Tielens provides an excellent starting point. Although it is written originally for WSS 3.0, the content is still valid for SharePoint 2010 as well.

Since my goal was not to add custom items to the menu, but rather to delete an existing one, the trivial solution was to create my custom version of AddListMenuItems. The original version of this JavaScript method (assuming English – United States locale ID 1033) can be found in the 14\TEMPLATE\LAYOUTS\1033\CORE.JS file, but to have a more readable one, get it from CORE.debug.js instead.

Note: There are tons of posts on the web about how to inject JavaScript code into your page. Most of them favor to add a Content Editor Web Part to the page and paste your code into that. However, I feel this approach to be more appropriate for a  power-user, and usually add my scripts through a ScriptLink CustomAction or via an AdditionalPageHead. I found these methods are easier to integrate into and deploy from a Visual Studio 2010 solution, but they have a drawback that they do not affect only the target page. If you need a targeted solution you should first verify  in the code of the AdditionalPageHead if it is the right list then register the script using Page.ClientScript.RegisterClientScriptBlock, or through one of the several version of the static Register* method of ScriptLink  class.

  1. SPContext context = SPContext.Current;
  2.  
  3. if ((context != null) && (context.List != null) && (context.List.Title == "YourList"))
  4. {
  5.     // register your script here
  6. }

To target the script in the case of a ScriptLink CustomAction you can apply the method described in this post.

Back to the topic, whatever solution you choose to reference and deploy the JavaScript code, you should alter the copy of the original version of the AddListMenuItems method as described below. 

Note: It is important not to tamper with the original version as it affects all of your SharePoint sites and what might be even worse, it is not a supported modification.

Look up the following code section in the AddListMenuItems method. It is responsible for creation of the Delete Item menu item in ECB.

  1. if (currentItemID.indexOf(".0.") < 0 && HasRights(0x0, 0x8)
  2.       && !currentItemIsEventsExcp)
  3. {
  4.     if (ctx.listBaseType==4)
  5.         strDisplayText=L_DeleteResponse_Text;
  6.     else
  7.         strDisplayText=L_DeleteItem_Text;
  8.     strAction="DeleteListItem()";
  9.     strImagePath=ctx.imagesPath+"delitem.gif";
  10.     menuOption=CAMOpt(m, strDisplayText, strAction, strImagePath, null, 1180);
  11.     CUIInfo(menuOption, "Delete", ["Delete"]);
  12.     menuOption.id="ID_DeleteItem";
  13.     CUIInfo(menuOption, "Delete", ["Delete"]);
  14. }

Let’s modify that code a bit as shown here:

  1. if (currentItemID.indexOf(".0.") < 0 && HasRights(0x0, 0x8)
  2.       && !currentItemIsEventsExcp) {
  3.     if (allowDeletion(currentItemID)) {
  4.         if (ctx.listBaseType == 4)
  5.             strDisplayText = L_DeleteResponse_Text;
  6.         else
  7.             strDisplayText = L_DeleteItem_Text;
  8.         strAction = "DeleteListItem()";
  9.         strImagePath = ctx.imagesPath + "delitem.gif";
  10.         menuOption = CAMOpt(m, strDisplayText, strAction, strImagePath, null, 1180);
  11.         CUIInfo(menuOption, "Delete", ["Delete"]);
  12.         menuOption.id = "ID_DeleteItem";
  13.         CUIInfo(menuOption, "Delete", ["Delete"]);
  14.  
  15.     }
  16. }

Yes, the only difference is the extra condition that calls allowDeletion method passing the currentItemID parameter. The value of this variable is the ID of the actual list item.

How you implement allowDeletion in your JavaScript is up to your business requirements. Here are two examples of what can be achieved simply:

Disable deleting items having a specific ID. Probably not the most useful example but the simplest one for sure.

  1. function allowDeletion(itemID) {
  2.     return ((itemID == 1) || (itemID == 3));
  3. }

Disable deleting folders. Useful if you deployed a folder structure into your list and would like to prohibit users from destroying that.

  1. function allowDeletion(itemID) {
  2.  
  3.     var selectedItems = SP.ListOperation.Selection.getSelectedItems();
  4.     var result = false;
  5.  
  6.     for (var i = 0; i < selectedItems.length; i++) {
  7.  
  8.         if (selectedItems[i].id == itemID) {
  9.             // true (enable deletion), if it is a list item, not a folder
  10.             result = (selectedItems[i].fsObjType == 0);
  11.             break;
  12.         }
  13.     }
  14.  
  15.     return result;
  16.  
  17. }

If you need more sophisticated logic, like doing comparison with list item field values, you should apply some trick using WCF Data Services or even hacks to utilize the client object model but more about that in a forthcoming post.

And the most interesting part (at least, for me) of the story is coming now.

After deploying my script and testing the solution I realized that if you select an item that has its Delete Item menu removed from the ECB menu then Delete Item button on the ribbon is disabled. Nice! That is what I worked a lot earlier for without success. And that is the best that if you select multiple items from the list, and deletion is disabled at least for one of them the corresponding ribbon button is disabled as well.

To understand what happens behind the curtains I added some alert statements to my scripts (the simplest way of JavaScript debugging). It turned out, that the AddListMenuItems method (and our allowDeletion method as well) called once for each list item when they got selected first time. It means if you select multiple items by checking the topmost checkbox in the header, the methods are triggered for each items one by one. If a selected item is deselected, and selected the second (or even more) times, the methods are not triggered once more, so the state of the Delete Item menu won’t change. If it was removed (not added to be exact) the first time, it remains removed. If it was added the first time, it remain there. Of course, a page reload resets this kind of caching.

I removed (commented out) the script lines from this part of the function one by one, and found that the menu item actually gets added by this line:

menuOption = CAMOpt(m, strDisplayText, strAction, strImagePath, null, 1180);

However, if I remove this method call (actually, there are two of the same line there):

CUIInfo(menuOption, "Delete", ["Delete"]);

then the Delete Item ribbon button won’t be enabled, even if there are items to delete. In this case, the items can be still deleted through ECB’s Delete Item menu.

If you check the “signiture” of this method it is:

function CUIInfo(menuItem, command, enabledCommands)

That means it registers the “Delete” command for our menu item and sets the “Delete” command as an enabled command as well. I have not tracked the code any further, but it seems that the Delete Item button on the ribbon is enabled only if all of the selected list items have a menu item with enabled commands (CUIEnabledCommands property) containing the “Delete” command. Seems to be a very interesting infrastructure behind this.

Note: The method described here does not protect you from items being deleted in the Datasheet View or other customized views, not to mention deleting items from custom code or web service / WCF calls (like client object model WCF Data Services etc.).

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: