Monday, July 14, 2014

SharePoint 2013 Workflow-Check if required documents are available inside a Document Set prior to the approval process

We can use SharePoint Designer workflows to author an approval process. Sometimes the approval process can be complex where we need to create advanced workflows. In SharePoint 2013 workflows we can use HTTP web services and loops to assist such instances.

Recently I created an approval workflow to a library which stores document sets. Following is the pre-condition to the approval.

Each document set has two allowed content types named “Policy Document” and “Policy Checklist”. The approval process proceeds if the document set contain at least one document from each document set. Otherwise the approval process terminates.

Following are the questions we need to address

  • How can we access documents within a document?
  • How can we get the content type ID of each document?

We will call two web service methods to obtain relevant information and will use built in loop to assist the process. Following are the steps I used to answer above questions

1. Construct the server relative URL for document sets

Let’s assume our document set is located at “https://sp13/sites/dev/Shared Documents/DocumentSet1”. Below activity will remove “https://sp13/sites/dev/” component and assigns “Shared Documents/DocumentSet1” to a variable named “itemFolder”

image

2. Prerequisites to call SharePoint REST API

As I mentioned earlier, SharePoint 2013 workflows can call RESTful services by default. In this case we will use services provided in “_api/web”. Since those services provide xml output we have convert them to JSON.

To do that we will create a dictionary. For both “Accept” and “Content-Type” variables provide “application/json;odata=verbose” as the value

image 

image

3. Call web service to get all items within the document set

To get items under a document set we will use GetFolderByServerRelativeUrl() method. Following is a sample service call

https://sp13/sites/dev/_api/web/GetFolderByServerRelativeUrl('Shared Documents/Set1')/Files

If you need further information on this method you can refer this blog post. To call the service using workflow we will use Call HTTP web service activity as shown below.

Following are the sub steps

A. When constructing  the url, we will use the sever relative path created in step 1

image

B. Create an empty dictionary variable named JSONResults

C. Edit properties of the activity to assign “Header” and “JSONResults” dictionaries to RequestHeaders and ReseponseContent parameters.

image

Finally the activity looks like below

image

Unfortunately this service doesn’t return Content Type ID property. As a result we will get a property from this service which can be input to our second service to obtain Content Type Id. We will call the second service in step 8.

4. Store results to a separate dictionary called allItems

image

5. Get count of items to start the loop

image

6. Store variable to hold content type IDs

image

7. Create a loop to iterate each item we have in allItems dictionary

Create an integer variable named index.

image

Then we will get the ServerRelativeUrl property from the item and assign it to variable serverRelativeUrl

image

8. Call the second web service to get content type id.

Since “GetFolderByServerRelativeUrl()/Files” method does not return the content type of items. As a result we need to execute another service to do so. We will use the ServerRelativeUrl property returned from step 7 as an input to the second service

This time we will call GetFileByServerRelativeUrl() method. Following is a sample of the service call.

https://sp13/sites/dev/_api/Web/GetFileByServerRelativeUrl('/sites/dev/Shared Documents/Set1/3.txt')/ListItemAllFields

If you need further information on this method you can refer this blog post as well.

As we did for the first method we will call HTTP web service activity as shown below

image

image

9. Get ContentTypeId property of the item

This service returns just a single item. So it is easy to get the value

image

10. Create Boolean variables to store content type availability 

Since we have two content types, we have two Boolean variables named “ct1IsAvailable” and “ct2IsAvailable”

image

11. Set Boolean value true if the content type was found

image

12. Specify Termination condition at the end of the loop

image

13. Conditionally allow or restrict the approval

image

14. Terminate the workflow

image

Summary

In the above article I explained how to use http web services available in “_api/web” within SharePoint 2013 workflows. A practical scenario was also explained where we need to check at least one document from each content type available in the document set.

Tuesday, June 24, 2014

Presentation–Site provisioning techniques including site provisioning providers for SharePoint 2013

Recently I did a presentation on site provisioning providers at Sri Lanka SharePoint forum. In that session I described about other site provisioning techniques as well.

10393838_10152318026097530_3673986027299882108_n

There are many site provisioning techniques including Site Definitions, Feature Stapling, Web Templates, Site Provisioning Provider (SPWebProvisioningProvider) etc.. If we need to obtain full control over the provisioning process we may need to select the Site Provisioning Provider approach.

You can find some sample codes I used in demo from this location.

Sunday, June 15, 2014

Set result source and item display template programmatically to SharePoint 2013 search ResultScriptWebPart

Sometimes we need to programmatically set search result source and a custom item display template to a search web part.

Following is a sample code segment to do the needful

  1. //Get the instance of web part
  2. var homePage = web.GetFile("SitePages/Home.aspx");
  3. using (var webPartManager = homePage.GetLimitedWebPartManager(PersonalizationScope.Shared))
  4. {
  5. var recentDocs = webPartManager.WebParts.Cast<WebPart>().FirstOrDefault
  6.         (wp => wp.Title.Equals("Recent Documents")) as ResultScriptWebPart;
  7. var querySettings = new DataProviderScriptWebPart
  8.         {
  9.            PropertiesJson = recentDocs.DataProviderJSON
  10.         };
  11.  
  12. //Get the search service application proxy
  13. var settingsProxy = SPFarm.Local.ServiceProxies.GetValue
  14.         <SearchQueryAndSiteSettingsServiceProxy>();
  15. var searchProxy = settingsProxy.ApplicationProxies.GetValue
  16.         <SearchServiceApplicationProxy>("Search Service Application");
  17. var siteOwner = new SearchObjectOwner(SearchObjectLevel.SPSite, web);
  18.  
  19. //Set the result source. I set the default for demonstration
  20. var siteResultSource = searchProxy.GetResultSourceByName("Local SharePoint Results", siteOwner);
  21.  
  22.  
  23. querySettings.Properties["SourceName"] = siteResultSource.Name;
  24. querySettings.Properties["SourceID"] = siteResultSource.Id;
  25. querySettings.Properties["SourceLevel"] = siteOwner.Level;
  26.  
  27.  
  28. //Set the item display template
  29. recentDocs.ItemTemplateId = "~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_Contoso.js";
  30. recentDocs.DataProviderJSON = querySettings.PropertiesJson;
  31.  
  32. webPartManager.SaveChanges(recentDocs);
  33. }
  34. homePage.Update();

That’s all we need to do.

Saturday, June 7, 2014

Programmatically upload and use SharePoint 2013 Item Display Templates

This is the third post of a series of blog posts on SharePoint 2013 search display templates.

In previous posts I explained about Display Templates in general and about creating new Item Display Templates. But in some scenarios we can’t upload display templates manually through mapped network drives or design manager. Following are few scenarios where we require a programmatic approach to deploy search Item Display Templates.

Let’s assume that we have thousands of site collections in our environment and we have a search web part in each home page. Can we manually update each site? It is not feasible, isn’t it.

Another scenario is where we have a custom site template where all web parts, libraries, master pages, etc.. are provisioned when we create the site. In that situation, the expectation is to apply custom Display Templates automatically if available.

Following are the steps to deploy a search Item Display Template programmatically

1. Create the custom Item Display Template

In the previous post I explained about creating a custom Display Template. In this scenario we use a test site collection to create the template.

2. Create Visual Studio solution

We will create an empty SharePoint solution and add a module to host the Display Template

image

We need to deploy only the JavaScript file. But to make the solution complete we will include the html version also in the module. By the way, html file is helpful when we need to do changes in future. In that case we will perform following tasks

  • Do the modification in html file
  • Upload that file in to the test site
  • Get the generated JavaScript file and replace current file in the module with that file
  • Deploy the project

3. Update the Feature

Update the scope of the feature to be “Site”

image 

3. Update the element.xml file

  1. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  2.   <Module Name="DisplayTemplates" Url="_catalogs/masterpage/Display Templates/Search" RootWebOnly="TRUE">
  3.     <File Path="DisplayTemplates\Item_Contoso.js"
  4.           Url="Item_Contoso.js"
  5.           Type="GhostableInLibrary"
  6.           Level="Published"
  7.           ReplaceContent="TRUE">
  8.       <Property Name="Title" Value="Contoso Item" />
  9.       <Property Name="TargetControlType" Value=";#SearchResults;#Content Web Parts;#" />
  10.       <Property Name="DisplayTemplateLevel" Value="Item" />
  11.       <Property Name="TemplateHidden" Value="FALSE" />
  12.     </File>
  13.   </Module>
  14. </Elements>

4. Deploy the solution

Finally we will deploy the solution to make the template available for search web parts

image

Following is the link to the source code

Monday, June 2, 2014

Create SharePoint 2013 Item Display Template overriding RenderBody

This is the second post of a series of blog posts on SharePoint 2013 search display templates.
I’ve given an introduction on Display Templates in previous post, Where I mentioned different types and their relationships. In this post I’ll discuss on Item Display Templates.
If we navigate to the mapped network drive for Display Templates (“<Site URL>_catalogs/masterpage/Display Templates/Search”) we can find available Display Templates.  Let’s take Item_Word.html file as the example.
image
As shown in above diagram, Item Display Template has following main areas
1. Title
This is the title of the Display Template which will be shown in the settings area of the web part.
2. CustomDocumentProperies
This contains some properties about the Display Template itself. Most important of them is the ManagedPropertyMapping section. Within that section we can include any managed property those are accessed within the Item Display Template.
3. RenderBody (ctx)
As you can see from the diagram, “ctx” object is filled with data (e.g.: Icon, HoverUrl, etc..). Then we will call the RenderBody method which will call Item_CommonItem_Body template to generate the search result item.
By using above facts we can create our own Display Templates. As an example we will use following scenario.
Below is an out of the box search web part. We will create a new Display Template just to remove the path component
image
We need to follow the steps given below.
1. Create new template based on existing item template (e.g.: Item_Word)
image
2.Open the Item_Contoso.html and change Titles accordingly
image
3. Test the Display Template with a web part
image
It is working as expected. Now we need to customize the template.
Creating a “Result Type” is not mandatory as we want this Display Template to be available on this web part only.
4. Override RenderBody (ctx)
Since I need to remove the path (URL component displayed in green color) from our web part, I have two options to consider
  • Remove the managed property “Path” from our template
  • Remove the HTML component that renders the path.
Since managed property “Path” is referred by other places, it is not the best practice to remove it. So I will remove the HTML component that renders the path. How can I find the source of that HTML?
As I mentioned in my earlier post, when a Display Template calls RenderBody method it will call Item_CommonItem_Body template. The rendering part happens there. But can we directly modify that template according to our requirement?
It’s not the correct approach as that template is referred by all Item Display Templates. Instead I will copy necessary code segment from Item_CommonItem_Body template and insert that in our template
image
5. Find the code that renders the path and remove that component
Following is the code that renders the path.
image
We will remove “_#= truncatedUrl =#_” element and save the template. If we need we can do necessary modifications as well. Final result looks like below
image
Following is the final Display Template
  1. <html xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882">
  2.   <head>
  3.     <title>Contoso Item</title>
  4.  
  5.     <!--[if gte mso 9]><xml>
  6. <mso:CustomDocumentProperties>
  7. <mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden>
  8. <mso:MasterPageDescription msdt:dt="string">Displays a result tailored for a Microsoft Word document.</mso:MasterPageDescription>
  9. <mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106603</mso:ContentTypeId>
  10. <mso:TargetControlType msdt:dt="string">;#SearchResults;#</mso:TargetControlType>
  11. <mso:HtmlDesignAssociated msdt:dt="string">1</mso:HtmlDesignAssociated>
  12. <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'</mso:ManagedPropertyMapping>
  13. <mso:HtmlDesignConversionSucceeded msdt:dt="string">True</mso:HtmlDesignConversionSucceeded>
  14. <mso:HtmlDesignStatusAndPreview msdt:dt="string">http://sp13:8080/sites/4750/_catalogs/masterpage/Display%20Templates/Search/Item_Contoso.html, Conversion successful.</mso:HtmlDesignStatusAndPreview>
  15. </mso:CustomDocumentProperties>
  16. </xml><![endif]-->
  17.   </head>
  18.   <body>
  19.     <div id="Item_Contoso">
  20.       <!--#_
  21.         if(!$isNull(ctx.CurrentItem) && !$isNull(ctx.ClientControl)){
  22.             var id = ctx.ClientControl.get_nextUniqueId();
  23.             var itemId = id + Srch.U.Ids.item;
  24.             var hoverId = id + Srch.U.Ids.hover;
  25.             var hoverUrl = "~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_Word_HoverPanel.js";
  26.             $setResultItem(itemId, ctx.CurrentItem);
  27.             ctx.CurrentItem.csr_Icon = Srch.U.getIconUrlByFileExtension(ctx.CurrentItem);
  28.             ctx.CurrentItem.csr_OpenApp = "word";
  29.             ctx.currentItem_ShowHoverPanelCallback = Srch.U.getShowHoverPanelCallback(itemId, hoverId, hoverUrl);
  30.             ctx.currentItem_HideHoverPanelCallback = Srch.U.getHideHoverPanelCallback();
  31. _#-->
  32.       <div id="_#= $htmlEncode(itemId) =#_" name="Item" data-displaytemplate="ContosoItem" style="padding: 0; margin: 0;" class="ms-srch-item" onmouseover="_#= ctx.currentItem_ShowHoverPanelCallback =#_" onmouseout="_#= ctx.currentItem_HideHoverPanelCallback =#_">
  33.  
  34.         <!--#_
  35.         var id = ctx.CurrentItem.csr_id;
  36.         var title = Srch.U.getHighlightedProperty(id, ctx.CurrentItem, "Title");
  37.         if ($isEmptyString(title)) {title = $htmlEncode(ctx.CurrentItem.Title)}
  38.         var appAttribs = "";
  39.         if (!$isEmptyString(ctx.CurrentItem.csr_OpenApp)) { appAttribs += "openApp=\"" + $htmlEncode(ctx.CurrentItem.csr_OpenApp) + "\"" };
  40.         if (!$isEmptyString(ctx.CurrentItem.csr_OpenControl)) { appAttribs += " openControl=\"" + $htmlEncode(ctx.CurrentItem.csr_OpenControl) + "\"" };
  41.         var showHoverPanelCallback = ctx.currentItem_ShowHoverPanelCallback;
  42.         if (Srch.U.n(showHoverPanelCallback)) {
  43.             var itemId = id + Srch.U.Ids.item;
  44.             var hoverId = id + Srch.U.Ids.hover;
  45.             var hoverUrl = "~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_Default_HoverPanel.js";
  46.             showHoverPanelCallback = Srch.U.getShowHoverPanelCallback(itemId, hoverId, hoverUrl);
  47.         }
  48.         var displayPath = Srch.U.getHighlightedProperty(id, ctx.CurrentItem, "Path");
  49.         if ($isEmptyString(displayPath)) {displayPath = $htmlEncode(ctx.CurrentItem.Path)}
  50.         var url = ctx.CurrentItem.csr_Path;
  51.         if($isEmptyString(url)){
  52.             var useWACUrl = !$isEmptyString(ctx.CurrentItem.ServerRedirectedURL);
  53.             if(ctx.ScriptApplicationManager && ctx.ScriptApplicationManager.states){
  54.                 useWACUrl = (useWACUrl && !ctx.ScriptApplicationManager.states.openDocumentsInClient);
  55.             }
  56.             if(useWACUrl)
  57.             {
  58.                 url = ctx.CurrentItem.ServerRedirectedURL;
  59.             } else {
  60.                 url = ctx.CurrentItem.Path;
  61.             }        
  62.         }
  63.         ctx.CurrentItem.csr_Path = url;
  64.         var pathLength = ctx.CurrentItem.csr_PathLength;
  65.         if(!pathLength) {pathLength = Srch.U.pathTruncationLength}
  66.  
  67.         var maxTitleLengthInChars = Srch.U.titleTruncationLength;
  68.         var termsToUse = 2;
  69.         if(ctx.CurrentItem.csr_PreviewImage != null)
  70.         {
  71.             maxTitleLengthInChars = Srch.U.titleTruncationLengthWithPreview;
  72.             termsToUse = 1;
  73.         }
  74.  
  75.         var clickType = ctx.CurrentItem.csr_ClickType;
  76.         if(!clickType) {clickType = "Result"}        
  77. _#-->
  78.         <div id="_#= $htmlEncode(id + Srch.U.Ids.body) =#_" class="ms-srch-item-body" onclick="_#= showHoverPanelCallback =#_">
  79.           <!--#_
  80.             if (!$isEmptyString(ctx.CurrentItem.csr_Icon)) {
  81. _#-->
  82.           <div class="ms-srch-item-icon">
  83.             <img id="_#= $htmlEncode(id + Srch.U.Ids.icon) =#_" onload="this.style.display='inline'" src="_#= $urlHtmlEncode(ctx.CurrentItem.csr_Icon) =#_" />
  84.           </div>
  85.           <!--#_
  86.             }
  87.             var titleHtml = String.format('<a clicktype="{0}" id="{1}" href="{2}" class="ms-srch-item-link" title="{3}" onfocus="{4}" {5}>{6}</a>',
  88.                                           $htmlEncode(clickType), $htmlEncode(id + Srch.U.Ids.titleLink), $urlHtmlEncode(url), $htmlEncode(ctx.CurrentItem.Title),
  89.                                           showHoverPanelCallback, appAttribs, Srch.U.trimTitle(title, maxTitleLengthInChars, termsToUse));
  90. _#-->
  91.           <div id="_#= $htmlEncode(id + Srch.U.Ids.title) =#_" class="ms-srch-item-title">
  92.             <h3 class="ms-srch-ellipsis">
  93.               _#= titleHtml =#_
  94.             </h3>
  95.           </div>
  96.           <!--#_
  97.             if (!$isEmptyString(ctx.CurrentItem.HitHighlightedSummary)) {
  98. _#-->
  99.           <div id="_#= $htmlEncode(id + Srch.U.Ids.summary) =#_" class="ms-srch-item-summary">_#= Srch.U.processHHXML(ctx.CurrentItem.HitHighlightedSummary) =#_</div>
  100.           <!--#_
  101.             }
  102.             var truncatedUrl = Srch.U.truncateHighlightedUrl(displayPath, pathLength);
  103. _#-->
  104.           <div id="_#= $htmlEncode(id + Srch.U.Ids.path) =#_" tabindex="0" class="ms-srch-item-path" title="_#= $htmlEncode(ctx.CurrentItem.Path) =#_" onblur="Srch.U.restorePath(this, '_#= $scriptEncode(truncatedUrl) =#_', '_#= $scriptEncode(ctx.CurrentItem.Path) =#_')" onclick="Srch.U.selectPath('_#= $scriptEncode(ctx.CurrentItem.Path) =#_', this)" onkeydown="Srch.U.setPath(event, this, '_#= $scriptEncode(ctx.CurrentItem.Path) =#_', '_#= $scriptEncode(truncatedUrl) =#_')" >
  105.  
  106.           </div>
  107.         </div>
  108.         <!--#_
  109.         if (!$isEmptyString(ctx.CurrentItem.csr_PreviewImage))
  110.         {
  111.             var altText = Srch.Res.item_Alt_Preview;
  112.             if(!$isEmptyString(ctx.CurrentItem.csr_PreviewImageAltText)){
  113.                 altText = ctx.CurrentItem.csr_PreviewImageAltText;
  114.             }
  115.  
  116.             var onloadJS = "var container = $get('" + $scriptEncode(id + Srch.U.Ids.preview) + "'); if(container){container.style.display = 'inline-block';}" +
  117.                            "var path = $get('" + $scriptEncode(id + Srch.U.Ids.path) + "'); if (path) { Srch.U.ensureCSSClassNameExist(path, 'ms-srch-item-preview-path');}" +
  118.                            "var body = $get('" + $scriptEncode(id + Srch.U.Ids.body) + "'); if (body) { Srch.U.ensureCSSClassNameExist(body, 'ms-srch-item-summaryPreview');}";
  119.  
  120.             var previewHtml = String.format('<a clicktype="{0}" href="{1}" class="ms-srch-item-previewLink" {2}><img class="ms-srch-item-preview" src="{3}" alt="{4}" onload="{5}" /></a>',
  121.                                         $htmlEncode(clickType), $urlHtmlEncode(url), appAttribs, $urlHtmlEncode(ctx.CurrentItem.csr_PreviewImage), $htmlEncode(altText), onloadJS);
  122. _#-->
  123.           <div id="_#= $htmlEncode(id + Srch.U.Ids.preview) =#_"class="ms-srch-item-previewContainer">
  124.           _#= previewHtml =#_
  125.         </div>
  126.         <!--#_
  127.         }
  128. _#-->
  129.  
  130.  
  131.  
  132.  
  133.         <div id="_#= $htmlEncode(hoverId) =#_" class="ms-srch-hover-outerContainer"></div>
  134.       </div>
  135.       <!--#_
  136.         }
  137. _#-->
  138.     </div>
  139.   </body>
  140. </html>
