Second Life of a Hungarian SharePoint Geek

October 16, 2011

Creating more advanced conditions for your ECB menus through jQuery and synchronous Client Object Model / WCF Data Services calls

In my last post I showed you how to alter the Edit Control Block (ECB) menu of SharePoint 2010 based on simple conditions. As I described, if you need to use more advanced conditions, for example based on list item field values or other information not available on the client side, you should apply a few tricks or even hacks.

For these advanced solutions it is useful to provide the context to our allowDeletion method through the ctx parameter, so we add this one to the parameter list of the method.

It means that our original condition in method AddListMenuItems is changed from

if (allowDeletion(currentItemID))

to

if (allowDeletion(ctx, currentItemID))

In the simplest case, the information is already there at the page on client side, you simply have to find the way to get it. Checking the HTML source of the page or using the Developer Tools in the Tools menu of Internet Explorer (you can press F12 as a shortcut key) usually helps you to find the right track.

For example, if you need the title of the item, that is included in the LinkTitle field (as we have an ECB menu linked to the title) for each items. When looking up the right item, we should use the ID of the item (passed as the itemID parameter) and the ID of our list view (available in ctx.ctxId).

Note: Although it should be evident, your page must load the jQuery library before you can use jQuery methods. Similarly, the ECMAScript Client Object Model library must be loaded before you reference its objects in methods later in the posts.

So if we would like to allow deletion only for items having title beginning with ‘V’ then we need to apply a method like this:

  1. function allowDeletion(ctx, itemID) {
  2.  
  3.     var title = jQuery('div[Field="LinkTitle"][CTXName="ctx' + ctx.ctxId + '"][id="' + itemID + '"]').text();
  4.  
  5.     return (title.toUpperCase().startsWith("V"));
  6.  
  7. }

Next step is to see how to get the information if it is not available on the client side, but included in the list items on the server side. My first idea was to use the ECMAScript Client Object Model, however it turned out quickly that is not the best choice, as the AddListMenuItems method does not wait for the asynchronous reply supported by the client OM to be returned. Although I created a dirty workaround for that, I will show you that later.

Another alternative is to use REST protocol through the WCF Data Services.

As mentioned, our call must be synchronous, so we have to use jQuery.ajax specifying async: false. The next code snippet shows an example using the same condition applied above, that is title of the item must be started with ‘V’ to enable deletion.

  1. function allowDeletion(ctx, itemID) {
  2.  
  3.     itemUrl = ctx.HttpRoot + "/_vti_bin/listdata.svc/" + ctx.ListTitle +"(" + itemID + ")?$select=Title"    
  4.  
  5.     var title = "";
  6.  
  7.     jQuery.ajax(
  8.     {
  9.         type: 'GET',
  10.         url: itemUrl,
  11.         dataType: 'json',
  12.         success: function (result) {
  13.             if (result.isOk != false) {
  14.                 title = result.d.Title;
  15.             }
  16.         },
  17.         data: {},
  18.         async: false
  19.     });
  20.  
  21.     return (title.toUpperCase().startsWith("V"));
  22.  
  23. }

Note: When working with jQuery and WCF Data Services, it is useful to know about the parsererror issue with field values containing apostrophe (single quote) and how to fix it.

It is important to note that you are not restricted to the actual list only. With a bit of additional complexity you can query related lists as well using $expand and the lookup field / user field IDs in the current list. If you are unsure how to compose the URL for you request, I suggest you to try to create the filter first in C# using the LINQ syntax, then use Fiddler to capture the request or apply this simple trick from Sahil Malik.

If network bandwidth and speed are limited, then waiting for the synchronous result will cause issues via blocking the UI thread of the browser. If that is the case you should consider “pre-caching” the data required for checks on page load through a single request. If the item count of the list is limited, then you can cache all of the data (in our case, the IDs and the related titles of the items is needed), in the case of a larger list, you should get only the items displayed in the current page of the view. To get the IDs of the items on the page you should run a jQuery select similar to the one we used to get the title in the first example, and submit the REST request using a complex condition.

Last, I would like to show you my workaround for calling ECMAScript Client Object Model synchronously. As you might now, it is officially / theoretically not possible. I found that it can be done technologically, although I had to spend a few hours Fiddlering and digging into the internals of the ECMAScript Client OM (SP.Runtime.debug.js), comparing the JavaScript methods to the ones in the managed Client OM classes using Reflector. If you demand more information about it I can give you more details, but now I publish it “as is”.

To build up the request I use the “traditional” ECMAScript Client Object Model (that, of course, should be already loaded before our script), but before sending it I get the built-up request in XML format from the internal methods of the OM, and send the request through jQuery.ajax in synchronous mode, just as like the case of WCF Data Services. The JSON result is “loaded” into JavaScript objects.

Be aware that the method described here don’t use the public, documented interfaces, so it is probably not supported by MS, and suggested to be used only as an experiment. There is no guarantee that it will work for you, especially if our environments are at different SP / cumulative update level.

  1. function allowDeletion(ctx, itemID) {
  2.  
  3.     var title = "";
  4.  
  5.     try {
  6.         var context = SP.ClientContext.get_current();
  7.         var web = context.get_web();
  8.         var selectedListId = SP.ListOperation.Selection.getSelectedList();
  9.         var selectedListItem = web.get_lists().getById(selectedListId).getItemById(itemID);
  10.         context.load(selectedListItem, "Title");
  11.  
  12.         // start hacking
  13.         var pendingRequest = context.get_pendingRequest();
  14.         var webRequest = pendingRequest.get_webRequest();
  15.  
  16.         // get the request XML
  17.         var body = pendingRequest.$24_0().toString();
  18.  
  19.         // get the URL of client.svc
  20.         var url = webRequest.get_url();
  21.  
  22.         // "initialize" request
  23.         SP.ClientRequest.$1T(webRequest);
  24.  
  25.         // we should add digest later to the request as an HTTP header (see below)
  26.         var digest = webRequest.get_headers()['X-RequestDigest'];
  27.  
  28.         jQuery.ajax(
  29.         {
  30.             type: "POST",
  31.             data: body,
  32.             url: ctx.HttpRoot + url,
  33.             success: function (result) {
  34.                 if (result.isOk != false) {
  35.                     title = result[result.length – 1].Title;
  36.                 }
  37.             },
  38.             headers: {
  39.                 "x-requestdigest": digest
  40.             },
  41.             contentType: "application/x-www-form-urlencoded",
  42.             async: false
  43.         });
  44.  
  45.     }
  46.     catch (e) {
  47.         alert("Error: " + e);
  48.     }
  49.  
  50.     return (title.toUpperCase().startsWith("V"));
  51.  
  52. }

BTW, the example above achieves the same, it allows deletion only for items having title beginning with ‘V’. Similarly to the former WCF DS example, the synchronous request through the network blocks the UI thread, and might require pre-caching in case of a slow network.

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

Blog at WordPress.com.

%d bloggers like this: