Second Life of a Hungarian SharePoint Geek

July 24, 2011

Hiding ECB custom actions based on specific list properties using the client object model

There might be cases when you need to display an Edit Control Block custom action only when specific circumstances are met. These conditions can be list or user specific or more generic, like the menu is visible only for a given time interval.

Unfortunately the declarative description of a custom action allows only a very limited set of conditions through the RegistrationType, RegistrationId, RequiredAdmin, RequireSiteAdministrator and Rights attributes. If you have more complex requirements, you probably need to find a more advanced solution. The client object model in SharePoint 2010 allows you to create some really powerful customizations.

In this post I show you how to display an ECB custom action menu for lists having a specific property, but you can use the very same technique if you need to show / hide the menu based on the properties of the current user or even more complex SharePoint properties. This example shows a “Publish” ECB menu item for each Custom List as long as versioning is enabled for the list.

In the first step we register our ECB menu item for the Custom List list type.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <CustomAction Description="Custom Action Sample for Versioned Lists"
  4.               Id="JCOMCustomAction.PublishData"
  5.               Location="EditControlBlock"
  6.               ImageUrl="/_layouts/images/JCOMCustomActionOnVersionedLists/exclamation.png"
  7.               Sequence="1000"
  8.               Title="Publish"
  9.               RegistrationType="List"
  10.               RegistrationId="100" >
  11.     <UrlAction Url="javascript:alert('Item {ItemId} in list {ListId} is published')"/>
  12.   </CustomAction>
  13. </Elements>

We also register three ScriptLink custom action to inject a reference to the jQuery library, to our custom script file CustomAction.js, and a ScriptBlock that calls our custom checkListProps method after the page and the client object model library (sp.js) is loaded. It’s not enough to wait for the page load. If we don’t wait for the client object model library, and try to call our custom script immediately on page load, all of the references to the client library will be unknown to the script runtime.

We can wait for the page load using the jQuery(document).ready, and for sp.js file to be loaded using the ExecuteOrDelayUntilScriptLoaded method. Important, that one should use only the name of the method to call as the first parameter (like checkListProps and not checkListProps()).

Note, that since I called the jQuery noConflict() method to avoid possible conflicts, I have to explicitly type jQuery instead of the $ shortcut here and later in my custom script as well.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <CustomAction
  4.       ScriptSrc="JCOMCustomActionOnVersionedLists/jquery-1.6.1.min.js"
  5.       Location="ScriptLink"
  6.       Sequence="1000"
  7.       />
  8.   
  9.   <CustomAction
  10.       Location="ScriptLink"
  11.       ScriptSrc="JCOMCustomActionOnVersionedLists/CustomAction.js"
  12.       Sequence="1001"
  13.       />
  14.  
  15.   <CustomAction
  16.       Location="ScriptLink"
  17.       ScriptBlock="$.noConflict(); jQuery(document).ready(function() { ExecuteOrDelayUntilScriptLoaded(checkListProps, 'sp.js'); });"
  18.       Sequence="1002"
  19.       />
  20.  
  21. </Elements>

The most interesting part of the solution is our custom script file. The skeleton of the script is similar to my former solution.

  1. var inProgress = false;
  2. var list;
  3. var selectedListId;
  4.  
  5. function checkListProps() {
  6.     if (!inProgress) {
  7.         try {
  8.             inProgress = true;            
  9.             var selection = SP.ListOperation.Selection;
  10.             if (selection != null) {
  11.                 this.selectedListId = selection.getSelectedList();
  12.                 if (selectedListId != null) {
  13.                     var context = SP.ClientContext.get_current();
  14.                     var web = context.get_web();
  15.                     this.list = web.get_lists().getById(this.selectedListId);
  16.                     context.load(this.list);
  17.                     context.executeQueryAsync(Function.createDelegate(this, this.gotList), Function.createDelegate(this, this.failed));
  18.                 }
  19.             }
  20.         }
  21.         catch (e) {
  22.             alert("Error: " + e);
  23.             inProgress = false;
  24.         }
  25.     }
  26. }
  27.  
  28. function failed(sender, args) {
  29.     alert("Operation failed: " + args.get_message());
  30.     inProgress = false;
  31. }
  32.  
  33. jQuery.expr[":"].econtains = function (obj, index, meta, stack) { return (obj.textContent || obj.innerText || jQuery(obj).text() || "").toLowerCase() == meta[3].toLowerCase(); }
  34.  
  35. function gotList() {
  36.     if (!this.list.get_enableVersioning()) {                
  37.         
  38.         // just to be sure jQuery is loaded
  39.         if (jQuery) {
  40.  
  41.             var id = "ECBItems_" + this.selectedListId.toLowerCase();
  42.  
  43.             // this lookup does not work, probably due to braces in the ID
  44.             //jQuery("#" + id)
  45.  
  46.             // workaround
  47.             jQuery('div[id*="ECBItems"]').each(
  48.                 function () {
  49.                     if (jQuery(this).attr('id') == id) {
  50.                     // replace "Publish" to match the title of your ECB custom action
  51.                      jQuery(this).find('div div:first-child:econtains("Publish")').parent().remove(); } });  
  52.         }
  53.  
  54.      }
  55.      inProgress = false;
  56. }

The checkListProps method that we call after the page and the client object model script library is loaded first stores the current list ID in the selectedListId variable then gets a reference for the current list into the list variable using the list ID and the current web, and finally starts an asynchronous query to retrieve the list properties.

The failed method only displays the error message if the query fails.

The gotList method is called on query success. It checks if the versioning is enabled and if not, it removes the part of the ECB block that is responsible for displaying our custom action.

Finding the correct HTML element is a bit tricky. You can find another version in this MSDN forum thread for WSS 3.0, but I refactored this for SharePoint 2010 using jQuery to get somewhat more compact code.

The DIV tag that contains the ECB block has an ID like ECBItems_{62827211-6a39-4d9b-908f-d74ef14b2746}, where the GUID is the ID of the list. Unfortunately I was not able to get a reference for this kind of ID using jQuery, probably because the curly braces are not valid characters in the HTML ID attribute.

My workaround is to lookup HTML DIV elements with ID containing “ECBItems”, then iterate through the results using each(). If the ID is what we are looking for then we find the element whose first sub-DIV contains exactly the text of our custom action, and remove its parent node from the DOM.

If you are sure your page contains a single ECB block you can simply use this line:

$(‘[id*="ECBItems"]’).find(‘div div:first-child’).filter(function() {  return this.innerText == "Publish" }).parent().remove(); 

We need to check the first sub-DIV, not any arbitrary one, because the first one contains the text of the custom action, and we do not want to remove a possible another custom action with the same text in the DIV for UrlAction. We also have to do an exact match to avoid removing custom actions with similar texts. For example, the standard :contains() jQuery selector, would remove a Publish later menu too in our case. To force exact math I introduced the :econtains() selector.

You can download the sample solution from here.

To test the solution, I’ve created a Custom List instance with a few list items on my test site manually (not included in the sample code). After deploying the solution, the menu does not contain the Publish item.

image

Next, I set the list to support versioning:

image

When I was back to the list, the Publish item appeared in the ECB menu:

image

Advertisements

4 Comments »

  1. Hi,
    this article is really helpfull – thank you very much for sharing it.

    I tried to remove the “Workflow” item from the ECB menu; but this seems that it’s not working. Do you have an idea why this cannot be removed? It’s not listed in the jQuery(‘div[id*=”ECBItems”]’) collection.

    Do you have any idea? How can I solve that?
    Thanks!

    Comment by Fidy — August 3, 2011 @ 13:02

    • Hi Fidy,

      As I’ve described in my other post (Manipulating ECB menu items using jQuery), the ECBItems block is rendered by the SPCustomActionElement.RenderECBItemsAsHtml method and should contains ECB custom action elements. However, I think the menu itself may contain other, fixed item, ECB custom action items are only a subset of the menu items. There might be list pages where ECBItems block does not exist at all, but the ECB menu is still there with a limited set of items. I plan to blog about that soon. The “Workflow” item may be one, that is added by the system dynamically if it has sence in the context of the list. Hope it helps!

      Comment by Peter Holpar — August 5, 2011 @ 09:14

    • Just to add some tips to manipulate non-Custom Action based ECB menu items. First, I suggest you to read and understand Jan’s excellent posts about Customizing the SharePoint ECB with Javascript. Then you see that you can interpret ECB per-item menu rendering by injecting your own Custom_AddListMenuItems JavaScript function implementation. If you return true in this function then AddListMenuItems (core.js) stops creating the standard menu items. Unfortunately all of them, you cannot select the menu item you would like to remove. In this case you should copy the bulk of AddListMenuItems to your Custom_AddListMenuItems and remove the items you don’t need. If your customized Custom_AddListMenuItems version is injected only the form of the selected list / library (probably through a CEWP) then you are ready. However, if you injected the customization using a ScriptLink CustomAction, then it will be included into all pages. In this case you should implement the logic based on the context (ctx) to ensure the menu items are removed only for the selected list. For example, the method will do nothing just return false most of the time, but run the customized menu rendering code and return true in the special case(s).

      Comment by Peter Holpar — August 31, 2011 @ 13:19

  2. Great stuff, saved me a lot of time!

    Comment by GillouX — February 21, 2012 @ 09:24


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: