Sunday, June 21, 2015

How to use JQuery Deferred Objects in asynchronous operations for SharePoint JSOM

When we write SharePoint apps or client side logic in SharePoint pages, we may use asynchronous operations using JSOM or REST. As operations are asynchronous it is hard to control the flow of the execution. Apart from that we can make the situation even harder if we use nested asynchronous operations.

For an example I will read a web property using a REST call and the result is taken as an input to another REST call to read a list item.

Since my operations are asynchronous, how do I make sure that I enter to the second method once I return from the first method. It is difficult, right?

As a solution we can use JQuery Deferred objects. Following is a sample method to retrieve quick launch navigation links using deferred objects.

  1. function ReadNavigation() {
  2.     var def = $.Deferred();
  3.  
  4.     SP.SOD.executeOrDelayUntilScriptLoaded(function () {
  5.         var clientContext = new SP.ClientContext.get_current();
  6.         var quickLnch = clientContext.get_web().get_navigation().get_quickLaunch();
  7.         clientContext.load(quickLnch);
  8.  
  9.         clientContext.executeQueryAsync(
  10.             function () {
  11.                 var qlEnum = quickLnch.getEnumerator();
  12.                 var currentNav = [];
  13.                 while (qlEnum.moveNext()) {
  14.                     var node = qlEnum.get_current();
  15.                     var ob = {};
  16.                     var title = node.get_title();
  17.                     var url = node.get_url();
  18.  
  19.                     ob.title = title;
  20.                     ob.url = url;
  21.                     currentNav.push(ob);
  22.                 }
  23.                 def.resolve(currentNav);
  24.             }, function (sender, args) {
  25.                 def.reject("error");
  26.             });
  27.     }, "SP.js");
  28.     return def;
  29. }
  30.  
  31. ReadNavigation().done(function (nav) {
  32.     console.log(nav)
  33. }).fail(function (nav) { console.log("error!") })

Tuesday, June 16, 2015

Centrally manage reusable content in SharePoint Online. Publish reusable content to publishing sites using SPServices

Reusable Content is a great feature in SharePoint. We can create html snippets in our publishing sites and we can reuse them in any page (mostly wiki pages).

image  image

This is a good place to start if you don’t have a good understanding about the concept

Problem

Let’s take a real world example with hundreds of SharePoint publishing sites. If we use out of the box reusable content features, we have to manually add html content to each and every site collection. This is because the default boundary of the feature is Site Collection. I guess you can see the problem. If I need to update some common content I have to update that in all the places. Not a good solution, right?

Solution

As the solution, I create an authoring site collection and add my reusable content there.

image

Then I created an application page which I use to copy the content to all selected publishing site collections.