That’s all we need to do.

Sunday, May 25, 2014

Introduction to SharePoint 2013 search Display Templates

When we consider SharePoint 2013 features, Search is given a high priority. Some SharePoint sites are entirely driven by search where they have search web parts to discover relevant information.
In many cases the default look and feel of search web parts are not adequate. We may need to add more information or simply change the user experience of those web parts. In SharePoint 2010 we had to change the XSLT of the web part to do those modifications. But with SharePoint 2013 that is no longer an option.
We need to modify or create templates (HTML) those render information for search web part. Those templates are called Display Templates.
Rather than explaining Display Templates in a single post, I’ll discuss each topic in different blog posts as below
It’s essential to know important components of a search web part prior to the discussion on Display Templates. Then we will learn how those components map in to Display Templates.
image
As I mentioned in the above diagram, a search web part can have three main components. They are,
1. Control search results
This manages overall structure of the web part. If we need to change how a collection of result is displayed (e.g.: as lists, images, slides , etc..) we need to change the control search results component. To do the change we have to modify relevant “Control Display Template”.
2. Search result item
This represents an individual search result item within a search web part. To change the design of search result items we have to modify relevant “Item Display Template”.
An Item Display Template calls Item_CommonItem_Body template to render it’s body.
3. Hover Panel
This represent the popup which contains additional information on a search result item. To change the look and feel of this component we have to change relevant HoverPanel Item Display Template.
A HoverPanel Item Display Template calls Item_CommonHoverPanel_Body template to render it’s body
As mentioned in the below image, each type of Display Template does not operate in isolation. They have a relationship with each other.
image
You can find those templates in “<Site URL>_catalogs/masterpage/Display Templates/Search”. It’s better to map above URL as a network drive to easily access those files.
image
As you can see there are two files for each Display Template. We have to create the html file where the SharePoint itself will generate the respective JavaScript file. Generated JavaScript file is used as the Display Template internally.
Hope this post gave you some background knowledge on search Display Templates.
We will discuss further on Item Display Templates in our next blog post