Dot Net Mafia

Group site for developer blogs dealing with (usually) Ionic, .NET, SharePoint, Office 365, Mobile Development, and other Microsoft products, as well as some discussion of general programming related concepts.

This Blog



Corey Roth [MVP]

A SharePoint MVP bringing you the latest time saving tips for Ionic, SharePoint, and Office 365.

#SPC14 Post - Using Display Templates to format search results as a grid

Today at SharePoint Conference 2014 in session #SPC3000 on Display Templates, I showed how to format your search results as a grid when using the Content Search web part.  As promised, this blog post has gone live that walks you through the exact same steps we performed in the demo.  I've also attached the display templates in this post so that you can get started immediately.

To create a grid with display templates, we need to create a new Control display template and a new Item display template.  The control wraps the items so this is where the start of our table will begin.  You can build your display templates with design manager, the master page gallery, or with SharePoint Designer.

Building the Control Display Template

I don't like to build display templates from scratch, so we'll start by taking a copy of Control_List.html and call it Control_Grid.html.  Edit the file and start by changing the title tag.  This is what is shown in the Content Search web part.


You can optionally change the description if you would like.

<mso:MasterPageDescription msdt:dt="string">This is a grid.</mso:MasterPageDescription>

Next give the root div a new id.

<div id="Control_Grid">

Ultimately, we are going to use the jQuery DataTables library to make our tables look pretty.  We'll go ahead and include all of the necessary scripts now.  I am pulling them in via CDN.  You can tweak the version numbers as desired.  Add these into the script element.

$includeScript(this.url, "");

$includeScript(this.url, "");

$includeCSS(this.url, "");

The ListRenderRenderWrapper wraps each item template rendered with an li tag.  We need these so remove the two push statements.

To work with our table and table header, we need to generate some Ids.  These Ids we'll use with jQuery code later to add elements.  We do this in the same manner as you see in other display templates used in the search center.  We use ctx.ClientControl.get_nextUniqueid() to generate a random identifier.  We then append strings onto it for our own use.  You can add the following statements anywhere in the first JavaScript block (just not inside the wrapper).

var encodedId = $htmlEncode(ctx.ClientControl.get_nextUniqueId() + "_Table_");

var headerRowId = $htmlEncode(encodedId + "_HeaderRow_");

We now need to remove our ul elements and replace them with a table.  We're going to use the encodedId from above as our id.

<table  id="_#= encodedId =#_" class="resultsTable">

We will also want to close the table tag after ctx.RenderGroups.  Here's what the section looks like.

var noResultsClassName = "ms-srch-result-noResults";


var encodedId = $htmlEncode(ctx.ClientControl.get_nextUniqueId() + "_Table_");

var headerRowId = $htmlEncode(encodedId + "_HeaderRow_");


var ListRenderRenderWrapper = function(itemRenderResult, inCtx, tpl)


    var iStr = [];


    return iStr.join('');


ctx['ItemRenderWrapper'] = ListRenderRenderWrapper;


    <table  id="_#= encodedId =#_" class="resultsTable">

            _#= ctx.RenderGroups(ctx) =#_


Building the Item Display Template

The Item Display Template gets called once for every search result.  We need to create a display template that renders a table row as opposed to a list item.  To do this, we're going to copy Item_Diagnostic.html.  We'll call this new copy, Item_GridRow.html.  Start by changing the title tag of this file too.

<title>Grid Row</title>

You can optionally change the description if you would like.  Next, give the root div a new name.

<div id="Item_GridRow">

Now, we want to remove the existing HTML markup as we don't need it.  Remove everything between the ul tags.  Then we are going to replace it with our logic to display the table row.  If you remember, the diagnostic control, it has ten managed properties that you can map, Line 1 through Line 10.  This code simply writes out a new table row and then iterates through those lines 1 through 10.  The managed property and it's value are assigned to the lineValueInfo object.  If a managed property is mapped, it will write out the value.  If, it's mapped but there is no value, it will write out an empty table cell.  If a managed property hasn't been mapped, then it will not do anything.  This allows for the grid to feature the exact number of mapped columns and not have any empty cells.

<tr class="gridRow">       


for(var lineNum = 1; lineNum <= 10; lineNum++)



    var lineValueInfo = $getItemValue(ctx, String.format("Line {0}", lineNum));

    if(!$isNull(lineValueInfo) && !$isEmptyString(cbsDiagnostic_RenderPropertyMappings(lineValueInfo)))


        var lineId = String.format("{0}line{1}", encodedId, lineNum);

        var slotName = String.format($resource("item_Diagnostic_SlotNameFormat"), lineNum);





        if (ctx.CurrentItemIdx == 0)



            <td>_#= lineValueInfo =#_</td>













If you are getting confused with the cutting and pasting.  Don't worry because I've attached the display templates to this post.

Trying out the display templates

Add a content search web part to a page and then set the Control template to Grid and the Item template to Grid Row.


Your results should change to look like a grid now.  It's not very pretty yet, but it's a start.


We can add a few fields to it using the property mapping.  Start by checking the Change the mapping... checkbox.  In my example, I am going to add Author, Write, and Path.


Here is what our grid looks like now.


Adding a header row

Unfortunately, something that seems as simple as adding a header row is quite complicated.  The header row belongs in the control template.  However, we don't know what the managed properties are inside of this template.  We only have access to them from the item template.  Our technique to work around this is to collect the information in an array when the item template is executed.  Then we'll use an OnPostRender event to look at that array and build the header row.  Think of OnPostRender as your equivalent to $(Document).Ready() in jQuery.  We'll simply add this array to the ctx object.

Go back to Control_Grid.html and add the following line after the headerRowId line.  This initializes our array.  Now we just need to add some values to it.

ctx.ManagedPropertyNames = [];

Now, we need to add a table header inside the table element we originally created.  Notice the use of headerRowId that we defined before.


    <tr id="_#= headerRowId =#_" class="resultsTableHeader">



Now switch to Item_GridRow.html.  Here we are going to add some inside the loop.  The lineValueInfo object has the name of the managed property.  Add this snippet into the if statement checking if lineValueInfo is empty or not.

if (ctx.CurrentItemIdx == 0)


Since the item template gets executed multiple times, we only want to add this information once.  Using ctx.CurrentItemIdx, we can check to see if this is the first iteration of this display template.

Now we need to go back to Control_Grid.html and create the OnPostRender event.  You can add this code after the line you just added.

ctx.OnPostRender = [];

ctx.OnPostRender.push(function () {

    for(var i = 0; i < ctx.ManagedPropertyNames.length; i++)


        $(".resultsTableHeader").append("<th>" + ctx.ManagedPropertyNames[i] + "</th>");           



This code iterates through the managed properties stored in the ctx.ManagedPropertyNames array.  It then just appends a th element for each property name.

Save your display templates and then refresh your page in the browser and now we should have the names of the managed properties above each column.


Using DataTables

At this point, we have the makings of a good grid, but it's still quite easy.  Now is a good time to enlist the help of the jQuery DataTables plug-in.  The key to using this plug-in is having a well formatted table.  That means no missing cells or anything like that.  Luckily, I took all of the hard work out of that and our code accommodates for that.  Since we have already registered the script, it only takes one line of code to take advantage of it.  Just add this to the end of the OnPostRender function.  We just need to use the encodedId to get a reference to our table and then apply the dataTable() method to it.  I passed a few parameters to it as well such as disabling paging and sorting.  You can look up additional parameters on the plug-in's website. 

$("#" + encodedId).dataTable({ "bPaginate": false, "bAutoWidth": true, "bSort": false });

The entire OnPostRender method now looks like this.

ctx.OnPostRender = [];

ctx.OnPostRender.push(function () {

    for(var i = 0; i < ctx.ManagedPropertyNames.length; i++)


        $(".resultsTableHeader").append("<th>" + ctx.ManagedPropertyNames[i] + "</th>");           



    $("#" + encodedId).dataTable({ "bPaginate": false, "bAutoWidth": true, "bSort": false });


Now refresh your page, and you should have a nice grid that looks like this.


Now that looks a lot better.  You can use your own CSS to apply your own colors of course. 

As promised, I have attached the display templates to this post.  Feel free to try them out, use them for yourself, and improve them.  If you find them useful, feel free to leave a comment.



Stef said:

Hi Corey,

Thanks for your post, it looks great. However, I can't get the templates to work. I have even tried completely copying and pasting all of your attached files and still no joy.

I seem to be getting an error "Cannot call method 'fnSetData' of undefined (OnPostRender: )". Any ideas?


March 13, 2014 6:49 AM

CoreyRoth said:

@Stef This can happen if the datatables library doesn't load before the rest of your code executes this.  Try refreshing the page, including the datatables script locally, or using SP.SOD.executeFunc instead of $includeScript.

March 16, 2014 8:40 PM

John said:

I had the same issue with datatables loading before jQuery. SP.SOD.executeFunc did it for me.

April 2, 2014 8:47 AM

Tam Lai said:

Hi Corey,

I have only "en-us" folder in  "Language Files" folder, that causes content search web part  cant not find en-gb/customstrings.js. I dont know how to get the other locale folders like in your picture above. Can you help me?

May 27, 2014 4:05 AM

CoreyRoth said:

@Tam I assume they are created by installing language packs on-premises.  However, if those packs were installed after the site was created, it probably won't create the folder.  You should be able to create it manually and have it work though.

May 27, 2014 9:54 AM

Kameron Berget said:

I have set this up but my issues that is that data-tables have their own paging. SharePoint can only return 50 results and the datatable just ends up paging those 50 and leave the additional >50 alone. Anyway around this?

August 7, 2014 9:43 AM

Ken M said:

What do you think of taking this and adapting it in to the ListWithPaging control html???

August 14, 2014 3:20 PM

CoreyRoth said:

@Ken M It should be possible, but I haven't tried it. You would just need to move the code for the control into a display template adapted off of ListWithPaging.

August 18, 2014 12:05 PM

Joris said:

@John, can you share the modified sample which is using SP.SOD.executeFunc?

August 26, 2014 6:30 AM

Shweta said:

I am facing the same issue: "Unable to get property 'fnSetData' of undefined or null reference (OnPostRender: )"

September 1, 2014 8:30 PM

Dennis said:

I was able to get this to work just fine. Very nice. I do have a question. It seems to only work for queries on sites and site collections. Does one need to use a different template to have this work with Documents?

September 8, 2014 7:52 AM

CoreyRoth said:

@Dennis the display templates don't affect how your query executes.  This should work regardless of what your query is.

September 8, 2014 1:51 PM

Tony said:

@Stef and @Corey, Thanks for this post. I am also receiving Cannot "read property 'fnSetData' of undefined (OnPostRender" could you show share what you did?

September 25, 2014 3:26 PM

Kirsty said:

Is anyone able to help solve the 'fnSetData' problem mentioned above with an example?

October 1, 2014 12:20 AM

Mitika said:

@Corey : Very Nice & helpful Post.


For  'fnSetData'  problem, i removed below line in Control_Grid.html :

$("#" + encodedId).dataTable({ "bPaginate": false, "bAutoWidth": true, "bSort": false });

November 26, 2014 4:12 AM

Murali said:

@Corey : Very Nice & helpful Post

I am facing issue

'Line 5'{Firstname}:'FName'

I have to display Firstname as column header

in your case I am getting FName as Columnheader

June 11, 2015 7:59 AM

Jonathan said:

Hi Corey,

Thanks for a great video & this help guide, very very interesting.  I have tried to deploy in my environment, followed all the above step by step but keep getting the ubiquitous "Sorry, something went wrong" message :/

The error is:

Unable to get property 'push' of undefined or null reference (CoreRender: ~sitecollection/_catalogs/masterpage/Display Templates/Content Web Parts/

Any ideas what I've missed?



July 6, 2015 6:54 AM

Mingyu said:

Hi Corey,

I am doing a project for a comapny's FAQ site. And I used this content search display template to show most accessed FAQ items.

Besides title and date, they also want to display the access ranking 1~10 number in front of each line. But I know the number is not a managed property. How should as another column for numbers?

thanks in advance

October 18, 2015 6:05 AM

Ujawala said:

How to download your created custom templates?

November 18, 2015 4:34 AM

Amit said:

Hi Corey

I am Getting Below Error -

$(...).dataTable is not a function (OnPostRender: )

Do you have any idea what i have missed?


October 20, 2016 1:23 AM

Leave a Comment


About CoreyRoth

Corey Roth is an independent SharePoint consultant specializing in ECM, Apps, and Search.
2018 dotnetmafia.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems