In my recent post I’ve illustrated how can you implement a permission-based custom rendering template using the JavaScript client object model (JSCOM) and jQuery. That rendering template was implemented using the standard asynchronous JavaScript patterns via a callback method to not block the UI thread of the browser. In a fast network (in a LAN, for example) however, a synchronous implementation can function as well. Although there are some unsupported methods to make a JSCOM request synchronously, the JavaScript client object model was designed for asynchronous usage (see its executeQueryAsync method). To send our requests synchronously, we utilize the REST / OData interface in this post, and send the requests via the ajax function of jQuery.
To understand the original requirements and the configuration (field and list names, etc.), I suggest to read the first part first.
To enable using of jQuery selectors containing the dollar sign ($), we use the same escapeForJQuery helper function that we’ve created for the first part.
var restrictedValues1 = ['Approved', 'Rejected'];
var restrictedValues2 = ['Resubmit'];
var custom = custom || {};
custom.controlId = null;
var adminGroup = "MyGroup";
custom.escapeForJQuery = function (value) {
var newValue = value.replace(/\$/g, "\\$");
return newValue;
}
Instead of simply wrapping the standard display template of choice fields (SPFieldChoice_Edit), the editFieldMethod function is responsible to get the HTML content of the field control, as it would be rendered without the customization by invoking the SPFieldChoice_Edit function, then we determine the group membership of the user by calling the synchronous isCurrentUserMemberOfGroup function (more about that a bit later), finally we alter the HTML content by hiding the adequate options by calling the hideOptions function (see it later as well).
var isCurrentUserInGroup = custom.isCurrentUserMemberOfGroup(adminGroup);
if (isCurrentUserInGroup) {
html = custom.hideOptions(html, custom.controlId, restrictedValues1);
}
else {
html = custom.hideOptions(html, custom.controlId, restrictedValues2);
}
return html;
}
The hideOptions function loads the HTML source of the control into the DOM and removes the options that should be hidden for the given group. Finally it returns the HTML source of the altered control:
custom.hideOptions = function (html, ctrlId, restrictedValues) {
The isCurrentUserMemberOfGroup function sends a synchronous REST request via the the ajax function of jQuery to determine the group membership of the current user:
var serverUrl = String.format("{0}//{1}", window.location.protocol, window.location.host);
custom.isCurrentUserMemberOfGroup = function (groupName) {
In this case we simply register the editFieldMethod for both the ‘EditForm’ and for the ‘NewForm’ mode of the Status field, there is no need for the OnPostRender method:
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 permissionBasedFieldTemplate2.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:
Note, that (in contrast to the script introduced in the first part of this post) there is no need for the JSCOM JavaScript files (sp.runtime.js and sp.js) in this case.
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.
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.
custom.escapeForJQuery = function (value) {
var newValue = value.replace(/\$/g, "\\$");
return newValue;
}
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:
custom.hideOptions = function (ctrlId, restrictedValues) {
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).
custom.isCurrentUserMemberOfGroup = function (groupName, OnComplete) {
var clientContext = new SP.ClientContext.get_current();
var currentUser = clientContext.get_web().get_currentUser();
var groupsEnumerator = userGroups.getEnumerator();
while (groupsEnumerator.moveNext()) {
var group = groupsEnumerator.get_current();
if (group.get_title() == groupName) {
isMember = true;
break;
}
}
OnComplete(isMember);
}
function OnFailure(sender, args) {
OnComplete(false);
}
}
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.
var adminGroup = "MyGroup";
custom.applyPermissions = function (ctx) {
custom.isCurrentUserMemberOfGroup(adminGroup, function (isCurrentUserInGroup) {
console.log("Current user is member of group '" + adminGroup + "': " + isCurrentUserInGroup);
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:
In my recent blog entry I’ve illustrated how to change the out-of –the-box display order of the enterprise custom fields in the “Details” web part on the Project Details page of Project Server. In this post I go one step further and show, how to display / hide the fields based on conditions. As in the previous case, I use jQuery in this case either to achieve the goal.
For the sake of example, let’s assume you have a few custom fields (in this case “Field1” and “Field2”) that should be displayed only for the members of a special SharePoint group called “Admins”.
The script we need in this case is displayed in the code snippet below:
var adminGroup = "Admins";
function isCurrentUserMemberOfGroup(groupName, OnComplete) {
var clientContext = new SP.ClientContext.get_current();
var currentUser = clientContext.get_web().get_currentUser();
We use the setFieldVisibility function to hide / display the fields. On the page load we hide the fields in the first step. To verify if the current user belongs to the group and to perform a custom action after the check I use the isCurrentUserMemberOfGroup function borrowed from Stack Overflow. Finally, we set the visibility of the field in the callback function based on the group membership of the current user.
Assuming this script is saved in the file DisplayFieldsForGroup.js in the Site Assets library of the PWA site, and the jquery-1.9.1.min.js can be found in that library as well, you can add a Script Editor web part to the Project Details page, and include these two lines in the web part to inject the functionality into the page:
The out-of-the-box version of the Project Details page of Project Server contains two web parts. On the top, there is a web part with the title “Basic Info” (implemented in class Microsoft.Office.Project.PWA.WebParts.ProjectFieldPart, assembly Microsoft.Office.Project.Server.PWA) that displays the core information about the project, like its name and description as well as start and finish date and the name of the project owner.
There is another web part at the bottom of the page. It is the “Details” web part (implemented in class Microsoft.Office.Project.PWA.WebParts.ProjectCustomFieldsPart, assembly Microsoft.Office.Project.Server.PWA), and it displays the enterprise custom fields defined for the Project entity type.
In the “Basic Info” web part you can select, which project fields should be displayed, as well as their order via the web part properties, as displayed on the screenshot below:
However you don’t have such control on the fields displayed by the “Details” web part, it simply displays all of the project-related enterprise custom fields.
In this post I show you how to control the display order of the fields in the “Details” web part via jQuery, illustrating the possibilities by using two real-world problem as example.
Problem 1
Assume you have defined (among others) the following custom fields for the Project entity:
AField
AnotherField
CompanyComment
CompanyName
DivisionComment
DivisionName
JustOneMoreField
LastField
What can you do, if you have a business requirement that dictates the following order for these fields?
AField
AnotherField
DivisionName
DivisionComment
CompanyName
CompanyComment
JustOneMoreField
LastField
The fields highlighted with bold should be in the specified order. The other enterprise fields (including the ones created later) will be displayed before or after the highlighted block and sorted alphabetically.
You have the option to remove the “Details” web part from the page, and include all of the fields you need in the “Basic Info” web part in the required order. This solution seems to be simple at first sight, but it has the drawback, that you lose the original page structure with the two web parts, and what even worse, you should add any new enterprise custom field to the “Basic Info” web part manually in the future.
Alternatively, you can use jQuery to achieve the required results within the “Details” web part.
The changeFieldOrder function below demonstrates how to accomplish this goal. This function invokes the getTRForField function to find the table row for the specified field. It looks up then the row that belongs to the field specified in the first member of the string array (fieldOrder) it receives as parameter, then looks up the other rows one after the other as well, and places them after the previous one.
Optionally, you can define further field order sets and call the changeFieldOrder function repeatedly using these field sets as parameter.
$(document).ready(startScript);
function getTRForField(parent, fieldName) {
var result = parent.find("h3.ms-accentText").filter(function () { return $(this).text() === fieldName; }).closest("tr").first();
return result;
}
function changeFieldOrder(fieldOrder) {
var wp = $("div[name='WPClass:ProjectFieldPart']").last();
var tbody = wp.find("tbody").first();
for (i = 1; i < fieldOrder.length; i++) {
var firstTR = getTRForField(tbody, fieldOrder[i – 1]);
var secondTR = getTRForField(tbody, fieldOrder[i]);
firstTR.after(secondTR);
}
}
function startScript() {
var fieldOrder1 = ["DivisionName", "DivisionComment", "CompanyName", "CompanyComment"];
// you can define further field orders if you need
// var fieldOrder2 = ["CustomField2", "CustomField1"];
changeFieldOrder(fieldOrder1);
// changeFieldOrder(fieldOrder2);
}
Assuming this script is saved in the file CustomFieldOrder.js in the Site Assets library of the PWA site, and the jquery-1.9.1.min.js can be found in that library as well, you can add a Script Editor web part to the Project Details page, and include these two lines in the web part to inject the field-sorting functionality into the page:
Assume you would like to display the fields in this order:
DivisionName
DivisionComment
CompanyName
CompanyComment
AField
AnotherField
JustOneMoreField
LastField
The fields highlighted with bold should be at the very top of the field list and in the specified order. The other enterprise fields (including the ones created later) will be displayed after the highlighted block and sorted alphabetically.
Again, you have the option to remove the “Details” web part from the page, and include all of the fields you need in the “Basic Info” web part in the required order, but in this case you have the same drawbacks, as in the first case above.
Instead of this, you can use the setFieldOrder function that invokes the same getTRForField function we saw in the example above. Then it looks up the row that belongs to the field specified in the first member of the string array (fieldOrder) it receives as parameter, put it at the first row of the table, then looks up the other rows one after the other as well, and places them after the previous one.
function getTRForField(parent, fieldName) {
var result = parent.find("h3.ms-accentText").filter(function () { return $(this).text() === fieldName; }).closest("tr").first();
return result;
}
function setFieldOrder(fieldOrder) {
var wp = $("div[name='WPClass:ProjectFieldPart']").last();
var tbody = wp.find("tbody").first();
var trLast;
for (i = 0; i < fieldOrder.length; i++) {
var tr = getTRForField(tbody, fieldOrder[i]);
if (!trLast) {
tr.prependTo(tbody);
trLast = tr;
}
else {
trLast.after(tr);
trLast = tr;
}
}
}
function startScript() {
var fieldOrder = ["DivisionName", "DivisionComment", "CompanyName", "CompanyComment"];
setFieldOrder(fieldOrder);
}
You can combine the usage of the changeFieldOrder and setFieldOrder functions if you wish.
In the next blog post I plan to stay with the same topic, how the enterprise custom fields get displayed in the “Details” web part, but instead of setting display order, I show you how to hide / display them based on conditions, like the group membership of the current user.
A few months ago I already posted a solution to check duplicated attachment names on SharePoint forms. Another common problem source are invalid file names.
For example, if you have a standard list item edit form, and would like to upload an attachment with invalid file name (for example, invalid characters or exceeding length limitation), on submitting the form you receive an exception. Despite of the error, the item itself is saved, however, if you appended other files (with valid names) as well, the ones you appended after the attachment having invalid name are not saved to the item.
The error message you receive if the file name contains invalid characters:
The file or folder name contains characters that are not permitted. Please use a different name.<nativehr>0x81020073</nativehr><nativestack></nativestack>
The error message you receive if the file name is too long:
The specified file or folder name is too long. The URL path for all files and folders must be 260 characters or less (and no more than 128 characters for any single file or folder name in the URL). Please type a shorter file or folder name.<nativehr>0x800700ce</nativehr><nativestack></nativestack>
The issue is even more annoying if you have an edit form with some kind of custom logic behind it. For example, in one of our applications, there is a custom approval workflow attached to the list that is started automatically after the item is saved. If the file name is invalid, the workflow starts without the (probably for the approval important) attachment. It doesn’t really help customer satisfaction, I shouldn’t say.
How could we validate attachment file names and prohibit attachments having invalid names? We can use the same method as described in my former post. We should update our onAttachOKbuttonClicked method with the file name validations. The new version, including the duplicate check as well as file name length and special character validation:
$(document).ready(attachEventHandlers);
function attachEventHandlers() {
// override the default event handler with our custom method
alert("A file with name '" + newFileName + "' is already attached to this item.");
}
else {
// call the OkAttach js method of SharePoint
// this is the method that is originally called by uploading attachments
OkAttach();
}
}
}
Note: I check only special characters that are supported in the NTFS file system, but not supported in SharePoint file names. I don’t check if the file name contains officially forbidden strings (.files, –Dateien, etc.). Reason is that up to now I had no issues with such files.
Including this method on the form helps us to prevent submitting the form with invalid attachments, and let the user to fix potential issues around attachment file names before saving the item.
Assume you have a SharePoint form with a lot of input fields as well as the option to append attachments to the list item. Assume one of your users accidentally tries to attach the same file twice (or two separate files with the same name) to the list item. When submitting the form, the following exception is displayed:
Failed to get value of the "Attachments" column from the "Attachments" field type control. See details in log. Exception message: A file with the name already exists.
Note: the reason behind this error is that attachments in SharePoint are stored in individual folders by the item Id (for example, attachments of item with Id 34 of list YourList are stored in folder /lists/YourList/Attachments/34) and you cannot store multiple files within the same folder. This is different as the attachment handling with mails, where you can attach multiple files with the same name without any errors.
You can imagine the frustration of the user, as all of the data typed into the form was lost. Wouldn’t it be better not to allow the user to choose a file to attach if a file with the same name was already attached to the item? Definitely it would be far better, and it is quite simply to fulfill using jQuery.
To be able to perform the validation, first we have to understand, how the name of the attachments are store on the page. Assume a file called test1.txt is already attached to the item, and in this editing session we chose the file called test2.txt (from the path c:\temp) to be attached (as display below).
In this case, the HTML DOM of the page looks like this:
The key information: a table element with ididAttachmentsTable contains a tbody element, that has tr elements including td elements with class ms-vb. If the file is already attached to the item, then the file name is included in a child a element. If the file has been just uploaded, a span element includes the full path and the file name.
The next task is to find out how we can get the name of the file we would like to attach.
Analyzing the HTML of the page we can found an input element of type file under the td element with idattachmentsOnClient:
So first I tried to use the value of the onetidIOFile input field, but it turned out that the value of this field does not change as we attach further files to the item.
Assuming we upload the files file1.docx, file2.xlsx and file3.png in this sequence, the value of the $("#attachmentsOnClient").html() expression changes as described below:
We can see, that former input fields of type file got hidden via style="DISPLAY: none", and new file input elements are appended to the existing ones, That means, we can get the path of the actually attached file from the last file input element.
Last action is to replace the default event handler method (OkAttach) on the OK button. In the new event handler we get the name of the file being actually appended and compare it with the file names of other attachments (attached either in this editing session or already saved to the item). If we find a file with the same name, then a warning is displayed. If there is no conflicting attachment, then we call the default event handler that registers the file to be uploaded as attachment.
$(document).ready(attachEventHandlers);
function attachEventHandlers() {
// override the default event handler with our custom method
In SharePoint you can give feedback to your users through status messages and notifications. The notifications can disappear automatically (if you call the addNotification method with false as the second parameter), and we can hide status messages as well from code using JavaScriptsetTimeout method (see examples here). Assuming that your scripts are running in the background and can give feedback not only as a direct response to a user action, it is not ideal, if your messages disappear after a few seconds, as the user may omit them easily. On the other side, it would not be nice, if the messages flooded the UI, without the possibility the user can close them after reading, similarly to the OK button in a JavaScript alert.
Fortunately, it’s quite easy to inject this feature using jQuery. For example, we can insert an x into the status message using the code below. The message disappears as the user clicks on the x:
var statusId = SP.UI.Status.addStatus("Status message:", "This is a test message"); makeStatusClosable(statusId);
function makeStatusClosable(statusId) { var status = $(‘#’ + statusId); $(status).html(‘<a title="Close status message" href="" onclick="javascript:SP.UI.Status.removeStatus(\” + statusId + ‘\’);javascript:return false;">x</a> ’ + $(status).html()); }
The result is illustrated on the following screenshot:
The same solution for the notifications looks like this:
var notificationId = SP.UI.Notify.addNotification("This is a test message", true); makeNotificationClosable(notificationId);
Recently I had to create an autocomplete textbox that makes it easy to select users from the SharePoint user profiles. As a nice-to-have feature I planned to include a user photo in the autocomplete list. I fulfilled the requirements using SPServices and jQuery / Autocomplete UI widget of jQuery UI and the SharePoint Search web service.
In the sample application I used a Content Editor Web Part (CEWP) to inject my code and the necessary HTML elements.
The HTML elements are a text box (autoCompleteUsers) that “hosts” the Autocomplete widget, a button (startQuery) that invokes the query based on the text entered / item selected in the autocomplete text box, and a div (transformResult) that display the search results.
Note: Yes, I know there are XSLT plugins for jQuery out there, however I found they do not support all of the features I required, like reading XML / XSL both from a string and from a URL path. In our case we have to parse the XML from the response received from the web service. The XSL comes from our custom XSL files stored in a subfolder of 14\TEMPLATE\LAYOUTS.
The PeopleSearchResults.xsl is used to convert the XML response from the Search web service into a HTML table (see related screenshot at the bottom of this post):
We register our event handlers after the document loaded completely. On pressing Enter we submit the query by invoking the click method of the startQuery button. On pressing other keys we update the autocomplete list by calling the attachAutoComplete function.
When we receive the response from the Search web service for our request sent from the attachAutoComplete function, we process the response using XPath queries (getAutoCompleteData function) and display / refresh the autocomplete list using our custom HTML template (setAutoCompleteData function).
function attachAutoCompleteCompleted(xmlResponse) {
var users = getAutoCompleteData(xmlResponse);
setAutoCompleteData(users);
}
function getAutoCompleteData(xmlResponse) {
var xml = buildXMLFromString($(xmlResponse.responseXML).find("QueryResult").text());
Regarding using XPath queries with namespaces from JavaScript you may find this information useful.
The queryUsers function calls the Search web service when the user clicks on the Search button (or presses Enter in the text box) and display the result in the transformResult div using the helper functions defined earlier.
Recently I faced again with the problem, that as the scripts on my web pages submit REST queries after a longer pause or an IISRESET (that is, when the IIS application pools must be started), I receive on of the following errors on the first try:
‘Microsoft.SharePoint.DataService.ListNameItem’ does not have a property named ‘FieldName’. (SharePoint 2010, HTTP 400 – Bad Request in the browser) An error occurred while processing this request. (SharePoint 2010, HTTP 500 – Internal Server Error in the browser) No property ‘FieldName’ exists in type ‘Microsoft.SharePoint.Linq.DataServiceEntity’ at position 0. (SharePoint 2013, HTTP 400 – Bad Request in the browser)
When one submits the query via the browser, only the HTTP 400 / HTTP 500 error codes are displayed. You can see the detailed error message only via a network traffic analyzer tool, like Fiddler.
The subsequent queries (even using the same expression as originally) usually succeed. If we submits multiple queries at the same time (for example via a script on a page), it might happen that several of these queries fail with the same error.
How to avoid this kind of failures in a web application, when it is especially important to prevent the erratic behavior?
Using jQuery we can create a function like the one below to re-submit the query automatically and silently in case of errors. I applied a time-gap of 1 second in the sample to give the system time to wake up and a maximum number of 3 retries. If even after the 3rd retry we encounter an error, we simply display it to the user.
// alter the value to match your site var baseUrl = ‘http://yoursharepoint/yoursite/_vti_bin/listdata.svc/’; function sendRESTQuery(queryUrl, onSuccess, retryCount) { var retryInterval = 1000; // 1 sec. var retryCountMax = 3; // use a default value of 0 if no value for retryCount passed var retryCount = (retryCount != undefined) ? retryCount : 0;
$.ajax({ type: ‘GET’, contentType: ‘application/json;odata=verbose’, url: baseUrl + queryUrl, headers: { ‘X-RequestDigest’: $(‘#__REQUESTDIGEST’).val(), "Accept": "application/json; odata=verbose" }, dataType: "json", complete: function (result) { var response = JSON.parse(result.responseText); if (response.error) { // here you can include your custom logic to differentiate for example based on the error types if (retryCount <= retryCountMax) { window.setTimeout(function () { sendRESTQuery(queryUrl, onSuccess, retryCount++) }, retryInterval); } else { alert("Error: " + response.error.code + "\n" + response.error.message.value); } } else { bgDetails = response.d; onSuccess(bgDetails); } } }); }
Assuming we have a HTML page with a SELECT element having id = “dropItemList”, and a SharePoint list called YourList, the following code snippet demonstrates, how to fill up the options of the SELECT with the list items:
var dropItemListSelector = "#dropItemList";
function initDropItemList() { $(dropItemListSelector).hide(); var queryUrl = "YourList()?$select=Id,Title"; sendRESTQuery(queryUrl, function (responseData) { var items = responseData.results; $(dropItemListSelector).find(‘option’).remove().end().append(‘<option value="0">Please choose an item!</option>’); Enumerable.from(items).orderBy("$.Title").forEach(function (x) { $(‘<option>’).val(x.Id).text(x.Title).appendTo(dropItemListSelector); }); $(dropItemListSelector).show(); }); }
In the previous example I used the linq.js ver.3.0.4-Beta5 to assemble my LINQ query. Version of the library is important!
Recently I had to provide a solution that supports selecting SharePoint site groups through an HTML autocomplete textbox.
The solution was built on JavaScript-based components, like jQuery(version 1.8.3), Autocomplete widget of jQuery UI (version 1.10.3) and jQuery Library SharePoint Web Services (aka SPServices, version 2013.01). The JavaScript code was injected into the page using the Content Editor Web Part (CEWP).
In the code (see below) we get the list of groups through the GetGroupCollectionFromSite method of the UserGroup web service, and attach the autocomplete feature to a HTML textbox, filtering the matching groups by a simple RegExp based on the text entered into the textbox.