Since I’m using SharePoint Online I used a sandbox solution to deploy pages and I used SPServices to copy the content. To retrieve all site collections, I used SharePoint search REST API.

  1. <script type="text/javascript">
  2.  
  3.         $(document).ready(function () {
  4.             searchSites();
  5.         });
  6.  
  7.         function closeInProgressDialog() {
  8.             if (dlg != null) {
  9.                 dlg.close();
  10.             }
  11.         }
  12.  
  13.  
  14.         function showInProgressDialog() {
  15.             if (dlg == null) {
  16.                 dlg = SP.UI.ModalDialog.showWaitScreenWithNoClose("Please wait...", "Please wait...", null, null);
  17.             }
  18.         }
  19.  
  20.         var TrackCalls = { "callCount": 0, "receivedCount": 0, "errorCount": 0 };
  21.  
  22.         function freezeui() {
  23.             $.blockUI.defaults.css = {
  24.                 padding: 0,
  25.                 margin: 0,
  26.                 width: '30%',
  27.                 top: '40%',
  28.                 left: '35%',
  29.                 backgroundColor: '#fff',
  30.                 textAlign: 'center',
  31.                 cursor: 'wait'
  32.             };
  33.  
  34.             TrackCalls.callCount = 1;
  35.             TrackCalls.receivedCount = 0;
  36.             TrackCalls.errorCount = 0;
  37.             $.blockUI({ message: '<img src="Preloader_3.gif" /><h1>Just a moment...</h1><p>Deploying to all publishing sites</p>' });
  38.             callBack();
  39.         }
  40.  
  41.         function freezeuiLoad() {
  42.  
  43.             $.blockUI.defaults.css = {
  44.                 padding: 0,
  45.                 margin: 0,
  46.                 width: '30%',
  47.                 top: '40%',
  48.                 left: '35%',
  49.                 backgroundColor: '#fff',
  50.                 textAlign: 'center',
  51.                 cursor: 'wait'
  52.             };
  53.             TrackCalls.callCount = 1;
  54.             TrackCalls.receivedCount = 0;
  55.             TrackCalls.errorCount = 0;
  56.             $.blockUI({ message: '<img src="Preloader_3.gif" /><h1>Just a moment...</h1><p>Loading all publishing sites</p>' });
  57.             callBack();
  58.         }
  59.  
  60.  
  61.         function callBack() {
  62.             setTimeout(function () {
  63.  
  64.                 if (TrackCalls.callCount == TrackCalls.receivedCount) {
  65.                     $.unblockUI();
  66.                 }
  67.                 else if (TrackCalls.errorCount > 0) {
  68.                     $.unblockUI();
  69.                 }
  70.                 else {
  71.                     callBack();
  72.                 }
  73.             }, 2000);
  74.         }
  75.  
  76.  
  77.  
  78.  
  79.         function IsItemExist(web, list, title) {
  80.             var currentId;
  81.             $().SPServices({
  82.                 debug: true,
  83.                 operation: "GetListItems",
  84.                 async: false,
  85.                 webURL: web,
  86.                 listName: list,
  87.                 CAMLQuery: "<Query><Where><Eq><FieldRef Name='Title' /><Value Type='Text'>" + title + "</Value></Eq></Where></Query>",
  88.                 CAMLRowLimit: 1,
  89.                 completefunc: function (xData, Status) {
  90.                     $(xData.responseXML).SPFilterNode("z:row").each(function () {
  91.                         var currentItem = $(this);
  92.                         currentId = currentItem.attr("ows_ID");
  93.  
  94.                     });
  95.                 }
  96.             });
  97.  
  98.             return currentId;
  99.         }
  100.  
  101.         function searchSites() {
  102.             var dfd = $.Deferred(function () {
  103.                 freezeuiLoad();
  104.                 var url = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?querytext='contentclass:sts_site'";
  105.                 $.ajax({
  106.                     url: url,
  107.                     method: "GET",
  108.                     headers: { "Accept": "application/json; odata=verbose" },
  109.                     success: function (data) {
  110.                         TrackCalls.receivedCount++;
  111.                         var query = data.d.query;
  112.                         var resultsCount = query.PrimaryQueryResult.RelevantResults.RowCount;
  113.                         var siteUrls = new Array()
  114.  
  115.                         var siteList = [];
  116.  
  117.                         for (var i = 0; i < resultsCount; i++) {
  118.                             var row = query.PrimaryQueryResult.RelevantResults.Table.Rows.results[i];
  119.                             var siteUrl = row.Cells.results[6].Value;
  120.                             var siteTitle = row.Cells.results[3].Value
  121.  
  122.                             var listStatus = IsListExist(siteUrl, "Reusable Content");
  123.                             if (listStatus != "error") {
  124.                                 siteUrls.push(siteTitle);
  125.                                 var ob = {};
  126.                                 ob.Url = siteUrl;
  127.                                 ob.Title = siteTitle;
  128.                                 siteList.push(ob);
  129.                             }
  130.                             else {
  131.                                 console.log("Not Found");
  132.                             }
  133.                         }
  134.                         dfd.resolve(siteList);
  135.  
  136.                         $("#publishingSites").datagrid({
  137.                             columns: [[
  138.                                 {
  139.                                     field: 'Title', title: 'Site Name', formatter: function (value, row, index) {
  140.                                         return '<a href="' + row.Url + '">' + value + '</a>';
  141.  
  142.                                     }
  143.                                 },
  144.                                     { field: 'Url', title: 'Site Url ' }
  145.                             ]],
  146.                             onCheck: function (rowIndex, rowData) {
  147.                                 var ro = rowIndex;
  148.                                 var aa = rowData;
  149.                            }
  150.                         });
  151.  
  152.                         var dg = $("#publishingSites").datagrid();
  153.                         dg.datagrid({ data: siteList });
  154.                     },
  155.                     error: function (data) {
  156.                         TrackCalls.errorCount++;
  157.                     }
  158.                 });
  159.             });
  160.             return dfd.promise();
  161.         }
  162.  
  163.         function IsListExist(web, list) {
  164.             TrackCalls.callCount++;
  165.             var listStatus = "error";
  166.             $().SPServices({
  167.                 debug: true,
  168.                 operation: "GetList",
  169.                 async: false,
  170.                 webURL: web,
  171.                 listName: list,
  172.  
  173.                 completefunc: function (xData, Status) {
  174.                     TrackCalls.receivedCount++;
  175.                     listStatus = Status;
  176.                 }
  177.             });
  178.             return listStatus;
  179.         }
  180.  
  181.         function escapeHtml(unsafe) {
  182.             return unsafe
  183.                  .replace(/&/g, "&amp;")
  184.                  .replace(/</g, "&lt;")
  185.                  .replace(/>/g, "&gt;")
  186.                  .replace(/"/g, "&quot;")
  187.                  .replace(/'/g, "&#039;");
  188.         }
  189.  
  190.         function CopyItems() {
  191.             freezeui();
  192.             $().SPServices({
  193.                 operation: "GetListItems",
  194.                 async: false,
  195.                 listName: "Reusable Content",
  196.                 completefunc: function (xData, Status) {
  197.                     TrackCalls.receivedCount++;
  198.  
  199.                     $(xData.responseXML).SPFilterNode("z:row").each(function () {
  200.                         var xitem = $(this);
  201.                         var myItem = xitem.attr("ows_Title");
  202.                         var reusableText = escapeHtml(xitem.attr("ows_ReusableHtml"));
  203.  
  204.                         var rows = $('#publishingSites').datagrid('getSelections');
  205.                         for (var i = 0; i < rows.length; i++) {
  206.                             var siteTitle = rows[i].Url;
  207.                             if (siteTitle != _spPageContextInfo.webAbsoluteUrl) {
  208.  
  209.                                 var id = IsItemExist(siteTitle, "Reusable Content", myItem);
  210.                                 if (id != undefined) {
  211.                                     //update
  212.                                     TrackCalls.callCount++;
  213.                                     $().SPServices({
  214.                                         operation: "UpdateListItems",
  215.                                         async: false,
  216.                                         webURL: siteTitle,
  217.                                         batchCmd: "Update",
  218.                                         listName: "Reusable Content",
  219.                                         ID: id,
  220.                                         valuepairs: [["Title", myItem], ["ReusableHtml", reusableText]],
  221.                                         completefunc: function (xData, Status) {
  222.                                             TrackCalls.receivedCount++;
  223.                                         }
  224.                                     });
  225.                                 }
  226.                                 else {
  227.                                     //add
  228.                                     TrackCalls.callCount++;
  229.                                     $().SPServices({
  230.                                         operation: "UpdateListItems",
  231.                                         async: false,
  232.                                         webURL: siteTitle,
  233.                                         batchCmd: "New",
  234.                                         listName: "Reusable Content",
  235.                                         valuepairs: [["Title", myItem], ["ReusableHtml", reusableText]],
  236.                                         completefunc: function (xData, Status) {
  237.                                             TrackCalls.receivedCount++;
  238.                                         }
  239.                                     });
  240.  
  241.                                 }
  242.                             }
  243.                         }
  244.                     });
  245.                 }
  246.             });
  247.         }
  248.  
  249.     </script>

Following is the end result. You have to select consuming publishing sites and click Sync button to publish updates

image

Hope this one helps someone :)

Tuesday, May 26, 2015

Presentation-Introduction to SharePoint Online at Kaatsu International University

Recently I did a presentation on “Introduction to SharePoint Online” at Kaatsu International University, Sri Lanka. I conducted few demos as well as implementation guidelines. Following is the presentation I used

Saturday, May 2, 2015

Resolving System.Threading.ThreadAbortException: Thread was being aborted error when performing a long time consuming operation in a page from Layouts folder

Sometimes we use SharePoint application pages or web parts to perform heavy operations those take long time than usual. In that case we have to explicitly increase the timeout periods using the Web.Config file.

Recently I came across a similar situation where an application page is used to create site collections using custom definitions.To resolve the error I modified the Web.Config file of the relevant web application and provided following values.

  1. <httpRuntime maxRequestLength="2097152" executionTimeout="1500" requestLengthDiskThreshold="8192" shutdownTimeout="108000" />

Unfortunately that remedy didn’t work.

After few hours of struggling it occurred to me that I was modifying the wrong Web.Config file. Since my page is located in Layouts folder, the page is not directly bound to a specific web application. For these kind of situations we are provided with a different Web.Config file, which is located under the Layouts folder.

image

I had to do following modification to increase the timeout period under system.web section

  1. <httpRuntime executionTimeout="1500" />

That fixed my issue.

Tuesday, April 21, 2015

SSRS - “The item '.rdl' cannot be found. ---> Microsoft.ReportingServices.Diagnostics.Utilities.ItemNotFoundException: The item '.rsds' cannot be found” in multi server SharePoint farm

I came across above exception when modifying the data source of SSRS reports using reporting services SOAP web service. Following is the detailed exception I received.

“System.ApplicationException: Failure in ActivateFeature for feature “<Feature>” on site: “<Site>” ---> System.Web.Services.Protocols.SoapException: The item 'http://sp13/Reports/TestReport.rdl' cannot be found. ---> Microsoft.ReportingServices.Diagnostics.Utilities.ItemNotFoundException: The item 'http://sp13/Reports/TestReport.rdl' ' cannot be found.   
at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)”

To resolve the issue I had to follow the steps given below

1. Check if you have installed SSRS Service and Proxy in all SharePoint Servers   

I found out above services were installed only in my application server. So I had to configure it in all WFE servers

  1. Install-SPRSService
  2. Install-SPRSServiceProxy

2. Recreate the SSRS Service application

above actions solved my issue :)

Thursday, April 9, 2015

SSRS - System.Web.Services.Protocols.SoapException: The permissions granted to user 'NT AUTHORITY\ANONYMOUS LOGON' are insufficient for performing this operation. when using ReportingServices2010.asmx in web applications with multiple authentication providers

I came across above exception when accessing SSRS reports using reporting services SOAP web service. Following is the detailed exception I received.

“System.ApplicationException: Failure in ActivateFeature for feature "<Feature>" on site: '<Site>' ---> System.Web.Services.Protocols.SoapException: The permissions granted to user 'NT AUTHORITY\ANONYMOUS LOGON' are insufficient for performing this operation. ---> Microsoft.ReportingServices.Diagnostics.Utilities.AccessDeniedException: The permissions granted to user 'NT AUTHORITY\ANONYMOUS LOGON' are insufficient for performing this operation.
at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
at Microsoft.SqlServer.ReportingServices2010.ReportingService2010.CreateCatalogItem(String ItemType, String Name, String Parent, Boolean Overwrite, Byte[] Definition, Property[] Properties,”

To resolve the issue I had to include following code segment when calling the web service

  1. ReportingService2010 service = new ReportingService2010();
  2. service.Credentials = CredentialCache.DefaultCredentials;
  3. service.Url = "http://sp13/_vti_bin/ReportServer/ReportService2010.asmx";
  4.  
  5. var authCookie = HttpContext.Current.Request.Cookies["FedAuth"];
  6. if (authCookie != null)
  7. {
  8.   var fedAuth = new Cookie(authCookie.Name, authCookie.Value, authCookie.Path, string.IsNullOrEmpty(authCookie.Domain) ? HttpContext.Current.Request.Url.Host : authCookie.Domain);
  9.   service.CookieContainer = new CookieContainer();
  10.   service.CookieContainer.Add(fedAuth);
  11. }

Friday, April 3, 2015

Deploy Advanced Search Box web part to SharePoint 2013 using module

We use Advanced Search Box to refine our search using keywords, managed properties and result types.This is very useful tool when the SharePoint environment contains a large amount of data.

image

There can be situations where you need to add this web part to custom web part pages programmatically. This task was very easy in SharePoint 2010 environments as this web part (AdvancedSearchBox.dwp) is available in web part gallery. Unfortunately this web part is no longer available in web part gallery in SharePoint 2013.

In this article I’ll show how to make that web part available in web part gallery so you can add it to pages later. Following are the steps I used to deploy the web part to the gallery

1. Navigate to the search center and execute sample query to get Advanced Search option

image

2. Edit the Advanced Search page and edit web part properties if required

image

3. After customizing properties, export the web part

image

4. Navigate to your visual studio solution and add a new Module. Include exported web part in that module

image

5. Modify Elements.xml as below

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <Module Name="WebParts" Url="_catalogs/wp" RootWebOnly="TRUE">
  4.     <File Path="WebParts\AdvancedSearchBox.dwp" Url="AdvancedSearchBox.dwp" Type="GhostableInLibrary">
  5.       <Property Name="Group" Value="Default Web Parts"></Property>
  6.     </File>
  7.   </Module>
  8. </Elements>

6. Deploy and activate the feature. Now the web part should be available in web part gallery

image

After the web part is deployed to the gallery we can easily add it to pages programmatically by using current page’s Limited Web Part Manager (SPWeb.GetLimitedWebPartManager)