Create custom Display Templates in classic SharePoint Search site

Classic SharePoint Search is still a good fit


There are features in the classic search like Search Results, Result Types, Query Rules etc. not yet configurable OOTB on the modern pages. Having the PnP responsive pack applied makes the classic search sites semi-responsive. It is still something worth taking into account when designing new solutions because it can be quickly configured, and the provisioning of the search site with all its configurations can be fully automated through the PnP Provisioning engine.


Custom Display Templates in the search results web part


The image below shows customized search results page that uses custom display template. The template has an icon, URL, redirects to a custom URL that is mapped against RefinableString50 managed property, but also sends event to app insights when the URL is clicked by user. This is how my final display template looks like.

SharePoint site opened in browser

To create display template, we have to create new file with HTML extension or download one from the default templates library (e.g. ~site/_catalogs/masterpage/Display Templates/Search/Item_Default.html) rename it and modify it after. I named mine Item_VisionMyPlacePromoted.html and this is how the code within my template looks.



<html xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882">

<head>
<title>MyPlace Promoted Item</title>

<!--[if gte mso 9]><xml>
<mso:CustomDocumentProperties>
<mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden>
<mso:MasterPageDescription msdt:dt="string">Displays the Vision MyPlace Promoted result item template.</mso:MasterPageDescription>
<mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106603</mso:ContentTypeId>
<mso:TargetControlType msdt:dt="string">;#SearchResults;#</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', 'RefinableString50':'RefinableString50'</mso:ManagedPropertyMapping>
<mso:HtmlDesignConversionSucceeded msdt:dt="string">True</mso:HtmlDesignConversionSucceeded>
<mso:HtmlDesignStatusAndPreview msdt:dt="string">https://xxx.sharepoint.com/sites/xxx/_catalogs/masterpage/Display Templates/Search/Item_VisionMyPlacePromoted.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_VisionMyPlacePromoted">
<!--#_
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/Item_Default_HoverPanel.js";
$setResultItem(itemId, ctx.CurrentItem);
            if(ctx.CurrentItem.IsContainer){
                ctx.CurrentItem.csr_Icon = Srch.U.getFolderIconUrl();
            }
            ctx.currentItem_ShowHoverPanelCallback = Srch.U.getShowHoverPanelCallback(itemId, hoverId, hoverUrl);
ctx.currentItem_HideHoverPanelCallback = Srch.U.getHideHoverPanelCallback();
window.trackVisionSearchQuickLinkClick = function(title, url) {

if(window.appInsights) {
var data = {
"source": "visionSearch",
"title": title,
"url": url,
"fromPage": window.location.href
}
window.appInsights.trackEvent("visionSearchQuickLinkClick", data);
console.log(data);
}
}
_#-->
<div id="_#= $htmlEncode(itemId) =#_" name="Item" data-displaytemplate="VisionMyPlacePromotedItem" class="ms-srch-item" onmouseover="_#= ctx.currentItem_ShowHoverPanelCallback =#_"
onmouseout="_#= ctx.currentItem_HideHoverPanelCallback =#_">
<a href="_#= ctx.CurrentItem.RefinableString50 =#_" target="_blank" title="_#= ctx.CurrentItem.Title =#_" style="font-size: 1.3em;font-weight: 400;" onclick="window.trackVisionSearchQuickLinkClick('_#= ctx.CurrentItem.Title =#_', '_#= ctx.CurrentItem.RefinableString50 =#_');" ><svg style="max-width: 20px;margin: 0px 5px -8px 0px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M336 0H48C21.49 0 0 21.49 0 48v464l192-112 192 112V48c0-26.51-21.49-48-48-48zm0 428.43l-144-84-144 84V54a6 6 0 0 1 6-6h276c3.314 0 6 2.683 6 5.996V428.43z"/></svg>_#= ctx.CurrentItem.Title =#_</a>
<div id="_#= $htmlEncode(hoverId) =#_" class="ms-srch-hover-outerContainer"></div>
</div>
<!--#_
}
_#-->
</div>
</body>

</html>
<!--
Font Awesome Free 5.1.0 by @fontawesome - https://fontawesome.com
License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->


Brief explanation on the Display template markup


To write JavaScript we should use strange syntax like


<!--#_ //JavaScript here _#-->


or in the HTML attributes like that


<a id="_#= $htmlEncode(itemId) =#_" ...


then the HTML template will be converted into raw JavaScript when published to SharePoint. More detailed information can be found on the Microsoft docs site.


Custom metadata in Display Template


One important piece of the template is the <mso:ManagedPropertyMapping msdt:dt="string"> section where we can specify our own managed properties mapping and the search api will pull the data for us. This is how I specified a refinable string property 'RefinableString50' in the template and then I use it in the HTML of the template to display the data that is in RefinableString50.


<a href="_#= ctx.CurrentItem.RefinableString50 =#_" ...


or as a JavaScript function parameter


<a ... onclick="window.trackVisionSearchQuickLinkClick('_#= ctx.CurrentItem.Title =#_', '_#= ctx.CurrentItem.RefinableString50 =#_');" > ...


Custom functions in Display Template


I added a custom function in the template to track clicks for that URL using Application Insights. So, this is the part of my custom function. Note the application insights script is not injected by the template, it assumes it is loaded on the page. Here is a post on how to bring the app insights on a search page.



window.trackVisionSearchQuickLinkClick = function(title, url) {

if(window.appInsights) {
var data = {
"source": "visionSearch",
"title": title,
"url": url,
"fromPage": window.location.href
}
window.appInsights.trackEvent("visionSearchQuickLinkClick", data);
console.log(data);
}
}
// then in the HTML of the template
<a href="_#= ctx.CurrentItem.RefinableString50 =#_" onclick="window.trackVisionSearchQuickLinkClick('_#= ctx.CurrentItem.Title =#_', '_#= ctx.CurrentItem.RefinableString50 =#_');" ></a>


Keep all assets together in the Display Template


I added a svg file in the template directly, so I do not have assets dependencies. I also added some inline css styles. This way the template will have all of it so no need to split across different files or reference such. For me, less dependencies in files means we can maintain all in one file, but the template should not be a complex one, if more complex logic has to be added then splitting to multiple files and assets could work better.


How to deploy Display Template


To deploy our search display template, we have to go to the masterpage gallery of our search classic site usually at https://contoso.sharepoint.com/<YOUR_SEARCH_SITE>/_catalogs/masterpage/Display Templates/Search and drop the .html file there. A popup will open asking for more details.


~sitecollection/_catalogs/masterpage/Display Templates/Search


Once the file is uploaded, then there will be another popup window that we should pay attention.


SharePoint site opened in browser

The important pieces are to double check if the desired metadata fields are in the Managed Property Mappings input box and the Associated File should be checked. When the associated file is checked it will compile a JavaScript file that will be used by the search results web part on a classic search page.

The file must be published it in order the changes to take effect. By default, when uploaded the file is in draft mode (minor version) and it has to be published.


SharePoint site opened in browser

Automated deployment of a display template


I use the PnP provisioning engine to deploy templates. Here is my XML template files part.


...
<pnp:Files>
<pnp:File Src="_catalogs\masterpage\Display Templates\Search\Item_VisionMyPlacePromoted.html" Folder="_catalogs/masterpage/Display Templates/Search" Overwrite="true" Level="Published" />
</pnp:Files>
...


How to make the Display Template available on a classic search page


This can be done directly in a search results web part, but I usually create new Result Type and setup specific content type to be rendered using custom display template. If we go to site settings, then navigate to Site Collection Administration and select Result Types we will end up on a page with URL ~site/_layouts/15/manageresulttypes.aspx?level=sitecol. My custom Result Type can be seen from picture below and the New Result Type button can be used to create a new result type.


SharePoint site opened in browser

When new result type is created, we have the option to specify which Display Template to be used based on condition. The important part is to specify a condition. In my case I have to show my custom display template for specific content type. Under actions I selected my custom display template so when the user searches for content from classic search results page and if there are any results based on the content type then the user will see the custom display template.


SharePoint site opened in browser

Debugging SharePoint Display Template


Once the .html file is uploaded to the Library, SharePoint automatically generates JavaScript file that is being loaded on a search results page when user searches for content that is suppose to use the template. Then we can use browser developer tools to debug our template.


Google Chrome developer tools opened

Conclusion


The fact that the classic SharePoint search web parts are configurable, and the provisioning can be fully automated makes the classic search more attractive at this current moment for me than custom effort to bring it on a modern client side page. We can take the advantage of having our own custom display templates to provide better user experience. The template markup and JavaScript objects can sometimes be tricky, but it is still less development effort. The benefit is that we can do it in less time, the downside is that it requires some template knowledge and cannot easily be unit tested nor debugged offline. P.S: How we navigate from modern page to the classic search is something I would not touch in this post.


Useful links

  1. My 10 SharePoint display template tips and tricks