#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.
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.
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.
You can optionally change the description if you would like. Next, give the root div a new name.
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.
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.
<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.
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.