Second Life of a Hungarian SharePoint Geek

August 29, 2016

Permission-based Rendering Templates, Part 1: The Asynchronous Solution

Filed under: CSR, JavaScript, jQuery, JSCOM, SP 2013 — Tags: , , , , — Peter Holpar @ 21:49

Recently I read a question on SharePoint StackExchange about how one can restrict the available options in a choice field based on the group membership of the current user. My answer was to create a custom client rendering templates (CSR) and set it via the JSLink property of the choice field. If you are new in the usage of custom rendering templates, you can find several superb introduction on the topic on the web, like this one. At the time of my answer I had no sample code ready to publish (honestly, I was rather surprised that I have not found any such example on the web), but in the meantime I prepared two various implementations for the same problem. In this post I describe the first possible approach, an asynchronous solution based on the JavaScript client object model (JSCOM) and jQuery. The other solution will be discussed in a later post.

Both approaches share the same custom list: it is a list having a standard Title field and choice field called Status that has three state options: ‘Approved’, ‘Rejected’ and ‘Resubmit’. If the current use is member of a specific SharePoint group (let’s say ‘MyGroup’), the options ‘Approved’ and ‘Rejected’ should be displayed in the editable mode (that means on ‘EditForm’ and on ‘NewForm’), otherwise only the option ‘Resubmit’.

In our JavaScript rendering template we define the custom namespace, that includes the member properties and methods of the template. The same editFieldMethod function will be used in both editable modes. It’s simply a wrapper around the standard display template of choice fields (SPFieldChoice_Edit), the single extra work it performs is to store the ID of the corresponding HTML element (select in this case) into a member property called controlId. The standard format of the Id is NameOfTheChoiceField_GuidOfTheChoiceField__$DropDownChoice, for example in my case it is Status_fb5a9aac-5fdb-442e-96ac-ab7161cc4208_$DropDownChoice. We store its value to be able to find the HTML element and it children option elements via jQuery later in our asynchronous callback method.

  1. var restrictedValues1 = ['Approved', 'Rejected'];
  2. var restrictedValues2 = ['Resubmit'];
  3.  
  4. var custom = custom || {};
  5.  
  6. custom.controlId = null;
  7.  
  8. custom.editFieldMethod = function (ctx) {
  9.     var fieldSchema = ctx.CurrentFieldSchema;
  10.     custom.controlId = fieldSchema.Name + '_' + fieldSchema.Id + '_$DropDownChoice';
  11.     var html = SPFieldChoice_Edit(ctx);
  12.     return html;
  13. }

We created a simple escapeForJQuery helper function to escape the dollar sign ($) in the ID, as I found IE 11 and  jQuery have issues with that character when used in selectors.

  1. custom.escapeForJQuery = function (value) {
  2.     var newValue = value.replace(/\$/g, "\\$");
  3.     return newValue;
  4. }

Note: you might have problems with the underscore (_) as well, especially if you use old browser versions, however I have not experienced such problems.In this case you should extend the escapeForJQuery helper function. See this guide:

Given this fact, authors who write CSS often attempt to employ the underscore in a similar fashion when creating class and ID names. This should not be done. Although underscores are, as of this writing, technically permitted in class and ID names, there are many historical and practical reasons why they should be avoided.

We utilize the escapeForJQuery function in our next helper function. The hideOptions method hides those options of a specific HTML element with ID specified in the ctrlId parameter that have any of the the values specified in the restrictedValues array parameter:

  1. custom.hideOptions = function (ctrlId, restrictedValues) {
  2.     restrictedValues.forEach(function (rv) {
  3.         var selector = "#" + custom.escapeForJQuery(ctrlId) + " option[value='" + custom.escapeForJQuery(rv) + "']";
  4.         $(selector).remove();
  5.     });        
  6. }

We use a third helper function called isCurrentUserMemberOfGroup to determine via CSOM if the current user is member of a group. This function – borrowed from this answer – has two parameters: the name of the group (groupName) and a callback method (OnComplete).

  1. custom.isCurrentUserMemberOfGroup = function (groupName, OnComplete) {
  2.  
  3.     var clientContext = new SP.ClientContext.get_current();
  4.     var currentUser = clientContext.get_web().get_currentUser();
  5.  
  6.     var userGroups = currentUser.get_groups();
  7.     clientContext.load(userGroups);
  8.  
  9.     clientContext.executeQueryAsync(OnSuccess, OnFailure);
  10.  
  11.     function OnSuccess(sender, args) {
  12.         var isMember = false;
  13.         var groupsEnumerator = userGroups.getEnumerator();
  14.         while (groupsEnumerator.moveNext()) {
  15.             var group = groupsEnumerator.get_current();
  16.             if (group.get_title() == groupName) {
  17.                 isMember = true;
  18.                 break;
  19.             }
  20.         }
  21.  
  22.         OnComplete(isMember);
  23.     }
  24.  
  25.     function OnFailure(sender, args) {
  26.         OnComplete(false);
  27.     }
  28. }

