Apply custom logic for all display templates

customize item_commonitem_body.html display template

Frank Chen
7 min readAug 16, 2017

Problems

SharePoint 2013 Search Display approach was out there about a few years and many people loved the features which allowed them to customize the search results based on the JavaScript. Even though it still leveraged the old ASP type of rendering process, working JavaScript provides many benefits for developers regarding the customization.

Recently, I helped one of my customers to implement Cloud Hybrid Search for their SPO and On-Premise SharePoint. My customer has a medium to large SharePoint On-Premise farm which includes a few Web Application. After hybrid Search integration, the end users are able to use a unified search center on SPO to search the content located on SPO and On-Premise SP. Since the end users are able to use search center from anywhere, it actually introduced a requirement that if a on-premises content is shown for a external user connected from an external network, we need to show an indicator that this is a on-premises content and the end user will expect to see a 500 error if they don’t have VPN connected before. When I first looked at this requirement, I immediately think about the display template customization. I can create a custom display template and apply this template to a result type defined for all On-Premise contents. But it actually bring up a question that the default OOTB result type won’t be able to applied for On-Premise contents. Let’s explain a little bit detail, since you create a display template and map it to a result type, the search result will render results using a display template defined in a result type, it actually lose the OOTB features. For example, if a user search a On-Premise document, the result won’t show a nice word icon in front of result like any other OOTB word result because your custom display template takes over the rendering process.

Research

As Understanding how search results are displayed in SharePoint Server 2013 mentioned, the following diagram shows how display template works with result types. No matter which result type your search results belong to, they are all calling “Common Item Display Template” defined in item_commonitem_body.html file. The logic a display templates call this common template is thought _#=ctx.RenderBody(ctx)=#_ which you can find from all OOTB display templates. It actually enlightened me that maybe I can customize item_commonitem_body.html to inject my logics.

After I implemented that in item_commonitem_body.html, I immediately found out that it might not be a good approach. The reason is that my changes will be overwritten if SPO has a update for this display template since it’s OOTB template. As SPO rolled out the new features really quick, it could happen tomorrow.

I raised this question in Microsoft tech community and didn’t get any responses. When I was struggling, I found out SP2013 — Changing the default group template in Search Results WP (GroupTemplateId) which basically mentioned about how to change Group Template. The way to export Search Result Web Part and change the group template inspired me.

Basically, when you export Search Result Web Part, it actually generates a xml for web part configuration. There is one element called “ItemBodyTemplateId” which defined how the Search Result Web Part loads a common item body template. See the screenshot below. I can create my own common item template by copying from the original item_commonitem_body.html and point “ItemBodyTemplateId” element to my custom display template. I can add whatever customization I want to my new template. By doing that, I can inject my logics after all OOTB display templates rendering without using OOTB item_commonitem_body.html template.

Implementation

Change default Item_commonitem_body.html

Before we move in the display template, let’s first look at how to change the default item_commonitem_body and use our own. The following steps list the detail (the steps will be same for On-Premise SharePoint although I use SPO as a example):

  • Go to display template folder under master page gallery at your search center on SPO.
  • Download item_commonitem_body.html to your local and rename it to Item_HybridSearchItem_Body.html.
  • Upload Item_HybridSearchItem_Body.html back to display template folder in your SPO search center.
  • SPO will generate Item_HybridSearchItem_Body.js automatically. Get Item_HybridSearchItem_Body.js URL which we will use for “Search Result” web part.
  • Go to your result.aspx in your SPO search center.
  • Change page to the editing mode by click “gear icon”-> “Edit Page”.
  • Select “Search Result” web part and click small triangle icon on the web part right upper corner to bring up the web part context menu. Click “Export” menu to export the “Search Result” web part and save as “Search Result.webpart”.
  • Open “Search Result.webpart” xml and search for <property name=”ItemBodyTemplateId”.
  • Type “Item_HybridSearchItem_Body.js” URL to the value of this element. The following is an example
<property name=”ItemBodyTemplateId” type=”string”>~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_HybridSearchItem_Body.js</property>
  • Save the Search Result.webpart.
  • Go back to the result.aspx page and remove the original “Search Result” web part.
  • Click “Add a Web Part” to bring up Adding Web Part UI. Click “Upload a Web Part” link on the left side and upload your Search Result.webpart xml to there.
  • After uploading, you will see your Search Result web part available for selection.
  • Add your Search Result web part to the page and reorganize it on the page. Save the page.
  • When you search something, bring developer tool dialog, you should be able to see Item_HybridSearchItem_Body.js file is loaded.
  • Now you can add your logics in Item_HybridSearchItem_Body.html and upload it back to display template folder to see the changes.

Pass original ctx.CurrentItem to your post back

After you create a new common template from copying the original, you can add the logic over there. As we mentioned before, since display template is using ASP type of rendering approach, you cannot directly add your DOM manipulation logic in the code because the html elements are not ready yet. You need to either rely on AddPostRenderCallback() or ctx.OnPostRender() method.

When you use AddPostRenderCallback() method, you will find out that the ctx.CurrentItem won’t be available after your callback is run. The reason is because ctx.CurrentItem will be reset when rendering is completed. You can leverage Function.createDelegate() to pass the ctx.CurrentItem or detail properties to there. The following cod sample shows how:

var postRenderCallbackArgs={titleId: titleId,currentItem:ctx.CurrentItem};AddPostRenderCallback(ctx, Function.createDelegate(postRenderCallbackArgs, function(){var parameter = this;//you get your ctx.CurrentItem.console.log(parameter.currentItem);}));

Load jQuery

When I manipulated DOM element, I needed jQuery to help me easily generate DOM elements. There are a lot of articles available to show how to reference external JavaScript library from your display template. I am not going to repeat that. I just list my code sample as the following:

var postRenderCallbackArgs={titleId: titleId,currentItem:ctx.CurrentItem};AddPostRenderCallback(ctx, Function.createDelegate(postRenderCallbackArgs, function(){var parameter = this;var rendering=function(para){//your logics go there.};//load jQueryif(typeof jQuery === “undefined”){RegisterSod(‘jquery-1.11.2.min.js’, ‘https://code.jquery.com/jquery-1.11.2.min.js');EnsureScriptFunc(‘jquery-1.11.2.min.js’, ‘jQuery’, function () {rendering(parameter);});}else{console.log(“jQuery is loaded”);rendering(parameter);}}));

Get more Managed Properties

For our hybrid search scenario, Cloud SSA create a managed property called “IsExternalContent” to differentiate the content between SPO and On-Premise. We need to look at this managed property’s value to add the indicator. Let’s step back a little bit to look at how to include additional managed properties in our common item body template. When we need to include additional managed properties, we normally add them into <mso:ManagedPropertyMapping> element inside display template. I opened up my own common item template which is item-HybridSearchItem_Body.html and found out it has the same <mso:ManagedPropertyMapping> element as other OOTB templates. However, no matter how I changed this element, the Managed Properties I was trying to add over there wasn’t able to show up in the ctx.CurrentItem object. Since this is a template called after result type display template is call, I think it makes sense to rely on the result type display template to include managed properties instead of this common item body template. The solution for this issue is to run a secondary search to retrieve whatever managed properties I am interested in based on a Id. Which Id can I use? I checked the ctx.CurrentItem object. It seems like “DocId” is the one, but it cannot be used for search. I have to use “WorkId” which has same value as “DocId” and can be used for search. The idea will be that we first get “WorkId” from ctx.CurrentItem and use that id to search and include the properties for result. The following code sample shows the detail:

var postRenderCallbackArgs={titleId: titleId,currentItem:ctx.CurrentItem};AddPostRenderCallback(ctx, Function.createDelegate(postRenderCallbackArgs, function(){var parameter = this;var rendering=function(para){//console.log(“onPostRender event.”);//console.log(para);var context = Srch.ScriptApplicationManager.get_clientRuntimeContext();var keywordQuery = new Microsoft.SharePoint.Client.Search.Query.KeywordQuery(context);keywordQuery.set_queryText(‘WorkId:”’+para.currentItem.WorkId+’”’);var properties = keywordQuery.get_selectProperties();properties.add(‘IsExternalContent’);var searchExecutor = new Microsoft.SharePoint.Client.Search.Query.SearchExecutor(context);var results = searchExecutor.executeQuery(keywordQuery);var titleId = “#”+$htmlEncode(para.currentItem.csr_id + Srch.U.Ids.titleLink);var searchCallbackArgs={ctrlLink:$(para.titleId),ctrlImage: $(“<img src=’”+Srch.U.replaceUrlTokens(“~site/PublishingImages/giphy.gif”)+”’ style=’hight:16px;width:16px;’>”),results: results,currentItem: para.currentItem};context.executeQueryAsync(Function.createDelegate(searchCallbackArgs, function(){var searchArgs = this;var resultTables=this.results.m_value.ResultTables;//console.log(searchArgs);if(resultTables !=null &&resultTables.length>0 &&resultTables[0].ResultRows!=null &&resultTables[0].ResultRows.length>0){var resultRow = resultTables[0].ResultRows[0];if(resultRow[“IsExternalContent”]!=undefined && resultRow[“IsExternalContent”]){//insert your logics for On-Premise search result.}else{//insert your logics for SPO search result.}}}),function(sender, args){console.log(“error”);});};//load jQueryif(typeof jQuery === “undefined”){RegisterSod(‘jquery-1.11.2.min.js’, ‘https://code.jquery.com/jquery-1.11.2.min.js');EnsureScriptFunc(‘jquery-1.11.2.min.js’, ‘jQuery’, function () {rendering(parameter);});}else{console.log(“jQuery is loaded”);rendering(parameter);}}));

I also created a simple project CommonDisplayTemplateSample hosted at Github. It includes display template and .webpart file. Feel free to check out.

--

--