SharePoint Group Display Templates [Grouping]

the questions...

How to Group Search Results Using Default Display Templates?

Grouping search results with display templates for the CSWP?

How to Define a Custom Group Display Template (GroupTemplateId)?

and the answer is a sad one...


Well, 1st create yourself a Group Display Template file, just take the default "group_xxx.html" from the "Search" or "Content Search" Folders, whatever you are using.

After adding it to the Master Pages, you can't add it via JS, you MUST download a copy or you Search WebPart (export) and change the value in "GroupTemplateId" to your group JS file.

Now at least you have a CSWP with you own group.html/js file.

So I've never really payed attention but in our Controls Display Templates the rendering part is in _#= ctx.RenderGroups(ctx) =#_ , GROUPS, not items.

This actually ALWAYS going through a group rendering function, and hey, i want to use it. but how?

nothing... NOTHING is the documentation (THX MS!).

but where would be the fun in that?

so unminifying the "clientrenderer.js" file i found the "RenderGroups" function, aka there as "function k(a)" over there, and with some efforts and debugging i got the point (in the end i'll add Appendix A for that function).


1st thing is to set is as the group template id, setting the "ListDataJSONGroupsKey" property, and setting an equivalent array property in the ctx object

ctx.ClientControl.set_groupTemplateId('~sitecollection/_catalogs/masterpage/display templates/content web parts/Group_Content_QA.js');
ctx.ListDataJSONGroupsKey = "ResultGrouped"
ctx.ListData.ResultGrouped = [];

The conclusion is that you need to give the renderer an array mimicking the ResultTables with the following properties:

- ResultRows: [items array],
- RowCount: [items array length]
- TableType: "RelevantResults"

sadly no tolling at all so you'll need to code that for yourself, including grouping the results, ect.

Control Display Template Code

//function to create the ResultTables fake array
function createResultGroup(items){
 return {
  ResultRows: items, 
  RowCount: items.length,
  TableType: "RelevantResults",

//just a ref
var _items = ctx.ListData.ResultTables[0].ResultRows;

//categories dictionary
var _groupDictionary = {};

//populating dictionary categories
_items.forEach(function(e) { if ( !_groupDictionary[e.QACategoryOWSCHCS] ){ _groupDictionary[e.QACategoryOWSCHCS] = []; } });

//populating dictionary categories with items
_items.forEach(function(e) { _groupDictionary[e.QACategoryOWSCHCS].push(e); });

//adding to ctx categories as a named array
ctx.groupsNames = Object.keys(_groupDictionary);

//populating fake ResultTables array (ResultGrouped)
ctx.groupsNames.forEach(function(g) { ctx.ListData.ResultGrouped.push( createResultGroup(_groupDictionary[g]) ); });

Group Display Template Code

This one is quite simple, just add html for the group, leave the line
ctx.ListDataJSONItemsKey = "ResultRows";

and look, the render items line
_#= ctx.RenderItems(ctx) =#_

Item Display Template Code

Really nothing here, you can use ctx.CurrentGroupIdx and anything you add to the ctx object on the way.

hope you had fun!

P.S. - unless you really want to expose the entire Search WebPart tooling (query ect.) to the end user, a simple JS (angular or pure or whatever) could be much easier, and if its all in a single list you might want to check JSLink, another product from MS, which can be a bit nicer, and have it's own issues.

Appendix A
(used for the highlight)

//function k(a) is ctx.RenderGroups(ctx)
function k(a) {
 //testing if ctx is valid with data
    if (a == null || a.ListData == null) return "";
    var b = null; //didnt figure out this one
    if (a.Templates != null) b = a.Templates.Group;
    var k = a.ListData, // ref for data
  f(a) returns the ListDataJSONGroupsKey which is usually ResultTables
  so j is actually the search results as we usually know it
  j is ref to ctx.ListData.ResultTables
        j = k[f(a)],  
        h = "";  // html results as string
    if (j == null) {
        if (typeof b == "string" || typeof b == "function") {
            a.CurrentGroupIdx = 0;
            a.CurrentGroup = k;
            a.CurrentItems = k[c(a)];
            h += CoreRender(b, a);
            a.CurrentItems = null;
            a.CurrentGroup = null
        return h
    for (var i = 0; i < j.length; i++) {
  //ctx.ListData.ResultTables[0] which is usually all we have
        var g = j[i],
   //not sure what's all that
            e = d(a, g, "Group");
        if (e == null || e == "") {
            if (b == null || b == {}) return "";
            if (typeof b == "string" || typeof b == "function") e = b;
            if (e == null || e == "") {
                var l = g.GroupType;
                e = b[l]
        if (e == null || e == "") continue;
        a.CurrentGroupIdx = i;  // define group index before rendering
        a.CurrentGroup = g;   // define group before rendering
        a.CurrentItems = g[c(a)]; // define group items before rendering
        h += CoreRender(e, a);  // render items
        a.CurrentGroup = null;  // reset group after rendering
        a.CurrentItems = null  // reset group items after rendering
    return h


Popular posts from this blog

c# Service Play Sound with NAudio example by Moshe

Asp.Net Ending Response options, Response.End() vs CompleteRequest()

JS/JQ simulate Enter event