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

Posted Thursday, February 20, 2014 9:57 AM by CoreyRoth

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.

<title>Grid</title>

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, "https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.0.min.js");

$includeScript(this.url, "https://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js");

$includeCSS(this.url, "https://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css");

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 = [];

    iStr.push(itemRenderResult);

    return iStr.join('');

}

ctx['ItemRenderWrapper'] = ListRenderRenderWrapper;

_#-->

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

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

    </table>

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(!lineValueInfo.isEmpty)

        {

        if (ctx.CurrentItemIdx == 0)

            ctx.ManagedPropertyNames.push(lineValueInfo.managedPropertyName);

_#-->

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

<!--#_

        }

        else

        {

        _#-->

            <td></td>

        <!--#_

        }

    }

}

_#-->       

</tr>

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.

ContentSearchPropertiesGridSelected

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

DisplayTemplateGridNoHeader

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.

ContentSearchPropertiesMapping

Here is what our grid looks like now.

DisplayTemplateGridNoHeaderMoreColumns

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.

<thead>

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

    </tr>

</thead>

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)

    ctx.ManagedPropertyNames.push(lineValueInfo.managedPropertyName);

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.

DisplayTemplateGridHeader

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.

DisplayTemplateGridWithDataTables

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.

Comments

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

Thursday, March 13, 2014 6:49 AM by Stef

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?

Thanks!

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

Sunday, March 16, 2014 8:40 PM by CoreyRoth

@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.

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

Wednesday, April 2, 2014 8:47 AM by John

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

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

Tuesday, May 27, 2014 4:05 AM by Tam Lai

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?

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

Tuesday, May 27, 2014 9:54 AM by CoreyRoth

@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.

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

Thursday, August 7, 2014 9:43 AM by Kameron Berget

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?

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

Thursday, August 14, 2014 3:20 PM by Ken M

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

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

Monday, August 18, 2014 12:05 PM by CoreyRoth

@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.

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

Tuesday, August 26, 2014 6:30 AM by Joris

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

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

Monday, September 1, 2014 8:30 PM by Shweta

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

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

Monday, September 8, 2014 7:52 AM by Dennis

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?

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

Monday, September 8, 2014 1:51 PM by CoreyRoth

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

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

Thursday, September 25, 2014 3:26 PM by Tony

@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?

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

Wednesday, October 1, 2014 12:20 AM by Kirsty

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

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

Wednesday, November 26, 2014 4:12 AM by Mitika

@Corey : Very Nice & helpful Post.

@Kirsty

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

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

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

Thursday, June 11, 2015 7:59 AM by Murali

@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

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

Monday, July 6, 2015 6:54 AM by Jonathan

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?

T.i,a

Jonathan

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

Sunday, October 18, 2015 6:05 AM by Mingyu

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

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

Wednesday, November 18, 2015 4:34 AM by Ujawala

How to download your created custom templates?

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

Thursday, October 20, 2016 1:23 AM by Amit

Hi Corey

I am Getting Below Error -

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

Do you have any idea what i have missed?

Thanks

Leave a Comment

(required) 
(required) 
(optional)
(required)