The isCurrentUserMemberOfGroup function is invoked by the applyPermissions function. In the callback function we hide the adequate options based on the group membership of the user.

  1. var adminGroup = "MyGroup";
  2.  
  3. custom.applyPermissions = function (ctx) {
  4.     custom.isCurrentUserMemberOfGroup(adminGroup, function (isCurrentUserInGroup) {
  5.         console.log("Current user is member of group '" + adminGroup + "': " + isCurrentUserInGroup);
  6.  
  7.         if (custom.controlId) {
  8.             if (isCurrentUserInGroup) {
  9.                 custom.hideOptions(custom.controlId, restrictedValues1);
  10.             }
  11.             else {
  12.                 custom.hideOptions(custom.controlId, restrictedValues2);
  13.             }
  14.         }
  15.     });
  16. };

In our rendering template we register the custom editing method editFieldMethod, and set the applyPermissions function to be called as OnPostRender:

  1. var customOverrides = {};
  2. customOverrides.Templates = {};
  3.  
  4. customOverrides.Templates.Fields = {
  5.     'Status': {
  6.         'EditForm': custom.editFieldMethod,
  7.         'NewForm': custom.editFieldMethod
  8.     }
  9. };
  10.  
  11. customOverrides.Templates.OnPostRender = custom.applyPermissions;
  12.  
  13. SPClientTemplates.TemplateManager.RegisterTemplateOverrides(customOverrides);

The full source code of the rendering template introduced in this post:

  1. 'use strict';
  2.  
  3. (function () {
  4.  
  5.     var restrictedValues1 = ['Approved', 'Rejected'];
  6.     var restrictedValues2 = ['Resubmit'];
  7.  
  8.     var custom = custom || {};
  9.  
  10.     custom.controlId = null;
  11.  
  12.     custom.editFieldMethod = function (ctx) {
  13.         var fieldSchema = ctx.CurrentFieldSchema;
  14.         custom.controlId = fieldSchema.Name + '_' + fieldSchema.Id + '_$DropDownChoice';
  15.         var html = SPFieldChoice_Edit(ctx);
  16.         return html;
  17.     }
  18.  
  19.     custom.isCurrentUserMemberOfGroup = function (groupName, OnComplete) {
  20.  
  21.         var clientContext = new SP.ClientContext.get_current();
  22.         var currentUser = clientContext.get_web().get_currentUser();
  23.  
  24.         var userGroups = currentUser.get_groups();
  25.         clientContext.load(userGroups);
  26.  
  27.         clientContext.executeQueryAsync(OnSuccess, OnFailure);
  28.  
  29.         function OnSuccess(sender, args) {
  30.             var isMember = false;
  31.             var groupsEnumerator = userGroups.getEnumerator();
  32.             while (groupsEnumerator.moveNext()) {
  33.                 var group = groupsEnumerator.get_current();
  34.                 if (group.get_title() == groupName) {
  35.                     isMember = true;
  36.                     break;
  37.                 }
  38.             }
  39.  
  40.             OnComplete(isMember);
  41.         }
  42.  
  43.         function OnFailure(sender, args) {
  44.             OnComplete(false);
  45.         }
  46.     }
  47.  
  48.     custom.escapeForJQuery = function (value) {
  49.         var newValue = value.replace(/\$/g, "\\$");
  50.         return newValue;
  51.     }
  52.  
  53.     custom.hideOptions = function (ctrlId, restrictedValues) {
  54.         restrictedValues.forEach(function (rv) {
  55.             var selector = "#" + custom.escapeForJQuery(ctrlId) + " option[value='" + custom.escapeForJQuery(rv) + "']";
  56.             $(selector).remove();
  57.         });        
  58.     }
  59.  
  60.     var adminGroup = "MyGroup";
  61.  
  62.     custom.applyPermissions = function (ctx) {
  63.         custom.isCurrentUserMemberOfGroup(adminGroup, function (isCurrentUserInGroup) {
  64.             console.log("Current user is member of group '" + adminGroup + "': " + isCurrentUserInGroup);
  65.  
  66.             if (custom.controlId) {
  67.                 if (isCurrentUserInGroup) {
  68.                     custom.hideOptions(custom.controlId, restrictedValues1);
  69.                 }
  70.                 else {
  71.                     custom.hideOptions(custom.controlId, restrictedValues2);
  72.                 }
  73.             }
  74.         });
  75.     };
  76.     
  77.     var customOverrides = {};
  78.     customOverrides.Templates = {};
  79.  
  80.     customOverrides.Templates.Fields = {
  81.         'Status': {
  82.             'EditForm': custom.editFieldMethod,
  83.             'NewForm': custom.editFieldMethod
  84.         }
  85.     };
  86.  
  87.     customOverrides.Templates.OnPostRender = custom.applyPermissions;
  88.  
  89.     SPClientTemplates.TemplateManager.RegisterTemplateOverrides(customOverrides);
  90.     
  91. })();

Assuming your custom list is called PermBasedField, and both jQuery (in my case it is jquery-1.9.1.min.js) and our custom JavaScript rendering template (in my case it’s called permissionBasedFieldTemplate.js) are stored in the root of the Site Assets library of the root web, you can register the template using the following PowerShell script:

$web = Get-SPWeb http://YourSharePointSite
$list = $web.Lists["PermBasedField"]

$field = $list.Fields.GetFieldByInternalName("Status")
$field.JSLink = "~sitecollection/_layouts/15/sp.runtime.js|~sitecollection/_layouts/15/sp.js|~sitecollection/SiteAssets/jquery-1.9.1.min.js|~sitecollection/SiteAssets/permissionBasedFieldTemplate.js"
$field.Update()

Stay tuned, the second part of the post including a synchronous approach should come soon.

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: