Simple Pagination with the Content Search Web Part

The default paging function within the SharePoint 2013 Content Search web part is lacking a few features. One of them is the ability to know how many items you’re looking at and which set of items you are currently looking at. You basically had little to no indication on where you were in the document results, especially if you had a lot of results to display at once.

The original SharePoint 2013 Search Query results paging
Original SharePoint 2013 Content Search results paging

The simple paging control that I created displays some basic information about the results you’re seeing. I display the total pages, total results, and which page you’re currently on. This is ideal for my solution because I wanted to use a compact Web Part display. Leveraging the power of Display Templates (new with SharePoint 2013) I used some OOTB functionality mixed in with my own code. Let me show you how I did that.

This my revised Paging for the Search Query results
This is my revised Paging for the Content Search results

Brief Intro on Display Templates

Display templates in SharePoint Server 2013 are templates used in Web Parts that use search technology to show the results of a query made to the search index. Display templates control which managed properties are shown in the search results, and how they appear in the Web Part. I created a new custom Control Template for my Display Template. We already have a custom Item Template that we use to display our content in a grid form, so I did not need to modify anything there.

There are two primary types of display templates:

  • Control templates determine the overall structure of how the results are presented. Includes lists, lists with paging, and slide shows.
  • Item templates determine how each result in the set is displayed. Includes images, text, video, and other items.

After you add a Search Web Part (such as the Content Search Web Part) to a page, to configure the Web Part, you select both a control display template and an item display template, as shown below.

Control Template and Item Template selections.
Control Template and Item Template selections.

Continue reading the MSDN article here (Display Templates – https://msdn.microsoft.com/en-us/library/office/jj945138.aspx) on Display Templates on how to create, modify, or add Display Templates to your solution.

Breakdown

This is all wrapped within a Bootstrap styled DIV container that flexes with my display resolution. I’m leaving out my own custom style sheet in case you want to style the CSS the way you want. I can provide my own CSS style that I used to create the exact look if you’d like. Just message me and I’ll email you the source.

I treated the paging functionality in 4 separate stages to display as expected

  1. Current/Previous Page
  2. Last Page
  3. Next Page
  4. Total Pages

The first thing that I did was place all of the known information that I was going to be using into variables.

 var pagingInfo = ctx.ClientControl.get_pagingInfo();
 var rowsPerPage = ctx.ClientControl.get_numberOfItems();
 var totalRows = ctx.DataProvider.get_totalRows();
 var currentPage = ctx.ClientControl.get_currentPageNumber();
 var totalPages = Math.ceil(totalRows / rowsPerPage);
 var selfLinkId = "SelfLink_";
 var imagesUrl = GetThemedImageUrl('searchresultui.png');
 var pl = '';

I then added logic to check when the “Previous” arrow needed to be shown and what page number to display at the current page.

for (var i = 0; i < pagingInfo.length; i++) {
 var pl = pagingInfo[i];
 if(!$isNull(pl)) {
 var imagesUrl = GetThemedImageUrl('searchresultui.png');
 if(pl.startItem == -1 && currentPage != totalPages) { 
 var selfLinkId = "SelfLink_" + pl.pageNumber;
 _#-->
 <div id="PagingSelf">Page _#= $htmlEncode(pl.pageNumber) =#_</div>
 <!--#_ 
 } else if(pl.pageNumber == -1) { 
 var iconClass = Srch.U.isRTL() ? "ms-srch-pagingNext" : "ms-srch-pagingPrev"; 
 _#-->
 <div id="PagingImageLink ">
 <a id="PageLinkPrev" href="#" class="ms-commandLink ms-promlink-button ms-promlink-button-enabled ms-verticalAlignMiddle" title="_#= $htmlEncode(pl.title) =#_" onclick="$getClientControl(this).page(_#= $htmlEncode(pl.startItem) =#_);return Srch.U.cancelEvent(event);">
 <span class="ms-promlink-button-image">
 <img src="_#= $urlHtmlEncode(imagesUrl) =#_" class="_#= $htmlEncode(iconClass) =#_" alt="_#= $htmlEncode(pl.title) =#_" />
 </span>
 </a> 
 </div>
 <!--#_ 
 } else if(pl.pageNumber == -2) { 
 var iconClass = Srch.U.isRTL() ? "ms-srch-pagingPrev" : "ms-srch-pagingNext";
 //Do not display anything.
 } 
 }
 }

 

I then added the part to check for the Last Page and display correctly once reached.

//Display the last page
 if((currentPage != totalPages) && totalPages > 2) {
 _#--> 
 <div id="PagingLink" class="">of _#= $htmlEncode(totalPages) =#_</div> 
 <!--#_
 }else if(currentPage == totalPages){
 _#--> 
 <div id="PagingLink" class="">Page _#= $htmlEncode(totalPages) =#_ of _#= $htmlEncode(totalPages) =#_</div> 
 <!--#_
 }

 

The “Next Page” logic should always show until the Last Page is reached.

for (var i = 0; i < pagingInfo.length; i++) {
 var pl = pagingInfo[i];
 if(!$isNull(pl)) {
 var imagesUrl = GetThemedImageUrl('searchresultui.png');
 if(pl.pageNumber == -2) { 
 var iconClass = Srch.U.isRTL() ? "ms-srch-pagingPrev" : "ms-srch-pagingNext";
 _#-->
 <div id="PagingImageLink">
 <a id="PageLinkNext" href="#" class="ms-commandLink ms-promlink-button ms-promlink-button-enabled ms-verticalAlignMiddle" title="_#= $htmlEncode(pl.title) =#_" onclick="$getClientControl(this).page(_#= $htmlEncode(pl.startItem) =#_);return Srch.U.cancelEvent(event);">
 <span class="ms-promlink-button-image">
 <img src="_#= $urlHtmlEncode(imagesUrl) =#_" class="_#= $htmlEncode(iconClass) =#_" alt="_#= $htmlEncode(pl.title) =#_" />
 </span>
 </a>
 </div>
 <!--#_ 
 } 
 }
 }

 

Finally, to display the “Total Count” section, I simply re-used the existing SharePoint Search Result controls. Search results uses the “Default Result” template in Control_SearchResults.html.  The Search Results template can be found in your Site SettingsMaster Page Gallery > Display Template > Search folder in your Site Collection.

 <!-- Total item count. -->
<!--#_ 
 if(ctx.ClientControl.get_showResultCount() && ctx.DataProvider.get_totalRows() > 0){ 
 var start = ctx.DataProvider.get_currentQueryState().s;
 var resultsPerPage = ctx.DataProvider.get_resultsPerPage();
 var totalRows = ctx.DataProvider.get_totalRows();
 var countDisplayString = Srch.Res.rs_ApproximateResultCount;
 var exactCount = Srch.Res.rs_ResultCount;
 if (start + resultsPerPage > totalRows) { countDisplayString = (totalRows == 1)? Srch.Res.rs_SingleResultCount : Srch.Res.rs_ResultCount; }
_#-->
 <div id="ResultCount" class="ms-srch-resultscount">
 _#= String.format($htmlEncode(exactCount), $htmlEncode(totalRows.localeFormat("N0"))) =#_ 
 </div>
<!--#_ 
 }

Summary

With this paging functionality, you will be able to quickly know where you are on within the Content Search results. There are much more complicated Paging functionality that are out there, such as the ones with ellipses and multiple page numbers.

This Paging design can give your customers a simple solution to knowing what they are looking at. I have also created a more complex pagination solution in our current SharePoint 2013 environment, but its a hard pill to swallow if you need to conserve real estate. My solution had 11 elements to display a truncated pagination on our Content Search displays.

Advanced Pagniation
More advanced pagination can have up to 13 elements. That’s a lot of real estate for a web part. (img source: http://static.bbci.co.uk/)

 

Full Code

Here is the full code that I used to achieve the simple paging for my Control Template.

<div id="Control_ListWithPaging">

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

 var isRollupPageInDisplayMode = Srch.ContentBySearch.isRollupPage(ctx.ClientControl) && !Srch.U.isPageInEditMode();
 var noResultsClassName = isRollupPageInDisplayMode ? "ms-attractMode ms-uppercase ms-alignCenter" : "ms-srch-result-noResults";

 var ListRenderRenderWrapper = function(itemRenderResult, inCtx, tpl)
 {
 var iStr = [];
 iStr.push('<li>');
 iStr.push(itemRenderResult);
 iStr.push('</li>');
 return iStr.join('');
 }
 ctx['ItemRenderWrapper'] = ListRenderRenderWrapper;
_#-->
 <ul class="cbs-List MWS_Control_ListWithPaging">
 _#= ctx.RenderGroups(ctx) =#_
 </ul>
<!--#_

 if (ctx.ClientControl.get_shouldShowNoResultMessage())
 {
_#-->
 <div class="_#= noResultsClassName =#_">_#= $noResults =#_</div>
<!--#_
 }
_#-->

 <!-- Paging Display -->
<!--#_
 if(ctx.ClientControl.get_showPaging()){
 var pagingInfo = ctx.ClientControl.get_pagingInfo();
 var rowsPerPage = ctx.ClientControl.get_numberOfItems();
 var totalRows = ctx.DataProvider.get_totalRows();
 var currentPage = ctx.ClientControl.get_currentPageNumber();
 var totalPages = Math.ceil(totalRows / rowsPerPage);
 var selfLinkId = "SelfLink_";
 var imagesUrl = GetThemedImageUrl('searchresultui.png');
 var pl = '';
 if(!$isEmptyArray(pagingInfo)){ 
_#-->
 <div class="MWS_Control_ListWithPaging_Paging_Wrapper col-xs-6 col-sm-12"> 
 <!--#_ 
 for (var i = 0; i < pagingInfo.length; i++) {
 var pl = pagingInfo[i];
 if(!$isNull(pl)) {
 var imagesUrl = GetThemedImageUrl('searchresultui.png');
 if(pl.startItem == -1 && currentPage != totalPages) { 
 var selfLinkId = "SelfLink_" + pl.pageNumber;
 _#-->
 <div id="PagingSelf">Page _#= $htmlEncode(pl.pageNumber) =#_</div>
 <!--#_ 
 } else if(pl.pageNumber == -1) { 
 var iconClass = Srch.U.isRTL() ? "ms-srch-pagingNext" : "ms-srch-pagingPrev"; 
 _#-->
 <div id="PagingImageLink ">
 <a id="PageLinkPrev" href="#" class="ms-commandLink ms-promlink-button ms-promlink-button-enabled ms-verticalAlignMiddle" title="_#= $htmlEncode(pl.title) =#_" onclick="$getClientControl(this).page(_#= $htmlEncode(pl.startItem) =#_);return Srch.U.cancelEvent(event);">
 <span class="ms-promlink-button-image">
 <img src="_#= $urlHtmlEncode(imagesUrl) =#_" class="_#= $htmlEncode(iconClass) =#_" alt="_#= $htmlEncode(pl.title) =#_" />
 </span>
 </a> 
 </div>
 <!--#_ 
 } else if(pl.pageNumber == -2) { 
 var iconClass = Srch.U.isRTL() ? "ms-srch-pagingPrev" : "ms-srch-pagingNext";
 //Do not display anything.
 } 
 }
 } 
 
 //Display the last page
 if((currentPage != totalPages) && totalPages > 2) {
 _#--> 
 <div id="PagingLink" class="">of _#= $htmlEncode(totalPages) =#_</div> 
 <!--#_
 }else if(currentPage == totalPages){
 _#--> 
 <div id="PagingLink" class="">Page _#= $htmlEncode(totalPages) =#_ of _#= $htmlEncode(totalPages) =#_</div> 
 <!--#_
 } 
 
 
 for (var i = 0; i < pagingInfo.length; i++) {
 var pl = pagingInfo[i];
 if(!$isNull(pl)) {
 var imagesUrl = GetThemedImageUrl('searchresultui.png');
 if(pl.pageNumber == -2) { 
 var iconClass = Srch.U.isRTL() ? "ms-srch-pagingPrev" : "ms-srch-pagingNext";
 _#-->
 <div id="PagingImageLink">
 <a id="PageLinkNext" href="#" class="ms-commandLink ms-promlink-button ms-promlink-button-enabled ms-verticalAlignMiddle" title="_#= $htmlEncode(pl.title) =#_" onclick="$getClientControl(this).page(_#= $htmlEncode(pl.startItem) =#_);return Srch.U.cancelEvent(event);">
 <span class="ms-promlink-button-image">
 <img src="_#= $urlHtmlEncode(imagesUrl) =#_" class="_#= $htmlEncode(iconClass) =#_" alt="_#= $htmlEncode(pl.title) =#_" />
 </span>
 </a>
 </div>
 <!--#_ 
 } 
 }
 } 
 _#-->
 <!-- Total item count. -->
<!--#_ 
 if(ctx.ClientControl.get_showResultCount() && ctx.DataProvider.get_totalRows() > 0){ 
 var start = ctx.DataProvider.get_currentQueryState().s;
 var resultsPerPage = ctx.DataProvider.get_resultsPerPage();
 var totalRows = ctx.DataProvider.get_totalRows();
 var countDisplayString = Srch.Res.rs_ApproximateResultCount;
 var exactCount = Srch.Res.rs_ResultCount;
 if (start + resultsPerPage > totalRows) { countDisplayString = (totalRows == 1)? Srch.Res.rs_SingleResultCount : Srch.Res.rs_ResultCount; }
_#-->
 <div id="ResultCount" class="ms-srch-resultscount">
 _#= String.format($htmlEncode(exactCount), $htmlEncode(totalRows.localeFormat("N0"))) =#_ 
 </div>
<!--#_ 
 } 
_#-->
 </div> <!-- //Close MWS_Control_ListWithPaging_Paging_Wrapper -->
<!--#_ 
 } 
 } 
_#-->

 </div>

Resources

One thought on “Simple Pagination with the Content Search Web Part

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.