Using jQuery DataTables with Search Display Templates

The new display templates in SharePoint 2013 allow for some great flexibility for designers and web developers in presenting search results in almost any format desired. For a proof of concept, a client wanted to aggregate tasks from across SharePoint. The company manages multiple assets and would like to roll up all tasks for all users across all locations in SharePoint in a single location. We were able to accomplish this goal fairly easily thanks to the new search display templates available in SharePoint 2013. This project also makes use of jQuery and DataTables.

Overview

The solution will be a new search results page was created in the search center. The page search results web part will have a predefined search query focused on returning tasks, something like ContentType:Task will be a good starting point.

SharePoint 2013 search results are made up of control templates and item templates (there are others, but this will be the focus for this post). The control template is used to render the outer DOM elements for the search results, and this is a great place to initialize jQuery controls, like DataTables. The item template is used to render individual search results. Since the goal for the client is to render a list of tasks in a tabular format, the control template should create a standard HTML table. The item template will create the individual table rows from the returned search results.

Control Template

DataTables prerequisites are table that includes a thead and a tbody. To keep things simple, the table will initially only include the title of the task and the source site. The markup should appear something like the following:

<table>
<thead>
<tr>
<th>Task</th>
<th>Source Site</th>
</tr>
</thead>
<tbody><!-- ROWS HERE --></tbody>
</table>

This creates the necessary markup required by DataTables. The search results should simply be the individual rows, something like:

<tr><td><!-- TITLE --></td><td><!-- SITE NAME AND LINK --></td></tr>

Starting with an existing control makes it easy to get the necessary initial markup. I chose to start with the Control_List.html located in the control templates/content web parts folder of the master page gallery.

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

<title>Tasks Data Table</title>

<!--[if gte mso 9]><xml>
<mso:CustomDocumentProperties>
<mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden>
<mso:MasterPageDescription msdt:dt="string">This template uses jQuery DataTables to display task information.</mso:MasterPageDescription>
<mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106601</mso:ContentTypeId>
<mso:TargetControlType msdt:dt="string">;#Content Web Parts;#</mso:TargetControlType>
<mso:HtmlDesignAssociated msdt:dt="string">1</mso:HtmlDesignAssociated>
<mso:CrawlerXSLFile msdt:dt="string"></mso:CrawlerXSLFile>
<mso:HtmlDesignPreviewUrl msdt:dt="string"></mso:HtmlDesignPreviewUrl>
<mso:HtmlDesignConversionSucceeded msdt:dt="string">True</mso:HtmlDesignConversionSucceeded>
<mso:HtmlDesignStatusAndPreview msdt:dt="string">http://connect.qrinc.com/poc/aip/_catalogs/masterpage/Display Templates/Content Web Parts/Control_TaskTable.html, Conversion successful.</mso:HtmlDesignStatusAndPreview>
</mso:CustomDocumentProperties>
</xml><![endif]-->
</head>

<body>
    <script>
    </script>

    <div id="Control_TaskDataTable">

<!--#_
if (!$isNull(ctx.ClientControl) &&
    !$isNull(ctx.ClientControl.shouldRenderControl) &&
    !ctx.ClientControl.shouldRenderControl())
{
    return "";
}
ctx.ListDataJSONGroupsKey = "ResultTables";
ctx["CurrentItems"] = ctx.ListData.ResultTables[0].ResultRows;
var $noResults = Srch.ContentBySearch.getControlTemplateEncodedNoResultsMessage(ctx.ClientControl);

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

var noResultsClassName = "ms-srch-result-noResults";
_#-->

<table class="artis-tasktable" id="_#= encodedID =#_">
	<thead>
		<tr>
			<th>Task</th>
			<th>Source Site</th>
		</tr>
	</thead>
	<tbody>
		_#= ctx.RenderItems(ctx) =#_
	</tbody>
</table>	

    </div>
</body>
</html>

Line 3: This changes the display name of the template as it appears in the content by search web part.

Line 9: Provides a clearer description of the display template.

Line 25: Changes the ID of the control.

Line 38: Changes the emitted control ID.

Lines 43-53: The table that will contain search results.

DataTables needs to be initialized after the content is put into the table. In a normal project, you might simply start with the standard jQuery $(document).ready(…), but this will not work with display templates. However, exploring how the Control_Slideshow.html handles this, this behavior can be handled.

ctx.OnPostRender = [];

ctx.OnPostRender.push(function () {
	$("#" + encodedID).dataTable(
	  {
           "bPaginate": true,
           "bLengthChange": false,
           "bFilter": false,
           "bSort": false,
           "bInfo": false,
           "bAutoWidth": false,
	       "bJQueryUI": true
          }
        );
});

ctx.OnPostRender allows us to hook into the execution lifecycle to add additional methods to execute once the control has finished rendering. This is a good place to hook in and initialize DataTables. However, we still need to include the script references or we will wind up with an error message instead of a working display template. In the script above between lines 21-23 we have a place to register scripts. Just simply include the necessary scripts and CSS:

<script type="text/javascript">
        $includeScript(this.url,"~sitecollection/style library/js/jquery-1.10.2.min.js");
        $includeScript(this.url,"~sitecollection/style library/js/jquery.dataTables.min.js");
        $includeCSS(this.url,"~sitecollection/style library/css/jquery.dataTables.css");
</script>

In this example, all of the scripts and css are located in the style library of the current site collection. Using the ASP.Net placeholder, we get the full URL to the current site collection. This will include the necessary scripts to render DataTables successfully.

The final markup:

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

<!--[if gte mso 9]><xml>
<mso:CustomDocumentProperties>
<mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden>
<mso:MasterPageDescription msdt:dt="string">This template uses jQuery DataTables to display task information.</mso:MasterPageDescription>
<mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106601</mso:ContentTypeId>
<mso:TargetControlType msdt:dt="string">;#Content Web Parts;#</mso:TargetControlType>
<mso:HtmlDesignAssociated msdt:dt="string">1</mso:HtmlDesignAssociated>
<mso:CrawlerXSLFile msdt:dt="string"></mso:CrawlerXSLFile>
<mso:HtmlDesignPreviewUrl msdt:dt="string"></mso:HtmlDesignPreviewUrl>
<mso:HtmlDesignConversionSucceeded msdt:dt="string">True</mso:HtmlDesignConversionSucceeded>
<mso:HtmlDesignStatusAndPreview msdt:dt="string">http://connect.qrinc.com/poc/aip/_catalogs/masterpage/Display Templates/Content Web Parts/Control_TaskTable.html, Conversion successful.</mso:HtmlDesignStatusAndPreview>
</mso:CustomDocumentProperties>
</xml><![endif]-->
</head>

<body>
    <script>
        $includeScript(this.url,"~sitecollection/style library/js/jquery-1.10.2.min.js");
        $includeScript(this.url,"~sitecollection/style library/js/jquery.dataTables.min.js");
        $includeCSS(this.url,"~sitecollection/style library/css/jquery.dataTables.css");
    </script>

    <div id="Control_TaskDataTable">

<!--#_
if (!$isNull(ctx.ClientControl) &&
    !$isNull(ctx.ClientControl.shouldRenderControl) &&
    !ctx.ClientControl.shouldRenderControl())
{
    return "";
}
ctx.ListDataJSONGroupsKey = "ResultTables";
ctx["CurrentItems"] = ctx.ListData.ResultTables[0].ResultRows;
var $noResults = Srch.ContentBySearch.getControlTemplateEncodedNoResultsMessage(ctx.ClientControl);

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

var noResultsClassName = "ms-srch-result-noResults";
ctx.OnPostRender = [];

ctx.OnPostRender.push(function () {
	$("#" + encodedID).dataTable(
	{
		"bPaginate": true,
        "bLengthChange": false,
        "bFilter": false,
        "bSort": false,
        "bInfo": false,
        "bAutoWidth": false,
		"bJQueryUI": true
     }
     );
});
_#-->

<table class="artis-tasktable" id="_#= encodedID =#_">
	<thead>
		<tr>
			<th>Task</th>
			<th>Source Site</th>
		</tr>
	</thead>
	<tbody>
		_#= ctx.RenderItems(ctx) =#_
	</tbody>
</table>	

    </div>
</body>
</html>

Item Template

Now attention is turned on the item template for the individual results. The item template will need to include managed properties to include in the final rendered result. Managed properties are defined either by a search administrator or by a site collection administrator.

Since we are keeping this simple, the item template will only include the Title of the task, a link to the task (available from the built in Title managed property), the title of the site (from the Site managed property) and a link to the site (from the SPSiteUrl managed property). The final template should look something like:

<tr>
	<td>
		<a href="TaskURL">Task Title</a>
	</td>
	<td>
		<a href="SiteURL">Site</a>
	</td>
</tr>

Again, I simply borrowed from an existing template to get started (Item_Default.html) to build the control. This gives me many of the managed properties I will need.

You will notice in the final markup that managed properties are mapped in a format similar to ‘Property'{Property}:’Property’. On MSDN, it is stated, “The property is a comma-delimited list of values that uses the following format: ‘property display name'{property name}:’managed property’.” The property display name is what will be displayed in the editing pane while the web part is being customized. The property name maps to a localized resource that is used for property binding. The managed property is a list of managed properties to use for the value.

<html xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882">
<head>
<title>Data Table Task Row</title>

<!--[if gte mso 9]><xml>
<mso:CustomDocumentProperties>
<mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden>
<mso:ManagedPropertyMapping msdt:dt="string">'Link URL'{Link URL}:'Path','Task Title'{Task Title}:'Title','SecondaryFileExtension','ContentTypeId','ListItemID','SiteTitle','SitePath','Site','SPSiteURL'</mso:ManagedPropertyMapping>
<mso:MasterPageDescription msdt:dt="string">This Item Display Template will show a single task on a single row. This must be used with the Tasks Data Table control.</mso:MasterPageDescription>
<mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106603</mso:ContentTypeId>
<mso:TargetControlType msdt:dt="string">;#Content Web Parts;#</mso:TargetControlType>
<mso:HtmlDesignAssociated msdt:dt="string">1</mso:HtmlDesignAssociated>
<mso:CrawlerXSLFile msdt:dt="string"></mso:CrawlerXSLFile>
<mso:HtmlDesignPreviewUrl msdt:dt="string"></mso:HtmlDesignPreviewUrl>
<mso:HtmlDesignStatusAndPreview msdt:dt="string">http://connect.qrinc.com/poc/aip/_catalogs/masterpage/Display Templates/Content Web Parts/Item_TaskTableRow.html, Conversion successful.</mso:HtmlDesignStatusAndPreview>
<mso:HtmlDesignConversionSucceeded msdt:dt="string">True</mso:HtmlDesignConversionSucceeded>
</mso:CustomDocumentProperties>
</xml><![endif]-->
</head>

<body>
    <script>
    </script>

    <div id="Item_DataTaskRow">
<!--#_
var encodedId = $htmlEncode(ctx.ClientControl.get_nextUniqueId() + "_row_");

var linkURL = $getItemValue(ctx, "Link URL");
linkURL.overrideValueRenderer($urlHtmlEncode);

var taskTitle = $getItemValue(ctx, "Task Title");
var siteTitle = $getItemValue(ctx, "SiteTitle");

var linkId = encodedId + "link";
var titleId = encodedId + "title";
var siteId = encodedId + "site";

_#-->
		<tr id="_#= encodedId =#_" data-listitemid="_#= ctx.CurrentItem.ListItemID =#_">
			<td><a href="_#= linkURL =#_" id="_#= linkId =#_" data-role="modalOpen">_#= taskTitle =#_</a></td>
			<td><a href="_#= ctx.CurrentItem.SPSiteURL =#_">_#= siteTitle =#_</a></td>
		</tr>
	</div>
</body>
</html>

Line 3: Changes the display name.

Line 8: Set the managed properties to include.

Line 25: Changes the ID of the control.

Line 40-43: The individual search results will be rendered with the template.

This will provide the final markup required. On the original search page, the content by search web part is changed to utilize the new control and item templates.

DisplayTemplates-DataTables-Search

And the control is finally rendered and enhanced with DataTables:

DisplayTemplates-DataTables-Rendered

More information:

Be an over-achiever

Edit 8/20/2013 @ 3:48 PM: Fixed the code regions of the post.
Update 6/12/2014: It appears a reader, Peter, has found a solution to fix the issue where the script hasn’t fully loaded prior to the initialize function getting called. Check out his comment here.

27 thoughts on “Using jQuery DataTables with Search Display Templates

  1. Well, here we are in 2016 and I find it still remarkably difficult to get DataTables to render reliably in SP2013 Display Templates.

    I have tried numerous combinations of SOD functions (RegisterSod, RegisterSodDep, executeFunc; executeOrDelayUntilScriptLoaded; EnsureScriptFunc), $includeScript/$includeCSS, and $.getScript(), but I have yet to find a way to craft my design template Control file such that I >alwaysreliably< renders a DataTable?

  2. Hi Chris,

    I think to have solved the problem reported above about the dataTable method failing to execute:

    function initTable()
    {
    $(“#” + encodedID).dataTable(
    {
    “bPaginate”: true,
    “bLengthChange”: false,
    “bFilter”: false,
    “bSort”: false,
    “bInfo”: false,
    “bAutoWidth”: false,
    “bJQueryUI”: true
    }
    );
    }

    AddPostRenderCallback(ctx, function()
    {
    SP.SOD.executeOrDelayUntilScriptLoaded(initTable, “~sitecollection/style library/js/jquery.dataTables.min.js”);
    });

    At least for me this has fixed the problem!

    The SP.SOD part of this code will make SP wait until dataTables.min.js is loaded… this seems to be the real cause of the isue, not the jquery script (i noticed it was loaded even if did get the error), but the dataTables.js file that didn’t get loaded before executing the code.

    Best Regards,

    Peter

    1. Seems i may have been to quick… the above code does not work, it actually does not execute the script and what you get is just a table. I did figure out though that the culprid here is the fact that there are 2 large scripts that need to be loaded. If you add the JQuery script to a script editor on your page, then you can leave the other script in the template. This will get rid of the error.

      Anybody has any other ideas?

      1. Sorry to keep coming back at this, but i have found another solution that seems to work really well and is cleaner:

        At the top of the control remove the IncludeScript for the jquery.dataTables.min.js script, then adapt the code i gave earlier as follows:

        AddPostRenderCallback(ctx, function()
        {

        $.getScript(“/cs/archives/style library/js/jquery.dataTables.min.js”, function(){
        initTable();
        });

        });

        The jquery function getScript actually gets the scripts and executes it, then calls initTable. It is in no way possible now that the script is not loaded before initTable runs.

        I’m not really familiar with getScript so if anyone knows a downside to this approach, please let me know.

      2. Peter,

        Glad you’ve been able to find a solution to this. It would be nice if the new display templates had an injection engine similar to RequireJS where you could just list your dependencies and it would wait until they’re all available prior to executing the code. Until then, I think this looks like a good way to overcome that issue. Thanks for contributing!

        Chris

  3. Hi Josh,

    I love the post. It is exactly what I was trying to do.

    I have everything working, but could you help me understand what I need to do to override the max item limit. The CSWP is returning about 2000 items in the query writer. However number of items maxes out at 50 and even though paging on dataTables is working, it will only return 50 items.

    Have you seen this? Am I missing something?

    Thanks a bunch for your help,
    Derek

    1. Hi Derek,

      You are not missing something. You can’t display more than 50.

      You’ll need to go to the display templates\search folder and make a copy of the dialogresult control template. This one is a lot more complicated on first view, but you can paste in pretty much the same code as above.

      You than add a “search results” web part and that one allows for as many items as you want.

      Best Regards,

      Peter

    2. Hi Derek/Chris,

      I have continued my tests on this control and i must say that there is another problem lurking with this control.

      The controls on the grid (search, sorting, etc…) actually do not integrate at all with the way search works in SharePoint regardless of the fact that you use content search or search results webpart.

      When you search in the grid, you will only search in the page that is shown by SharePoint… to make this control work you would actually need to turn of paging entirely on the search web parts.

      I have not found a way to do that… if there is no way to do that then I’m affraid this control will never work.

      Too bad… i have learned a lot from this experience but it seems like i will not be able to use it.

      Best Regards,

      Peter

  4. Hi, first of all i learned a lot from your post but i am having difficulties with the Jquery library. I have the problem that the library is loaded too late which causes my $ to be undefined. Do you have any idea how this is possible?
    Thnx

  5. Good Post Chris. I was wondering do you have a solution for a table based layout, having an inserted column to support a persons picture. Ive been working on a corporate directory and every column works except that. It is coming back as a blank column.

  6. Chris, this is a great example, thank you for putting this together. Is it possible to avoid hard coding the column names in the control template? Can they be dynamically generated based on the display name of the fields returned in the search query?

    Thanks,
    Joshua

  7. I cannot get this working, all I require is a CSWP view in a table format for 3 – 4 columns and pagination. Is this solution suppose to do the trick.

  8. Hi Chris,

    This is very cool, it works fine on all browsers expect IE8. On IE8 I see error ”
    Object doesn’t support this property or method (OnPostRender: )”. Do you have any work around for this issue.

  9. Hi Josh,

    Great post, and I really love how nice it is to combine datatables and SP display templates.

    I do have one small issue though – about one third of the time, the following error appears:

    Object doesn’t support property or method ‘dataTable’ (OnPostRender: ) (screenshot: http://i.imgur.com/BKQvsF8.png)

    I can just refresh the page and the error (might) dissapear, so this is not consistent.

    Have you seen something similar on your end?
    What do you think might be the cause?

    1. Yes, I’ve seen the same issue on my end. It appears the script is not getting loaded prior to execution of the on post render method. I haven’t found a workaround yet.

      1. Thanks for your reply.

        If you ever do find a workaround, would you mind updating this post, or even dropping me an email ( kmosti CurlyA gmaildotcom ) ?

        Regards,
        Kris

    2. I got a Workaround for that, simply add a little delay to the funktion call using setTimeout

      ctx.OnPostRender.push(setTimeout(function () {
      $(“#” + encodedID).dataTable(
      { “bLengthChange”: false,
      “bPaginate”: false,
      “bFilter”: false,
      “bInfo”: false,
      “aaSorting”: [[ 0, “asc” ]],
      “aaSorting”: [[ 1, “asc” ]],
      “asStripeClasses”: [“ms-itmhover”],
      sSortable:”ms-headerSortTitleLink”
      }
      );
      },30));

  10. This is awesome Chris. One issue I’m having is getting the SiteTitle to show on my search results display template for a page item. I’m using a variation of the Default Item. Haven’t even done any of the jquery pieces yet. Just trying to get the SiteTitle in. I have other mapped properties displaying, but I can’t get the SiteTitle. I’ve mapped the property “SiteTitle”:”SiteTitle” and even “ows_siteName”:”ows_siteName” as well. Both of these are also mapped in my Search Schema. My javascript code is similar to yours:
    var SiteName2 = $getItemValue(ctx,”SiteTitle”);

    I even tried:
    var SiteName3 = $htmlEncode(Srch.U.truncateEnd(ctx.CurrentItem.SiteTitle, Srch.U.titleTruncationLength));

    and
    var SiteName4 = $getItemValue(ctx,”ows_SiteName”);

    Of course I have the html references to the variables as well. Any ideas?

    THANKS!
    Josh

    1. I just figured it out. SiteTitle didn’t work. I needed to map it to a Refinable String. Not sure that makes sense to me, but so far it’s working!

Leave a reply to Peter Cancel reply