JS Link


JS Link is one of the client side rendering(CSR) mechanism.

What is meant by CSR?

If the data is transformed by the client rather than the server by using client side technologies such as HTML,CSS and JavaScript, then it is known as CSR.

In Previous version of SharePoint, server side rendering like XSLT and CAML were used. We can even prefer server side rendering(XSLT) in SharePoint 2013, by referring XSL file in XSL Link and checking the Server render box as highlighted in red below.

WEBPART.png

JS Link can be found in List view web parts under Miscellaneous section(highlighted in black above). It links to a JavaScript file where rendering logic is written. To support JS Link, new content type called JavaScript Display template has been introduced with special site columns such as Target Control type, Target Scope etc. to ensure that CSR is targeted to the correct list web part.

Using JS Link, we can customize or control the rendering of

  • List Views
  • Forms(Display, Edit and New Forms)
  • Fields

Two events used for customization are:

  • OnPreRender
  • OnPostRender

To render a custom template, we need to register the template using the below javascript method:

SPClientTemplates.TemplateManager.RegisterTemplateOverrides(options);

where options takes the below structure:

var options = {
Templates:{
View: /* function or string */,
Body: /* function or string */,
Header: /* function or string */,
Footer: /* function or string */,
Group: /* function or string */,
Item: /* function or string */,
Fields: {
'Field Internal Name': {
View: /* function or string */,
EditForm: /* function or string */,
DisplayForm: /* function or string */,
NewForm: /* function or string */
}
}
},
OnPreRender: /* function or array of functions */,
OnPostRender: /* function or array of functions */
};

To find the internal name of the field, navigate to the list settings and click on the field under columns. This will take you to the “Edit Column” page. In “Edit column” page, click on the url to see the internal name of the field located at the end of URL as shown below:

https://yoursite.com/sites/SharePointConnect/_layouts/15/FldEdit.aspx?List=%9B7D21587E%2D7759%2D463A%2D89D2%2D014A41493451%7D&Field=Mark

For MDS(Minimal Download Strategy) disabled site:

We wrap the rendering logic in anonymous function so that it executes itself when the page is loaded and the global namespace will not be polluted.

Example 1:

(function()
{
var markContext  = {};
markContext.Templates = {};
markContext.Templates.Fields = {
'Mark': { 'View' : customizemark }
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(markContext);
})();
function customizemark()
{
var tem= ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
if(tem<50)
return "<span style='color:red;font-weight:bolder'>"+tem+"</span>";
else if(tem==50)
return "<span style='color:orange;font-weight:bolder'>"+tem+"</span>";
else if(tem>50)
return "<span style='color:green;font-weight:bolder'>"+tem+"</span>";
}

RESULT:

CSR-Sample1

Let us consider one more column called Status(Pass/Fail) and have requirement to show only status and not the mark. We can hide the column, ‘Mark’ from view using “Modify View” page but what about the forms. So, in order to hide the Mark field from forms, follow the below example:

Example 2:

(function () {
(window.jQuery || document.write('<script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.0.min.js"></script>'));
var hiddenFiledContext = {};
hiddenFiledContext.Templates = {};
hiddenFiledContext.Templates.OnPostRender = hideField;
hiddenFiledContext.Templates.Fields = {
"Mark": {
"NewForm": hiddenFiledTemplate,
"DisplayForm": hiddenFiledTemplate,
"EditForm": hiddenFiledTemplate
}
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(hiddenFiledContext);
})();
function hiddenFiledTemplate() {
return "<span class='csrHiddenField'></span>";
}
function hideField(ctx) {
jQuery(".csrHiddenField").closest("tr").hide();
}

 

RESULT:

HiddenField

For MDS Enabled site:

To apply Example-1 for MDS Enabled site, we need to register the template using:
RegisterModuleInit(relative path to file, function name);

RegisterModuleInit('/SiteAssets/CSR.js', Mark);
Mark();
function Mark()
{
var markContext  = {};
markContext.Templates = {};
markContext.Templates.Fields = {
'Mark': { 'View' : customizemark }
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(markContext);
}
function customizemark()
{
var tem= ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
if(tem<50)
return "<span style='color:red;font-weight:bolder'>"+tem+"</span>";
else if(tem==50)
return "<span style='color:orange;font-weight:bolder'>"+tem+"</span>";
else if(tem>50)
return "<span style='color:green;font-weight:bolder'>"+tem+"</span>";
}

When we use JS Link in a page where there are more than one list view, the rendering or customization may not appear correctly. To ensure that it is applied to the respective views, three additional options are used:

  • List Template Type – ID of list template
List template type Template ID
Custom List 100
Document Library 101
Survey 102
Links 103
Announcements 104
Contacts 105
Calendar 106
Tasks 107
Discussion Board 108
Picture Library 109
DataSources 110
Form Library 115
No Code Workflows 117
Custom Workflow Process 118
Wiki Page Library 119
CustomGrid 120
No Code Public Workflows<14> 122
Workflow History 140
Project Tasks 150
Public Workflows External List<15> 600
Issues Tracking 1100
  • View Style – ID of different styles available in “modify view” page. To find the ID of each view style:
    • Go to Modify view page
    • Right click on the page and select view source
    • you can find the ID of the style in <option>
    • Below is the list of styles with ID

ID

Style
0 Basic Table
12 Boxed, No Label
13 Boxed
15 Newsletter
16 Newsletter, no lines
17 Shaded
20 Preview Pane
Default Default
  • Base View ID: Attribute of view element that can be found in SharePoint Designer
    • In General, BaseViewID of “AllItemsView” is equal to 1 and BaseViewID of “Summary view” is equal to 0

Uploading JS File:

It is recommended to upload js file to master page gallery. To upload in master page gallery, follow the below steps:

  • Go to Site settings ->Master pages under Web Designer Galleries (In case of Publishing site, it will be Master pages and page layouts under Web Designer Galleries)
  • In Master page Gallery, click on FILE tab and choose Upload Document
  • Browse the js file from your computer and select the content type as JavaScript Display Template.

Javascript Display TemplateJS Display Template.png

  • Target Control Type: Type of control for which customization is applied to. It can be
    • Form
    • View
    • Field
  • Standalone: Specifies to include js file to override during view selection. Options are
    • Override
    • Standalone
  • Target Scope: URL of the website to which override applies to.
  • Target List Template ID: ID of the list template to which override applies to. Refer the above table under List Template ID
  • Fill these columns and hit Save button
  • Don’t forget to publish it if you are uploading it in publishing site.

Publish

  • If you do not have permission to master page gallery, upload it to SiteAssets
  • Now, go to the list web part and refer the uploaded js file as:
    • ~site/_catalogs/masterpage/csr.js if you have uploaded js file in master page gallery
    • ~site/SiteAssets/csr.js if you have uploaded it in Site Assets
  • Use the token ~sitecollection instead of ~ site if you are referring the js file in list web part of subsite
    • Ex:~sitecollection/_catalogs/masterpage/csr.js
  • To refer more than one js file in JS Link, use pipe symbol, |
    • Ex:~site/SiteAssets/csr.js|~site/SiteAssets/Demo.js
  • NOTE: Use CTRL+F5 (Hard Refresh) if changes to the code are not reflected in the browser

Advantages:

  • Performance is better as it rendered by client rather than the server
  • Ease of development

Disadvantages:

  • JS Link is not supported in Survey and events lists like calendar
  • Browser Compatibility – works differently in different browsers
  • JS Link does not work for custom list forms created in SharePoint designer

Refer more sample codes here

Site Template


How to create a site from existing template?

  • Go to the site (whose template is to be used for new site)
  • Click on Gear wheel->Site Settings
  • Click on Save site as template under Site Actions as highlighted below

Site Settings

  • On clicking the link, it takes to Save as  Template page where file name and Template name should be given
  • Check “Include Content” box and hit OK button.

Save As Template

  • The below message will be shown. Click on the “Solution gallery” hyperlink in it

Operation Completed

  • In Solution Gallery, you can see the template of the site that is created before few minutes.
  • Click on it to download the .wsp file. Now, we have a local copy of the template in our system.

Solution Gallery

  • Go to the Central admin site to create a new site collection
  • Click on New->Private Site Collection

New Site.png

  • Do not select in-built templates. Just click on custom tab and hit OK button.

New Site Collection

  • In Template selection, you cannot find the custom template as you have not yet uploaded.
  • To upload the solution, click on “Solution Gallery”

Upload Custom Template

  • Click on Upload Solution in the ribbon.
  • Choose the file from Computer and press OK button.

upload

Add Solution

  • In Add a Solution dialog, click on “Activate” to activate the solution.

Activate

  • If you go to the new site, you can see the custom tab with our template.
  • Select the template and Set up groups for the site.

Create from Custom

Groups

  • Now, we can see the contents of previous site in our new site as highlighted below.

New site from Template.png

CRUD Operations using JSOM


Let us consider the below list “Resource” on which CRUD Operations are to be performed using JavaScript.

 

Resource.PNG

Read Operation:

<script src="/SiteAssets/jquery.min.js"></script>
<script>
_spBodyOnLoadFunctionNames.push("Read");
function Read()
{
var clientContext = new SP.ClientContext.get_current();
var web = clientContext.get_web();
var oList = web.get_lists().getByTitle('Resource');
var camlQuery = new SP.CamlQuery();
var Query='<View><Query><FieldRef Name="Title"/></Query></View>';
camlQuery.set_viewXml(Query);
this.collListItem = oList.getItems(camlQuery);
clientContext.load(collListItem);
clientContext.executeQueryAsync(Function.createDelegate(this, QuerySucceeded), Function.createDelegate(this, this.QueryFailed));
}
function QuerySucceeded()
{
var shtml="";
var listItemEnumerator = collListItem.getEnumerator();
while(listItemEnumerator.moveNext())
{
var oListItem = listItemEnumerator.get_current();
var s=oListItem.get_item('Title');
shtml+="
" +s+ "
";
}
document.getElementById("Result").innerHTML=shtml;
}
function QueryFailed(sender, args)
{
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
</script>
<label for="Result">Resources:</label>
<div id="Result"></div>

RESULT:

READ-JSOM.PNG

Create Operation:

<script src="/SiteAssets/jquery.min.js"></script>
<script>
function create()
{
var name=document.getElementById("resName").value;
var role=document.getElementById("resRole").value;
var clientContext = SP.ClientContext.get_current();
var oList = clientContext.get_web().get_lists().getByTitle('Resource');
var itemCreateInfo = new SP.ListItemCreationInformation();
var oListItem = oList.addItem(itemCreateInfo);
oListItem.set_item('Title',name);
oListItem.set_item('Resource_x0020_Role',role);
oListItem.update();
clientContext.executeQueryAsync(QuerySucceeded, QueryFailed);
}
function QuerySucceeded()
{
alert("Record Created");
}
function QueryFailed(sender, args)
{
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
</script>
<table>
<tr>
<td><label for="resName">Enter the Resource Name:</label></td>
<td><input type="text" id="resName"/></td>
</tr>
<tr>
<td><label for="resRole">Enter the Resource Role:</label></td>
<td><input type="text" id="resRole"/></td>
</tr>
<tr>
<td colspan="2"><input type="button" value="Create" onclick="create()"></td>
</tr>
</table>

RESULT:

 

Update Operation:

<script src="/SiteAssets/jquery.min.js"></script>
<script>
function Update()
{
var role=document.getElementById("resRole").value;
var resourceId=document.getElementById("resId").value;
var clientContext = new SP.ClientContext.get_current();
var oList = clientContext.get_web().get_lists().getByTitle('Resource');
this.oListItem = oList.getItemById(resourceId);
oListItem.set_item('Resource_x0020_Role', role);
oListItem.update();
clientContext.executeQueryAsync(Function.createDelegate(this, this.QuerySucceeded), Function.createDelegate(this, this.QueryFailed));
}
function QuerySucceeded()
{
alert("Record Updated");
}
function QueryFailed(sender, args)
{
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
</script>
<table>
<tr>
<td><label for="resId">Enter the ID:</label></td>
<td><input type="text" id="resId"/></td>
</tr>
<tr>
<td><label for="resRole">Enter the Resource Role:</label></td>
<td><input type="text" id="resRole"/></td>
</tr>
<tr>
<td colspan="2"><input type="button" value="Update" onclick="Update()"></td>
</tr>
</table>

RESULT:

Delete Operation:

<script src="/SiteAssets/jquery.min.js"></script>
<script>
function Delete()
{
var resourceId=document.getElementById("resId").value;
var clientContext = new SP.ClientContext.get_current();
var oList = clientContext.get_web().get_lists().getByTitle('Resource');
this.oListItem = oList.getItemById(resourceId);
oListItem.deleteObject();
clientContext.executeQueryAsync(Function.createDelegate(this, this.QuerySucceeded), Function.createDelegate(this, this.QueryFailed));
}
 
function QuerySucceeded()
{
alert("Record Deleted");
}
function QueryFailed(sender, args)
{
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
</script>
<table>
<tr>
<td><label for="resId">Enter the ID:</label></td>
<td><input type="text" id="resId"/></td>
</tr>
<tr>
<td colspan="2"><input type="button" value="Delete" onclick="Delete()"></td>
</tr>
</table>

RESULT:

CRUD Operations using REST


Before looking at CRUD Operations, let us understand the concepts and variables used in it.

_spPageContextInfo :

It is a context variable rendered for each sharepoint page. Properties of _spPageContextInfo are:

Properties Values
webServerRelativeUrl “\sites\SharePointConnect”
webAbsoluteUrl “https:\wordpress.com.com\sites\SharePointConnect”
siteAbsoluteUrl “https:\wordpress.com\sites\SharePointConnect”
serverRequestPath “\sites\SharePointConnect\SitePages\Home.aspx”
layoutsUrl “_layouts\15”
webTitle “SharePointConnect”
webTemplate “1”
tenantAppVersion “3289089430”
isAppWeb false
webLogoUrl “https:\wordpress.com\sites\SharePointConnect\SiteAssets\logo.JPG”,
webLanguage 1033
currentLanguage 1033
currentUICultureName “en-US”
currentCultureName “en-US”
clientServerTimeDelta new Date(“2016-06-10T05:08:10.2860519Z”) – new Date()
siteClientTag “0$$15.0.4285.1000″
crossDomainPhotosEnabled false
webUIVersion 5
webPermMasks {High:2147484897,Low:3294958795}
pageListId ”{e40453e1-7c5c-4c8e-87dc-061b48931fea}”
pageItemId 1
pagePersonalizationScope 1
userId 6789
systemUserKey ”i:0\u0040.w|s-1-5-21-1743670581-294256949-3035048756-78901787″
alertsEnabled true
siteServerRelativeUrl “\sites\SharePointConnect”
allowSilverlightPrompt ‘True’

HTTP Methods:

METHOD USAGE
GET Retrieve the resource or retrieve data from Server
POST Creates the resource or submit the data to the server
DELETE Deletes the resource
MERGE Updates only the fields that are specified and maintains the same value(i.e. previous values) for all other fields
PUT Updates all the fields even if not specified. For unspecified fields, it uses default values.

Headers:

Accept: Used to specify the language that the client(browser) will accept
Content-type: Used to specify the language in which the data exists or the language in which the client will send when it is requested.
X-RequestDigest: Used to hold the form digest(request digest) value

Request Digest Value:
When a valid request is sent to the server and the same request is then sent multiple times fraudulently, replay attack occurs. Replay attacks can be better understood in case of transferring money. Imagine what happens if the same money transfer is sent multiple times.This can be prevented using request digest value which identifies the fake requests. For SharePoint-Hosted app, the request digest value lies in _REQUESTDIGEST object of page and can be retrieved in jquery as:

 $(“#__REQUESTDIGEST”).val()

This value should be used in create, update and delete operations to avoid replay attack.

Finding the List Item Type:

For create and update operations, list item type should be included. List item type is a string created by SharePoint when a list is created. We can easily determine the list item type using the format: SP.Data.ListNameListItem . If list name is EmployeeDetails then List Item Type will be SP.Data.EmployeeDetailsListItem. This does not work all the time because if List Name starts in small case, it capitalizes the first letter of the list name. so it is better to always capitalize the first letter of the list name and then pass the rest of the characters in list name using slice.

Slice is a method which extracts the part of a string. Its syntax is slice(start,end) where start parameter is required and end parameter is optional. If end is omitted, it extracts till the end.

“SP.Data.” + name.charAt(0).toUpperCase() + name.slice(1) + “ListItem”;

NOTE: This function does not work when the list name has special characters or spaces in between. In such cases, you can use the below function to find out the list item type.

function getItemTypeForListName(listTitle)
{
return executeJson({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/?$select=ListItemEntityTypeFullName",
method: 'GET'
}).then(function(data){
return data.d.ListItemEntityTypeFullName;
});
}
getItemTypeForListName(listTitle)
.done(function(name){
alert(name);
})
.fail(function(error){
console.log(JSON.stringify(error));
});
function executeJson(options)
{
var headers = options.headers || {};
headers["Accept"] = "application/json;odata=verbose";
if(options.method == "POST") {
headers["X-RequestDigest"] = $("#__REQUESTDIGEST").val();
}
var ajaxOptions =
{
url: options.url,
type: options.method,
contentType: "application/json;odata=verbose",
headers: headers
};
if(options.method == "POST") {
ajaxOptions.data = JSON.stringify(options.payload);
}
return $.ajax(ajaxOptions);
}

Consider a SharePoint List ” Employee Details” for performing CRUD Operations using REST.

Employee Details

Entity Tag(E-Tag):

E-Tag is used for Cache validation and conditional requests. When the client sends the request, the server responds with E-Tag which is stored in browser cache. If the same request is sent again, the browser validates the request with cache in “If- None-Match” header to the server. If E-Tag of the server and the browser matches, then server returns “304 Not modified status code” saying the browser to use the browser cache. If E-Tag does not match, server will respond to the request with new E-Tag.

Similar to If-None- Match header, “If-Match” header can be used for Conditional GET requests. If the E-Tag matches, 200(OK) is returned else 412 will be returned saying precondition failed.

The meaning of “If-Match: *” is that the method should be performed if the representation selected by the origin server exists, and must not be performed if the representation does not exist.

Read Operation:

<script src="/SiteAssets/jquery.min.js"></script>
<script>
$(document).ready(function()
{
read();
});
function read()
{
var resturl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('Employee Details')/items";
$.ajax({
  url: resturl,
  method: "GET",
  headers: { 
	"Accept": "application/json; odata=verbose"
	 },
	 success: function (data) {
	 var lnt = data.d.results.length;
	 for(var i=0; i<lnt; i++ )
	 {
	  var title = data.d.results[i].Title;
	  var id = data.d.results[i].Employee_x0020_ID;
	  var designation=data.d.results[i].Designation;
	  $("#Result").append("
<tr>
<td>"+title+"</td>
<td>"+id+"</td>
<td>"+designation+"</td>
</tr>
");
	}
	},
 	error: function (data) {
	console.log(data.responseJSON.error) 
	}
});
}
</script>
<table id="Result" border="1" cellspacing="5" cellpadding="5" width="100%"></table>

RESULT:

READ

Create Operation:

<script src="/SiteAssets/jquery.min.js"></script>
<script>
function createItem()
{
 var addNewItemUrl = "/_api/Lists/GetByTitle('Employee Details')/Items";
 var itemType = GetItemTypeForListName('Employee_x0020_Details');
 var empId = $("#Id").val();
 var empName=$("#Title").val();
 var empDesgn=$("#Designation").val();
 var item = {
  "__metadata": {"type": itemType },
  "Title": empName,
  "Employee_x0020_ID": empId,
  "Designation":empDesgn,
  };
 addNewItem(addNewItemUrl, item);
}
function addNewItem(url, data) {
 $.ajax({
  url: _spPageContextInfo.webAbsoluteUrl + url,
  type: "POST",
  headers: {
   "accept": "application/json;odata=verbose",
   "X-RequestDigest": $("#__REQUESTDIGEST").val(),
   "content-Type": "application/json;odata=verbose"
   },
  data: JSON.stringify(data),
  success: function (data) {
  alert("Record Added");
  },
  error: function (error) {
  console.log(JSON.stringify(error));
  }
 });
}
function GetItemTypeForListName(name) {
return "SP.Data."+ name.charAt(0).toUpperCase() + name.slice(1) +"ListItem";
}
</script>
<table>
<tr>
<td> <label for"Id">Employee ID:</label></td>
<td><input type"text" id="Id"/></td>
</tr>
<tr>
<td><label for="Title">Employee Name:</label></td>
<td><input type="text" id="Title"/></td>
</tr>
<tr>
<td><label for="Designation">Designation:</label></td>
<td><input type="text" id="Designation"/></td>
</tr>
<tr>
<td colspan="2"><input type="button" value="Add" onclick="createItem()"/></td>
</tr>
</table>

RESULT:

Update Operation:

<script src="/SiteAssets/jquery.min.js"></script>
<script>
function update()
{
 var empId = $("#Id").val();
 var empDesgn=$("#Designation").val();
 var requestUri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/GetByTitle('Employee Details')/getItemById(" + empId + ")";
 var itemType = GetItemTypeForListName('Employee_x0020_Details');
 var item = {
         "__metadata": { "type": itemType },
         "Designation": empDesgn,
          };
 UpdateData(requestUri,item);
}
function UpdateData(requestUri,item)
{
 $.ajax({
 url: requestUri,
 type: "POST",
 data:JSON.stringify(item),
 headers: {
 "accept":"application/json;odata=verbose",
 "content-type": "application/json;odata=verbose",
 "X-RequestDigest": $("#__REQUESTDIGEST").val(),
 "X-HTTP-Method": "MERGE",
 "IF-MATCH":"*"
 },
 success: function (data) {
             alert("Record Updated");
         },
           error: function (error) {
            console.log(JSON.stringify(error));
         }
 });
}
function GetItemTypeForListName(name) {
 return"SP.Data." + name.charAt(0).toUpperCase() + name.slice(1) + "ListItem";
}
</script>
<table>
<tr>
<td><label for="ID">Employee ID:</label></td>
<td><input type="text" id="Id"/></td>
</tr>
<tr>
<td><label for="Designation">Designation:</label></td>
<td><input type="text" id="Designation"/></td>
</tr>
<tr>
<td colspan="2"><input type="button" value="Update" onclick="update()"/></td>
<tr></table>

RESULT:

Delete Operation:

<script src="/SiteAssets/jquery.min.js"></script>
<script>
function Delete()
{
 var empId = $("#Id").val();
 var requestUri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/GetByTitle('Employee Details')/getItemById(" + empId + ")";
 DeleteItem(requestUri);
}
function DeleteItem(deleteuri)
{
 $.ajax({
 url: deleteuri,
 type: "POST",
 headers: {
 "accept":"application/json;odata=verbose",
 "content-type": "application/json;odata=verbose",
 "X-RequestDigest": $("#__REQUESTDIGEST").val(),
 "X-HTTP-Method": "DELETE",
 "IF-MATCH":"*"
 },
 success: function (data) {
             alert("Record Deleted");
         },
         error: function (error) {
            console.log(JSON.stringify(error)); 
        }
 });
}

</script>
<table>
<tr>
<td><label for="ID">Employee ID:</label></td>
<td><input type="text" id="Id"/></td>
</tr>
<tr>
<td colspan="2"><input type="button"  value="Delete" onclick="Delete()"/></td>
<tr></table>

RESULT: