This library can be used even on OnPrem environments. It includes most of the common operations we do in JS for SharePoint.
Download JS file from GitHub
This library can be used even on OnPrem environments. It includes most of the common operations we do in JS for SharePoint.
Download JS file from GitHub
This post is about showing the context menu in SharePoint Search results preview panes using Custom Display templates.
The solution provided here is not an OOTB menu that comes in Document Library ListView webpart. This is a custom markup and JS to render the options.
I’m using a custom JS file(previewpanefunctions.js) where I wrote all my functions which are called from hovertemplates (JS file can be downloaded at the end of this post). This JS can be reffered in your custom Control template using $includeScript like this
<script type="text/javascript"> $includeScript(this.url, "~sitecollection/siteassets/scripts/PreviewpaneFunctions.js"); </script> <div id="Control_SearchResults"> .....
implement your custom item display template, and hover displaytemplates. make sure you give correct path of your hover display template in item template. In my example, I created a control template where I referred my custom JS file and a custom word displaty template(Item_searchresult_word.html) and a custom work hover display template(item_searchresult_word_hover.html) , and in my “Item_searchresult_word.html”
if(!$isNull(ctx.CurrentItem) && !$isNull(ctx.ClientControl)){ var id = ctx.ClientControl.get_nextUniqueId(); var itemId = id + Srch.U.Ids.item; var hoverId = id + Srch.U.Ids.hover; var hoverUrl = "~sitecollection/_catalogs/masterpage/Display Templates/Search/myproject/item_searchresult_word_hover.js"; $setResultItem(itemId, ctx.CurrentItem); ....... .......
I implemented multiple display templates for each file type, all are with similar code, and created result sources and result types to use these templates in Search Results.
Now the real part is to create the hover template, I modified the existing display template markup to show some custom column values on previewpane and , custom actions like edit(owa), share, follow/unfollow, and menu(with multiple menu options) and some custom links on preview pane like ‘view version history’ and ‘add to onedrive’ links. I’ve used some custom functions to format date in this code. the template code looks like following, there are some other functionalities I’vent explained here like ‘uploading a new version’ of file. Which comes in my upcoming posts.
<html xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"> <head> <title>Word Item Hover Panel</title> <!--[if gte mso 9]><xml> <mso:CustomDocumentProperties> <mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden> <mso:MasterPageDescription msdt:dt="string">Displays a result hover panel tailored for a Microsoft Word document.</mso:MasterPageDescription> <mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106603</mso:ContentTypeId> <mso:TargetControlType msdt:dt="string">;#SearchHoverPanel;#</mso:TargetControlType> <mso:HtmlDesignAssociated msdt:dt="string">1</mso:HtmlDesignAssociated> <mso:ManagedPropertyMapping msdt:dt="string">'Title':'Title','Path':'Path','Description':'Description','EditorOWSUSER':'EditorOWSUSER','LastModifiedTime':'LastModifiedTime','CollapsingStatus':'CollapsingStatus','DocId':'DocId','HitHighlightedSummary':'HitHighlightedSummary','HitHighlightedProperties':'HitHighlightedProperties','FileExtension':'FileExtension','ViewsLifeTime':'ViewsLifeTime','ParentLink':'ParentLink','FileType':'FileType','IsContainer':'IsContainer','SecondaryFileExtension':'SecondaryFileExtension','DisplayAuthor':'DisplayAuthor','ServerRedirectedURL':'ServerRedirectedURL','SectionNames':'SectionNames','SectionIndexes':'SectionIndexes','ServerRedirectedEmbedURL':'ServerRedirectedEmbedURL','ServerRedirectedPreviewURL':'ServerRedirectedPreviewURL','RefinableString15':'RefinableString15','RefinableString19':'RefinableString19','RefinableString21':'RefinableString21','ListID':'ListID','ListItemID':'ListItemID','SPSiteURL':'SPSiteURL','ContactName':'ContactName','RefinableString22':'RefinableString22',,'SPWebUrl':'SPWebUrl'</mso:ManagedPropertyMapping> <mso:_dlc_DocId msdt:dt="string">CONSC-7-1972</mso:_dlc_DocId> <mso:_dlc_DocIdItemGuid msdt:dt="string">fda4514e-8afe-4630-8cc0-0a0a6d938dd6</mso:_dlc_DocIdItemGuid> <mso:_dlc_DocIdUrl msdt:dt="string">https://siteurl/_layouts/15/DocIdRedir.aspx?ID=CONSC-7-1972, CONSC-7-1972</mso:_dlc_DocIdUrl> <mso:HtmlDesignConversionSucceeded msdt:dt="string">True</mso:HtmlDesignConversionSucceeded> <mso:HtmlDesignStatusAndPreview msdt:dt="string">https://siteurl/_catalogs/masterpage/Display Templates/Search/Item_Word_HoverPanel_RecentModifiedContent.html, Conversion successful.</mso:HtmlDesignStatusAndPreview> <mso:CrawlerXSLFile msdt:dt="string"></mso:CrawlerXSLFile> <mso:HtmlDesignPreviewUrl msdt:dt="string"></mso:HtmlDesignPreviewUrl> </mso:CustomDocumentProperties> </xml><![endif]--> </head> <body> <div id="Item_Word_HoverPanel"> <!--#_ var i = 0; var wacurlExist = !Srch.U.e(ctx.CurrentItem.ServerRedirectedURL) && !Srch.U.e(ctx.CurrentItem.ServerRedirectedEmbedURL); var id = ctx.CurrentItem.csr_id; ctx.CurrentItem.csr_FileType = Srch.Res.file_Word; ctx.CurrentItem.csr_ShowFollowLink = true; ctx.CurrentItem.csr_ShowShareLink = true; ctx.CurrentItem.csr_ShowViewLibrary = true; //define ids var versionHistoryLink = id+ppConstants.versionHistoryLink; var addToOneDriveLink = id+ppConstants.addToOneDriveLink; var noVersioningMsg = id+ppConstants.noVersioningMsg; var contactNameLink = id+ppConstants.contactNameLink; var docViewEdit = id+ppConstants.docViewEdit; var followLink = id+ppConstants.docFollowStatusLink; var docShareLink = id+ppConstants.docShareLink; var ctxMenuLink = id+ppConstants.ctxMenuLink; //define menu element ids var ctxMenuContainer = id+ppConstants.ctxMenuContainer; var viewprops = id+ppConstants.viewPropsLink; var editprops = id+ppConstants.editPropsLink; var setAlerts = id+ppConstants.setAlertsLink; var checkin = id+ppConstants.checkInLink; var checkout = id+ppConstants.checkOutLink; var discardcheckout = id+ppConstants.discardCheckOutLink; var publishmajorversion =id+ppConstants.publishLink; var downloadcopy = id+ppConstants.downloadCopyLink; var deletefile = id+ppConstants.deleteFileLink; var uploadnewversion = id+ppConstants.uploadNewVersionLink _#--> <div class="ms-srch-hover-innerContainer ms-srch-hover-wacSize preview" id="_#= $htmlEncode(id + HP.ids.inner) =#_"> <div class="ms-srch-hover-arrowBorder" id="_#= $htmlEncode(id + HP.ids.arrowBorder) =#_"></div> <div class="ms-srch-hover-arrow" id="_#= $htmlEncode(id + HP.ids.arrow) =#_"></div> <div class="ms-srch-hover-content" id="_#= $htmlEncode(id + HP.ids.content) =#_" data-displaytemplate="WordHoverPanel"> <div id="_#= $htmlEncode(id + HP.ids.header) =#_" class="ms-srch-hover-header"> _#= ctx.RenderHeader(ctx) =#_ </div> <div id="_#= $htmlEncode(id + HP.ids.body) =#_" class="ms-srch-hover-body"> <!--#_ if(!Srch.U.n(ctx.CurrentItem.ServerRedirectedEmbedURL)) { ctx.CurrentItem.csr_DataShown = true; ctx.currentItem_ShowChangedBySnippet = true; _#--> <div class="ms-srch-hover-viewerContainer"> <iframe id="_#= $htmlEncode(id + HP.ids.viewer) =#_" src="_#= $urlHtmlEncode(ctx.CurrentItem.ServerRedirectedEmbedURL) =#_" scrolling="no" frameborder="0px" class="ms-srch-hover-viewer"></iframe> </div> <div class="ms-srch-hover-wacImageContainer"> <img id="_#= $htmlEncode(id + HP.ids.preview) =#_" alt="_#= $htmlEncode(Srch.Res.item_Alt_Preview) =#_" onload="this.parentNode.style.display='block';" /> </div> <!--#_ } else { ctx.CurrentItem.csr_ShowLastModifiedTime = true; ctx.CurrentItem.csr_ShowAuthors = true; } if(!Srch.U.e(ctx.CurrentItem.SectionNames)) { ctx.CurrentItem.csr_DataShown = true; _#--> <div class="ms-srch-hover-subTitle"> <h3 class="ms-soften">_#= $htmlEncode(Srch.Res.hp_SectionHeadings) =#_</h3> </div> <!--#_ var sectionNames = Srch.U.getArray(ctx.CurrentItem.SectionNames); var sectionIndexes = Srch.U.getArray(ctx.CurrentItem.SectionIndexes); if(!Srch.U.n(sectionIndexes) && sectionIndexes.length != sectionNames.length) { sectionIndexes = null; } var hitHighlightedSectionNames = Srch.U.getHighlightedProperty(id, ctx.CurrentItem, "sectionnames"); if(!Srch.U.n(hitHighlightedSectionNames) && hitHighlightedSectionNames.length != sectionNames.length) { hitHighlightedSectionNames = null; } var numberOfSectionsToDisplay = Math.min(Srch.SU.maxLinesForMultiValuedProperty, sectionNames.length); var sectionsToDisplay = new Array(); var usingHitHighlightedSectionNames = Srch.SU.getSectionsForDisplay( hitHighlightedSectionNames, numberOfSectionsToDisplay, sectionsToDisplay); for(i = 0; i < sectionsToDisplay.length; ++i) { var index = sectionsToDisplay[i]; if(Srch.U.n(index)) { continue; } var tooltipEncoded = $htmlEncode(sectionNames[index]); var htmlEncodedSectionName = ""; if(usingHitHighlightedSectionNames) { htmlEncodedSectionName = hitHighlightedSectionNames[index]; } else { htmlEncodedSectionName = tooltipEncoded; } _#--> <div class="ms-srch-hover-text ms-srch-ellipsis" id="_#= $htmlEncode(id + HP.ids.sectionName + i) =#_" title="_#= tooltipEncoded =#_"> <!--#_ if(!Srch.U.n(sectionIndexes) && sectionIndexes.length >= i && !Srch.U.e(sectionIndexes[index]) && wacurlExist) { var encodedSlideIndex = "&wdSlideIndex=" + $urlKeyValueEncode(sectionIndexes[index]); _#--> <a clicktype="HoverSection" linkindex="_#= $htmlEncode(i) =#_" href="_#= $urlHtmlEncode(ctx.CurrentItem.ServerRedirectedURL + encodedSlideIndex) =#_" target="_blank">_#= htmlEncodedSectionName =#_ </a> <!--#_ } _#--> </div> <!--#_ } } var modifieddate = window.CustomFunctions.FormatDateFromSearchResult(ctx.CurrentItem.LastModifiedTime); var author = $getItemValue(ctx, "DisplayAuthor"); if(author.value && modifieddate ) { var _authorArr = author.value.split(';'); var authorvalue = _authorArr[_authorArr.length-1]; _#--> Last Modified at : <span class="blue" title="_#=modifieddate =#_">_#=modifieddate =#_</span> by <span class="blue" title="_#=authorvalue=#_">_#=authorvalue =#_</span> <!--#_ } _#--> <a id="_#=versionHistoryLink=#_" class="pull-left">View Version History </a> <a id="_#=addToOneDriveLink=#_" class="pull-right">Add To My OneDrive</a> <div id="_#=noVersioningMsg=#_" class="ms-clear ms-hide ms-error">Versioning is not enabled on this library</div> </div> <div id="_#= $htmlEncode(id + HP.ids.actions) =#_" class="ms-srch-hover-actions"> <div id="Item_CommonHoverPanel_Actions"> <div class="ms-srch-hover-action"> <!--#_ var appAttribs = ""; if (!$isEmptyString(ctx.CurrentItem.csr_OpenApp)) { appAttribs += "openApp=\"" + $htmlEncode(ctx.CurrentItem.csr_OpenApp) + "\"" }; if (!$isEmptyString(ctx.CurrentItem.csr_OpenControl)) { appAttribs += " openControl=\"" + $htmlEncode(ctx.CurrentItem.csr_OpenControl) + "\"" }; _#--> <a id="_#=docViewEdit=#_" class="ms-calloutLink ms-uppercase edit" >View</a> <a id="_#=followLink=#_" class="ms-calloutLink ms-uppercase follow" >_#= $htmlEncode(Srch.Res.hp_Follow) =#_</a> <a id="_#=docShareLink=#_" class="ms-calloutLink ms-uppercase share" title="Share" >Share</a> <a id="_#=ctxMenuLink=#_" class="ms-calloutLink ms-uppercase menu" title="Menu" href="javascript:;" >menu</a> <div id="_#=ctxMenuContainer=#_" class="ms-core-menu-box ms-hide ms-core-defaultFont ms-shadow" contenteditable="false" style="position: absolute;z-index:100;right:-10px;bottom:36px" flipped="false"> <ul class="ms-core-menu-list"> <li class="ms-core-menu-item ms-hide" id="_#=viewprops=#_"> <a title="View Properties" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">View Properties</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=editprops=#_"> <a title="Edit Properties" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Edit Properties</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=setAlerts=#_"> <a title="Set Alerts" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Set Alert</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=checkin=#_"> <a title="Check In" href="javascript:;" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Check In</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=checkout=#_"> <a title="CheckOut" href="javascript:;" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Check Out</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=discardcheckout=#_"> <a title="Discard Checkout" href="javascript:;" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Discard CheckOut</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=publishmajorversion=#_"> <a title="Publish A Major Version" href="javascript:;" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Publish A Major Version</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=downloadcopy=#_"> <a title="Download Copy" href="_#=ctx.CurrentItem.Path=#_" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Download a copy</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=deletefile=#_"> <a title="Delete" href="javascript:;" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Delete</span> </div> </a> </li> <li class="ms-core-menu-item ms-hide" id="_#=uploadnewversion=#_"> <a title="Upload New Version" href="javascript:;" class="ms-core-menu-link"> <div class="ms-core-menu-label"> <span class="ms-core-menu-title">Upload New Version</span> </div> </a> </li> </ul> </div> </div> </div> </div> </div> <!--#_ if(!Srch.U.n(ctx.CurrentItem.ServerRedirectedEmbedURL)){ AddPostRenderCallback(ctx, function(){ HP.loadViewer(ctx.CurrentItem.id, ctx.CurrentItem.id + HP.ids.inner, ctx.CurrentItem.id + HP.ids.viewer, ctx.CurrentItem.id + HP.ids.preview, ctx.CurrentItem.ServerRedirectedEmbedURL, ctx.CurrentItem.ServerRedirectedPreviewURL); }); } //Add Data and other Options var props = { csrid:id, webUrl : ctx.CurrentItem.SPWebUrl, docPath:ctx.CurrentItem.Path, listId: ctx.CurrentItem.ListID, itemId: ctx.CurrentItem.ListItemID, contactName: ctx.CurrentItem.RefinableString22, serverRedirectUrl:ctx.CurrentItem.ServerRedirectedURL }; AddPostRenderCallback(ctx, function(){ //for other options window.SPOSearchCustomizations.Automation.PreviewPaneOperations.initPreviewPaneData(props); // for "..." menu window.SPOSearchCustomizations.Automation.PreviewPaneOperations.ContextMenu.init(props); }); _#--> </div> </div> </body> </html>
this is the whole markup for a work preview pane. the two functions
window.SPOSearchCustomizations.Automation.PreviewPaneOperations.initPreviewPaneData(props); window.SPOSearchCustomizations.Automation.PreviewPaneOperations.ContextMenu.init(props)
are defined in previewpanefunctions.js file.
This post is about “ScrollToTopOnRedraw” property of search results webpart. The page scrolls to top after a search operation (search, refinment, pagination) if set to true. It is a boolean property which is false by default and All we need to do is set this boolean value to true for our search results webpart/ search context.
We can set this property in Two ways:
1) Export your current search results webpart to get .webpart file
2) Open it in a text editor (VS)
3). Find the property “ScrollToTopOnRedraw” and change its value from False to True and Save the file.
4).Import the webpart and add it in your page.
Thats All. The page scrolls to top when you click on pagination links / apply refiners / apply search.
This is the simplest way to achieve the scroll if you are using custom control display template for your search results.
The OOTB “Control_SearchResults.html” control template looks like
<div id="Control_SearchResults"> <!--#_ if (Srch.U.shouldAnimate(ctx.DataProvider)){ Srch.U.hideElement(ctx.ClientControl.get_element()); ctx.OnPostRender = function(){ Srch.U.animateResults(ctx.ClientControl, ctx.DataProvider.get_userAction()); }; } _#-->
Add a line of code at the top of if condition to change the property value
<div id="Control_SearchResults"> <!--#_ //scroll to top on redraw(pagination, refiner, search) ctx.ClientControl.set_scrollToTopOnRedraw(true); if (Srch.U.shouldAnimate(ctx.DataProvider)){ Srch.U.hideElement(ctx.ClientControl.get_element()); ctx.OnPostRender = function(){ Srch.U.animateResults(ctx.ClientControl, ctx.DataProvider.get_userAction()); }; } _#-->
This post is just another example about JSLink you can find on web. This post is about show follow/Unfollow Document link in List-view. The solution is simple. Add a text column on the List (Say FollowLink) and hide this column in forms. Include the column in Listview. Link the following JSLink code in Listview webpart.
var SPOFieldCustomizations = window.SPOFieldCustomizations || {}; SPOFieldCustomizations.CustomizeFollowLinkFieldRendering = function () { var fieldJsLinkOverride = {}; fieldJsLinkOverride.Templates = {}; fieldJsLinkOverride.OnPostRender = postRenderHandler; fieldJsLinkOverride.Templates.Fields = { // Make sure the Priority field view gets hooked up to the GetPriorityFieldIcon method defined below // we have to update the column name here 'FollowLink': { 'View': SPOFieldCustomizations.GetFollowLinkFieldMarkup } }; // Register the rendering template SPClientTemplates.TemplateManager.RegisterTemplateOverrides(fieldJsLinkOverride); }; SPOFieldCustomizations.GetFollowLinkFieldMarkup = function (ctx) { var followLinkMarkup = ""; if (ctx.CurrentItem.FSObjType === "0") { followLinkMarkup = '<a class="ms-profile-followLink ms-heroCommandLink" href="javascript:;">' + '<span class="ms-profile-followHeroImageParent">' + '<img data-toggle="tooltip" data-placement="right" title="" id="Img' + ctx.CurrentItem.ID + '" src="/_layouts/15/images/gears_anv4.gif?rev=35" class="ms-profile-followHeroImage" ></img>' + '</span>' + '</a>'; SPOFieldCustomizations.isFollowed('/' + ctx.CurrentItem.FileRef, 'Img' + ctx.CurrentItem.ID); } return followLinkMarkup; }; SPOFieldCustomizations.isFollowed = function (documentUrl, imgid) { window.Utilities.Social.REST.LoadIsFollowed(documentUrl, _spPageContextInfo.webAbsoluteUrl, 1, function (responseData) { jsonObject = JSON.parse(responseData.body); if (jsonObject.d.IsFollowed === true) { $('#' + imgid).attr("title", "Click to Unfollow").attr('data-followed', '1') .attr('src', '/_layouts/15/images/socialcommon.png?rev=33') .addClass("unFollow") } else { $('#' + imgid).attr("title", "Click to Follow").attr('data-followed', '0') .attr('src', '/_layouts/15/images/socialcommon.png?rev=33') .removeClass("unFollow"); } $('#' + imgid).click(function () { var FollowStatus = $(this).attr('data-followed'); var url = ''; if (FollowStatus === '1') url = _spPageContextInfo.webAbsoluteUrl + '/_api/social.following/stopfollowing'; else if (FollowStatus === '0') url = _spPageContextInfo.webAbsoluteUrl + '/_api/social.following/follow'; if (url !== '') { SPOFieldCustomizations.toggleFollow(url, documentUrl, imgid, FollowStatus); } }); }, function (s, a, errMsg) { $('#' + imgid).text(errMsg); }); }; SPOFieldCustomizations.toggleFollow = function (url, documentUrl, imgid, FollowStatus) { window.Utilities.Social.REST.ToggleFollow(url, documentUrl, _spPageContextInfo.webAbsoluteUrl, 1, function (responseData) { var jsonObject = JSON.parse(responseData.body); var isfollowed = jsonObject.d.Follow; var stoppedFollowing = jsonObject.d.StopFollowing; if (isfollowed !== undefined) { $('#' + imgid).attr('title', 'Click to Unfollow').attr('data-followed', '1') .addClass("unFollow"); } else { $('#' + imgid).attr('title', 'Click to Follow').attr('data-followed', '0') .removeClass("unFollow"); } }, function (s, a, errMsg) { $('#' + imgid).text(errMsg); }); } function postRenderHandler(ctx) { // code to execute after view render } // Call the function. SPOFieldCustomizations.CustomizeFollowLinkFieldRendering();
There are few utility function calls inside this JSLink code to follow/unfollow documents. The code looks like
function LoadAndExecuteSodFunction(scriptKey, fn) { if (!ExecuteOrDelayUntilScriptLoaded(fn, scriptKey)) { LoadSodByKey(NormalizeSodKey(scriptKey)); } } RegisterSod('/_layouts/15/sp.requestexecutor.js'); window.Utilities = window.Utilities || {}; window.Utilities.Social = window.Utilities.Social || {}; window.Utilities.Social.REST = window.Utilities.Social.REST || function () { var LoadIsFollowed = function (documentOrSiteUrl, siteurl, actortype, successcallback, failcallback) { var requestinfo = { url: siteurl + '/_api/social.following/isfollowed', method: "POST", body: JSON.stringify({ "actor": { "__metadata": { "type": "SP.Social.SocialActorInfo" }, "ActorType": actortype, "ContentUri": documentOrSiteUrl, "Id": null } }), headers: { "accept": "application/json;odata=verbose", "content-type": "application/json;odata=verbose" }, success: function (responseData) { successcallback(responseData); }, error: function (s, a, errMsg) { failcallback(s, a, errMsg); } }; LoadAndExecuteSodFunction('sp.requestexecutor.js', function () { var executor = new SP.RequestExecutor(siteurl); executor.executeAsync(requestinfo); }); }; var ToggleFollow = function (url, documentOrSiteUrl, siteurl, actortype, successcallback, failcallback) { var requestinfo = { url: url, method: "POST", body: JSON.stringify({ "actor": { "__metadata": { "type": "SP.Social.SocialActorInfo" }, "ActorType": actortype, "ContentUri": documentOrSiteUrl, "Id": null } }), headers: { "accept": "application/json;odata=verbose", "content-type": "application/json;odata=verbose" }, success: function (responseData) { successcallback(responseData); }, error: function (s, a, errMsg) { failcallback(s, a, errMsg); } }; LoadAndExecuteSodFunction('sp.requestexecutor.js', function () { var executor = new SP.RequestExecutor(siteurl); executor.executeAsync(requestinfo); }); }; return { LoadIsFollowed: LoadIsFollowed, ToggleFollow: ToggleFollow}; }();
The first thing I really like about SharePoint REST APIs is, we do not need any reference to client assemblies or JS libraries. The basic operations in REST API includes CRUD operations on lists. You can find the list of these end points on MSDN. And we can find so many examples on MSDN and other websites using REST APIs with jQuery $.ajax.
Here is why I’m not a big fan of $.ajax with SharePoint APIs, for two reasons.
I’ve used $.ajax for adding a list item and I thought it will be working fine.
var AddListItem = function(product) { return $.Deferred(function (def) { var data = { __metadata: { type: 'SP.Data.ProductsListItem' }, Title: product.ProductName, ProductNumber: product.ProductNumber, Price: product.Price }; $.ajax({ url: webUrl + "/_api/web/lists/getbytitle('Products')/items", type: 'POST', contentType: 'application/json;odata=verbose', data: JSON.stringify(data), headers: { 'Accept': 'application/json;odata=verbose', 'X-RequestDigest': $('#__REQUESTDIGEST').val() }, success: function (data) { def.resolve(data) }, error: function (s,a,errMsg) { def.reject(errMsg) } }); }); };
But I see a problem with POST requests. we need to include X-RequestDigest parameter in request and the Request digest value expires in 18 seconds(AFAIK). The code worked fine in my office365 environment. But not worked when I make a POST call after 18 seconds. The RequestDigest timeout was very less in one of my on-premise environments so the code was never able to make POST operations. returned error message like 403/401. So I removed $.ajax and used SharePoint cross domain JS library sp.requestexecutor.js to make REST calls.
var AddListItem = function (product) { var executor = new SP.RequestExecutor(webUrl); return $.Deferred(function (def) { var url = webUrl + ";/_api/web/lists/getbytitle('Products')/items"; var data = { __metadata: { type: 'SP.Data.ProductsListItem' }, Title: product.ProductName, ProductNumber: product.ProductNumber, Price: product.Price }; executor.executeAsync({ url: url, method: 'POST', body: JSON.stringify(data), headers: { 'Accept': 'application/json; odata=verbose', 'content-type': 'application/json;odata=verbose'; }, success: function(data){def.resolve(data)}, error: function(s,a,errMsg){def.reject(errMsg)} }); }); };
Now, this code looks same like the code we use to make REST calls in Cloud hosted apps and it is same except the URL and url in SP.RequestExecutor() Constructor. The URL in cloud hosted apps looks like
appweburl +"/_api/SP.AppContextSite(@target)/web/lists/getbytitle('Products')/items?@target=" +hostweburl
We are not using the key Object of cross domain API “SP.AppContextSite” here and the URL is same as we use with $.ajax. The second Difference is we are creating ‘SP.RequestExecutor()’ to current site URL(not any appWebUrl). The benefit here we get is we need not to pass X-RequestDigest parameter. This cross domain library will take care of getting the Latest Request Digest from server(https://site/_api/contextInfo) and append it to Request. This code worked perfectly in all of my environments (Office 365 and OnPremise). You need to load /_layouts/15/sp.requestexecutor.js‘ to use this library.
In one of my projects we implemented a site collection which have list of community sub sites and you can follow / unfollow those communities from a WebPart at site collection level. And some of those communities are Private(with unique permissions). Assume you are using $.ajax to get the follow status / to follow / to unfollow. The code to get the status of Follow looks like.
var followingManagerEndpoint = webUrl + &amp;quot;/_api/social.following&amp;quot;;// you can use rootSite Url here function isFollowed() { $.ajax( { url: followingManagerEndpoint + &amp;quot;/isfollowed&amp;quot;, type: &amp;quot;POST&amp;quot;, data: JSON.stringify( { &amp;quot;actor&amp;quot;: { &amp;quot;__metadata&amp;quot;: { &amp;quot;type&amp;quot;:&amp;quot;SP.Social.SocialActorInfo&amp;quot; }, &amp;quot;ActorType&amp;quot;:SP.Social.SocialActorType.site, &amp;quot;ContentUri&amp;quot;:webUrl, &amp;quot;Id&amp;quot;:null } }), headers: { &amp;quot;accept&amp;quot;:&amp;quot;application/json;odata=verbose&amp;quot;, &amp;quot;content-type&amp;quot;:&amp;quot;application/json;odata=verbose&amp;quot;, &amp;quot;X-RequestDigest&amp;quot;:$(&amp;quot;#__REQUESTDIGEST&amp;quot;).val() }, success: function (responseData) { stringData = JSON.stringify(responseData); jsonObject = JSON.parse(stringData); if (jsonObject.d.IsFollowed === true ) { alert('The user is currently following the site.'); //stopFollowSite(); } else { alert('The user is currently NOT following the site.'); //followSite(); } }, error: requestFailed }); }
Assume that you are making this call to get the follow status of a private to which you do not have access. You cannot make a call with url as “webUrl + “/_api/social.following” because you do not have access to webUrl. So you can try rootSiteUrl in followingManagerEndPoint and pass the subsiteurl in contentUri parameter. It still thrown me an error(403 or 401, do not remember). The solution is to get rid of $.ajax and use SP.RequestExecutor(rootSiteUrl).
var LoadIsFollowed = function (documentOrSiteUrl, rootSiteUrl, actortype, successcallback, failcallback) { var requestinfo = { url: rootSiteUrl+ '/_api/social.following/isfollowed', method: &amp;quot;POST&amp;quot;, body: JSON.stringify({ &amp;quot;actor&amp;quot;: { &amp;quot;__metadata&amp;quot;: { &amp;quot;type&amp;quot;: &amp;quot;SP.Social.SocialActorInfo&amp;quot; }, &amp;quot;ActorType&amp;quot;: actortype, &amp;quot;ContentUri&amp;quot;: documentOrSiteUrl, &amp;quot;Id&amp;quot;: null } }), headers: { &amp;quot;accept&amp;quot;: &amp;quot;application/json;odata=verbose&amp;quot;, &amp;quot;content-type&amp;quot;: &amp;quot;application/json;odata=verbose&amp;quot; }, success: function (responseData) { successcallback(responseData); }, error: function (s, a, errMsg) { failcallback(s, a, errMsg); } }; var executor = new SP.RequestExecutor(rootSiteUrl); executor.executeAsync(requestinfo); };
Similarly we can implement code to follow/unfollow the subsite even you do not have access to it.