Part 3: Working with File Types in the Search Refiner Control Template

October 29, 2013

In the previous posts I explained how to create a new refiner control, but there is one search data type that needs some special attention. The search data type that will be explained in this post is the FileType. In this post I’ll show you the things that are so special about working with filetypes.

Note: for this post I’ll use the display template that was created in the previous post: Custom Search Refiner Control Part 2.

Point 1: Comparison Custom Template versus Default Template

You’ll get the following output with the custom display template:

Custom Template used with File Type
Custom Template used with File Type

The output of the default template is the following:

Default Template used with File Type
Default Template used with File Type

The first thing you’ll notice is that the names of the file types are different. The custom display template uses the exact file type values, where the default template is using user friendly names.

This comes from the fact that the default template has a function that does this remapping:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
function mapResultType(listData)
{
  var map = { };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_MSAccess")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["accdb", "accdc", "accde", "accdr", "accdt"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_AdobePDF")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["pdf"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Assignment")] = {
    "RefinerName": "ContentTypeId",
    "RefinementValues": ["0x010063C2F478ACC511DFB869B5BFDFD720851252*"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Blog")] = {
    "RefinerName": "WebTemplate",
    "RefinementValues": ["BLOG"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Book")] = {
    "RefinerName": "ContentTypeId",
    "RefinementValues": ["0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900ABD371128A994A0B98E7E888866B392F*"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Community")] = {
    "RefinerName": "WebTemplate",
    "RefinementValues": ["COMMUNITY"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Course")] = {
    "RefinerName": "ContentTypeId",
    "RefinementValues": ["0x010063C2F478ACC511DFB869B5BFDFD720851101*"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Discussion")] = {
    "RefinerName": "ContentTypeId",
    "RefinementValues": ["0x012002*", "0x0107*"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Email")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["eml", "msg", "exch"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_MSExcel")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["odc", "ods", "xls", "xlsb", "xlsm", "xlsx", "xltm", "xltx", "xlam"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Image")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["bmp", "jpeg", "png", "tiff", "gif", "rle", "wmf", "dib", "ico", "wpd", "odg"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Lesson")] = {
    "RefinerName": "ContentTypeId",
    "RefinementValues": ["0x010063C2F478ACC511DFB869B5BFDFD720851251*"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_NewsfeedPost")] = {
    "RefinerName": "ContentTypeId",
    "RefinementValues": ["0x01FD4FB0210AB50249908EAA47E6BD3CFE8B*", "0x01FD59a0df25f1e14ab882d2c87d4874cf84*"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_MSOneNote")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["one"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_MSPowerPoint")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["odp", "ppt", "pptm", "pptx", "potm", "potx", "ppam", "ppsm", "ppsx"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_MSProject")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["mpp"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_MSPublisher")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["pub"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_SharePointSite")] = {
    "RefinerName": "contentclass",
    "RefinementValues": ["STS_Web", "STS_Site"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Task")] = {
    "RefinerName": "contentclass",
    "RefinementValues": ["STS_ListItem_GanttTasks", "STS_ListItem_Tasks", "STS_ListItem_HierarchyTasks", "STS_List_GanttTasks", "STS_List_Tasks", "STS_List_HierarchyTasks"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_TeamSite")] = {
    "RefinerName": "WebTemplate",
    "RefinementValues": ["STS"]
  }; 
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Video")] = {
    "RefinerName": "ContentTypeId",
    "RefinementValues": ["0x0120D520A8*"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Visio")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["vsd", "vsx"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_MSWord")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["docx", "doc", "docm", "dot", "nws", "dotx"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Webpage")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["HTML", "MHTML"]
  };
  map[Srch.U.loadResource("rf_ResultTypeRefinerValue_Archive")] = {
    "RefinerName": "FileType",
    "RefinementValues": ["zip"]
  };

  var retListData = new Array();
  var assocListData = new Array();
  for (var i = 0; i < listData.length; i++)
  {
    var filter = listData[i];
    var mappedRefinementName = null;
    for(var key in map)
    {
      if (map[key].RefinerName == filter.RefinerName)
      {
        for (var j = 0; j < map[key].RefinementValues.length; j++)
        {
          var actualValue = filter.RefinementValue.toLowerCase(), candidateValue = map[key].RefinementValues[j].toLowerCase();

          if (actualValue == candidateValue ''
            (filter.RefinerName.toLowerCase() == "contenttypeid" && actualValue.startsWith(candidateValue.substring(0, candidateValue.length - 1)))) 
          {
            mappedRefinementName = key;
            break;
          }
        }
        if (!$isNull(mappedRefinementName))
        {
          break;
        }
      }
    }

    var mappedFilter = new Object();
    if (!$isNull(mappedRefinementName))
    {
      mappedFilter.RefinerName = map[mappedRefinementName].RefinerName;
      mappedFilter.RefinementCount = filter.RefinementCount;
      mappedFilter.RefinementName = mappedRefinementName;
      mappedFilter.RefinementTokens = [];
      var resultTypeTokenWrapper = (mappedFilter.RefinerName.toLowerCase() == "contenttypeid") ? function (x) {return x;} : 
                                                     Srch.RefinementUtil.stringValueToEqualsToken;
      for (var j in map[mappedRefinementName].RefinementValues) {
        mappedFilter.RefinementTokens.push(resultTypeTokenWrapper(map[mappedRefinementName].RefinementValues[j]));
      }

      if ($isNull(assocListData[mappedFilter.RefinementName]))
      {
        assocListData[mappedFilter.RefinementName] = mappedFilter;
      }
      else
      {
        assocListData[mappedFilter.RefinementName].RefinementCount += mappedFilter.RefinementCount;
      }
    }        
  }

  for (var key in assocListData)
  {
    retListData[retListData.length] = assocListData[key];
  }

  return retListData;
}

This function can be used in your template, this can placed at the bottom of your template.

Point 2: Combined Refinement Information

Another thing you’ll can see, is that different refinement titles are shown. For example Newsfeed post, Team site isn’t shown in the custom display template. These refiners are shown because there is a mapping in the default template which enriches the FileType data type with extra information from the following types: contentclass, ContentTypeId, and WebTemplate.

The following code is used to combine these data types together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
if (ctx.RefinementControl.propertyName === "FileType") {
  if (!$isNull(ctx.DataProvider.get_refinementInfo())) {
    if (!$isNull(ctx.DataProvider.get_refinementInfo()["contentclass"]))
    {
      listData = listData.concat(ctx.DataProvider.get_refinementInfo()["contentclass"]);
    }
    if (!$isNull(ctx.DataProvider.get_refinementInfo()["ContentTypeId"]))
    {
      listData = listData.concat(ctx.DataProvider.get_refinementInfo()["ContentTypeId"]);
    }
    if (!$isNull(ctx.DataProvider.get_refinementInfo()["WebTemplate"]))
    {
      listData = listData.concat(ctx.DataProvider.get_refinementInfo()["WebTemplate"]);
    }
  }

  if (hasControl)
    listData = mapResultType(listData);
}

When you adding this code to your display template, a modification is required to the loop that populates the selected and unselected arrays. The token mapping is already achieved with the remapping function, so it isn’t needed anymore for the file type. The code needs to be changed to this:

1
2
3
4
5
// Token mapping is already done for the FileType data
if (ctx.RefinementControl.propertyName !== "FileType") {
  filter.RefinementTokens = [filter.RefinementToken];
  filter.RefinementTokenWrappedValues = [Srch.RefinementUtil.stringValueToEqualsToken(filter.RefinementValue)];
}

The result of this looks like this:

File Type Custom Refiner
File Type Custom Refiner

Point 3: File Type Groups

The remapping function does more than just converting the file type to a friendly name. The file types are also mapped to with the possible values, this means that when you refining your results on the docx file type, the “doc”, “docm”, “dot”, “nws”, “dotx” are also taken into account.

When doing a refinement right now, you’ll end up with no results. To solve this problem, another method needs to be used for file type refinement. The method to use is the addRefinementFiltersJSONWithOr. The difference between addRefinementFiltersJSONWithOr and the addRefinementFiltersJSON method is the operator that they use. The addRefinementFiltersJSON uses an AND operator, where the addRefinementFiltersJSONWithOr uses an OR operation.

For the file types an OR operation is required (because a file has only one file type). The ShowRefiner function call in the unselected loop looks like this:

1
2
var addMethod = ctx.RefinementControl.propertyName === "FileType" ? "addRefinementFiltersJSONWithOr" : "addRefinementFiltersJSON";
ShowRefiner(filter.RefinementName, filter.RefinementCount, refiners, addMethod);

I have added a check to see if the control is used for file type refinement, so that the correct refinement can be achieved.

File type refinement
File type refinement

The decoded URL looks like this: http://your-site/Pages/DocumentResults.aspx#Default={"k":"","r":[{"n":"FileType","t":["equals(\"docx\")","equals(\"doc\")","equals(\"docm\")","equals(\"dot\")","equals(\"nws\")","equals(\"dotx\")"],"o":"or","k":false,"m":null}]}

Completely at the end of the URL you can see the OR operation.

Point 4: Removing the Current Refinement isn’t Working

At the moment, the Remove refinement link is partly working. For some refiners it works, for some it doesn’t. This is caused by the mapping with the contentclass, ContentTypeId, and WebTemplate data. At this moment the Remove refinement hyperlink removes the refinement for the current search data type (file type). When refining on one of the other data types, you’ll need to remove the refinement for that data type. What you need to do is creating a link that removes the refinement for all data types that it can have:

1
2
3
4
5
6
7
8
refinerRemoval[ctx.RefinementControl.propertyName] = null;
if (ctx.RefinementControl.propertyName == "FileType")
{
  refinerRemoval["contentclass"] = null;
  refinerRemoval["ContentTypeId"] = null;
  refinerRemoval["WebTemplate"] = null;
}
ShowRefiner('Remove refinement', null, refinerRemoval, 'updateRefinersJSON');

Point 5: Additional File Types

Maybe you noticed it or not, but in the first screenshot XML and TXT file type are shown. Once I implemented the remapping function, it wasn’t shown anymore. Inside the remapping function, there isn’t a mapping for the XML or TXT file types, but you can add your own file types to the list like this:

1
2
3
4
5
6
7
8
map["XML File"] = {
  "RefinerName": "FileType",
  "RefinementValues": ["xml"]
};
map["TXT File"] = {
  "RefinerName": "FileType",
  "RefinementValues": ["txt"]
};

The result looks like this:

Custom File Types
Custom File Types

Download

Download the complete refiner control here: Custom Refiner Control Part 3.

Part 4: Create a dropdown refiner control

Right now I explained the attentions points when working with file types are. Currently the refiner control is visualizing the items as a list, but this will change in the next part. In Part 4 I’ll show you how to create a dropdown refiner control.

Blog posts in this series:

Comments

comments powered by Disqus