Advanced Paging with SharePoint Content Search Web Part Display Templates

In this post, I cover the topic of exposing paging controls within the display templates used by SharePoint 2013’s Content Search Web Part (CSWP). In addition to looking at the underlying paging mechanisms used by the CSWP, I make available a couple of display templates that I created which include advanced paging support.

Click here to download associated ZIP file with display templatesPages in a BookI’ve had an opportunity to play with SharePoint 2013’s Content Search Web Part (CSWP) on a number of occasions in the last couple of years, and I have to say that I like it a lot. The CSWP can be employed to address a whole host of different use cases; in fact, in many situations I’ve found that it can be used to solve problems that were previously addressable only through custom code.

Search-driven content in SharePoint isn’t anything new, of course, but the display templates that are used to format search results in SharePoint 2013 are a large part of the CSWP’s “special sauce.” Through the use of a Control display template and an Item display template, it is possible to select, arrange, and style the search results that are returned and shown to users in a highly customizable fashion. With some knowledge of HTML and the help of SharePoint 2013’s Design Manager, it’s possible to produce some pretty impressive looking content. And if you know JavaScript, well … the sky is the limit on what you can produce.

Control And Item Display Templates

DisplayAndPropertyMappingsBefore I dive into paging and how I’ve tried to stretch what can be done with the CSWP, I want to briefly examine some display template basics. Although this post isn’t intended to be a primer on the CSWP and its associated display templates, there are a few items worth reviewing.

On the right is a snippet of a ToolPart containing some configuration data for a particular CSWP displaying some very basic data. The examples that follow (i.e., the two images shown below) use the configuration shown in the ToolPart, so refer to it if needed.

The first example below is how a CSWP showing three results might appear to an end user when the List with Paging Control template and Two lines Item template are applied. The second example (to the right of the first) differentiates which content regions on the CSWP are being driven by the List with Paging Control template (shown with green highlighting) and which are being driven by the Two lines Item template (shown in red).

Content Search Web Part Example as Seen by End User Content Search Web Part with Template Regions Highlighted

Through these images, I’m trying to convey a very simple point: the Control template dictates how the search results’ “container” appears, and the Item template determines how each individual search result within the container is displayed. The contents of any CSWP are rendered by the output of a single Control display template and zero or more Item display templates (again, one for each search result/item shown).

Nothing To See Here

As you might have guessed from the title of this post, one functional area that seems relatively unexplored and underdeveloped (in my experience) is that of paging and how paging controls are made available to end users of the CSWP. This is unfortunate, because whenever more results are returned than can be displayed at once – a very common scenario – some form of paging is needed.

Out of the box (OOTB), SharePoint 2013 comes with only a handful of Control and Item display templates that can be used with the CSWP to format your search results. Of these display templates, only one contains paging controls: the List with Paging Control template. If all you require is the basic forward/backward paging offered by the two buttons it contains, then the List with Paging display template may be adequate for your needs.

Personally, I think the List with Paging template is bland and kind of … ugly. It works, sure, but it doesn’t display several pieces of information that I find important; for example, the total number of search results and the result page that the user is currently on. Worse is the fact that it doesn’t provide any sort of mechanism to jump to a specific page. The best that an end-user can do is page forward or backward one page at a time.

Better Paging Support

CSWP With Advanced Paging SupportOne of the talks I’ve been giving recently at various SharePoint events and conferences is titled SharePoint’s New Swiss Army Knife: The Content Search Web Part. During that talk, I demonstrate a set of CSWP display templates that I put together to generate something decidedly “non-search” in appearance – like a directory of files to which the current user has access within the current site collection. An image of that CSWP example appears on the left.

This file directory CSWP instance was generated with three simple files: two custom display templates (one for the Control, and one for each Item shown) and a cascading style sheet. The actual result data isn’t particularly remarkable, but the manner in which the paging is implemented is what tends to catch people’s attention. With the Control template I created, end-users can:

  • See the total number of documents (i.e., search results) to which they have access
  • Clearly see which page of search results they’re on through the boxed page number and the “Page xx of yy” label
  • Identify which items/results they’re viewing through the “Items xxx to yyy of zzz” label
  • Jump directly to a page of results by clicking the specified page number

Finding all of this information and displaying it through the CSWP Control template took some research and tinkering. Some of the information was available directly within the search results that were passed to the CSWP, but some of it wasn’t. In the case where some desired information wasn’t available, it was computed with some basic math.

Overview Of The CSWP HTML

By default, the CSWP implements a client-side processing and paging model. Search results that are displayed are controlled by JavaScript functions contained within the Control and Item display templates that have been assigned to the CSWP. When a set of search results is being processed for display within the client browser, the Control template (which is the results container) gets called first to render the HTML that will frame or house the search items/results. For each search result or item that is passed to the CSWP, the Item display template then gets called to create a snippet of HTML for the result/item that can be inserted into the “frame” (commonly a

) created by Control display template.

An example of the rendered HTML for the search results shown in the previous “Better Paging Support” section appears below.

[code language=”html” autolinks=”false” collapse=”true” title=”HTML From CSWP Using New Display Templates (click to expand)”]

[/code]

The Control template (SwissArmy_Control_Template.js) creates the top level

and a child <ul> element (with a CSS class of “sas-table”) for the overall HTML structure, as well as the individual

elements used to contain each search/result item. For each search result/item, the Item template (SwissArmy_Item_Template.js) creates a 

block that is inserted as a child within an

element. Each of the individual item 

blocks created by the Item template has a CSS class of “sas-tablecell” for easier styling.

Below the block housing the search results is another

with a CSS class of “sas-paging-table.” As suggested by the class name, the

is used to house the clickable page numbers and additional paging information seen at the bottom of the CSWP control. And like the rest of the container information, this HTML is generated within the Control template.

Fetching Search Results

Each time a set of search results is needed, either on initial page rendering or when the user moves to a new page, the CSWP calls back to its SharePoint site collection for the data it needs. By default, this call occurs asynchronously; however, the CSWP can be configured to make such calls synchronously within the normal page processing sequence.

For example, if I had a page containing a CSWP at the following URL

http://sp2013-dev:18580/DisplayTemplateStyling

… the CSWP on that page would request search results for display by posting a request to the following endpoint:

http://sp2013-dev:18580/_vti_bin/client.svc/ProcessQuery

As part of its request, the CSWP passes an XML structure to the client.svc web service that looks something like the XML that appears below. This structure contains all of the information the ProcessQuery method needs to determine which search results should be sent back:

[code language=”xml” collapse=”true” autolinks=”false” title=”XML Request To Client.svc (click to expand)”]

(contentclass:STS_ListItem OR IsDocument:True)

LastModifiedTime
1

130

10

10

141

{8413cd39-2156-4e00-b54d-11efd9abdb89}

SourceName

false
0
1

Local SharePoint Results

SourceLevel

false
0
1

Ssa

Path

Title

FileExtension

SecondaryFileExtension

Title

Path

Author

SectionNames

SiteDescription

false

TryCache

true
0
3

Scope

false
0
1

{Site.URL}

UpdateLinksForCatalogItems

true
0
3

EnableStacking

true
0
3

ListId

false
0
1

fb9e18d3-1d80-4ab1-8e76-bc36b0a8e22d

ListItemId

false
6
2

TermId

false
0
1

0bfc89ed-1741-4925-9d25-828eaf74b2c8

TermSetId

false
0
1

0f14334e-b929-42bf-ad10-ed6a93d16a4f

TermStoreId

false
0
1

c4c58328-0e88-4937-91e9-cf7526b9b0db

http://sp2013-dev:18580/DisplayTemplateStyling#k=#s=131

true

ContentSearchRegular

false

false

QuerySession

false
0
1

a4e0e9b5-6dd9-4aea-b38b-26f325c67fdf

false

querygroup://webroot/Pages/DisplayTemplateStyling.aspx?groupname=Default

false

26103082-0e16-4ab0-a12c-bded636a7fb8Default

true

[/code]

Once the server finds the desired search results and processes them, it packages them up as a JSON object and returns that object to the browser for further action. The following is an example of a JSON structure (containing ten results) that was returned for the Swiss Army Knife CSWP as it was shown earlier in the “Better Paging Support” section:

[code language=”text” collapse=”true” title=”JSON Return Object From Client.svc Request (click to expand)”]
[
{
“SchemaVersion”:”15.0.0.0″,
“LibraryVersion”:”15.0.4569.1501″,
“ErrorInfo”:null,
“TraceCorrelationId”:”36d5029d-dcee-709e-a995-81ad013126e0″
},
119,
{
“IsNull”:false
},
122,
{
“IsNull”:false
},
130,
{
“IsNull”:false
},
134,
{
“IsNull”:false
},
140,
{
“IsNull”:false
},
166,
{
“IsNull”:false
},
171,
{
“418e02d2-9bc5-46fd-ada2-83762ed79256Default”:{
“_ObjectType_”:”Microsoft.SharePoint.Client.Search.Query.ResultTableCollection”,
“ElapsedTime”:31,
“Properties”:{
“RowLimit”:10,
“SourceId”:”\/Guid(8413cd39-2156-4e00-b54d-11efd9abdb89)\/”,
“EnableStacking”:true,
“SerializedQuery”:””
},
“QueryErrors”:null,
“QueryId”:”e12d2e7f-598c-4db7-ae6e-fbf021d0b230″,
“SpellingSuggestion”:””,
“TriggeredRules”:[

],
“ResultTables”:[
{
“_ObjectType_”:”Microsoft.SharePoint.Client.Search.Query.ResultTable”,
“GroupTemplateId”:null,
“ItemTemplateId”:null,
“Properties”:{
“GenerationId”:9223372036854775806,
“ExecutionTimeMs”:16,
“QueryModification”:”(contentclass:STS_ListItem OR IsDocument:True) -ContentClass=urn:content-class:SPSPeople”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fGroup_Default.js”,
“StartRecord”:0
},
“QueryId”:”e12d2e7f-598c-4db7-ae6e-fbf021d0b230″,
“QueryRuleId”:”00000000-0000-0000-0000-000000000000″,
“ResultRows”:[
{
“Rank”:0,
“DocId”:114,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=4″,
“Title”:”3. Optimus Prime Part – Button”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=4″,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:115,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=5″,
“Title”:”4. Weather Right Now (Not Really) – Button”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=5″,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:116,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=6″,
“Title”:”5. Weather Right Now Page – Button”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=6″,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:117,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=8″,
“Title”:”6. Weather Right Now Provisioned – Button”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=8″,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:119,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=9″,
“Title”:”8. Donuts And Drinks Pub – Button”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSiteAssets\u002fForms\u002fDispForm.aspx?ID=9″,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:688,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fWeatherRightNowScraper.aspx”,
“Title”:”WeatherRightNowScraper”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fWeatherRightNowScraper.aspx”,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:683,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fPages\u002fDonutsAndDrinksPub.aspx”,
“Title”:”DonutsAndDrinksPub”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fPages\u002fDonutsAndDrinksPub.aspx”,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:685,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fDonutsAndDrinks.aspx”,
“Title”:”DonutsAndDrinks”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fDonutsAndDrinks.aspx”,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:686,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fMakingPiTakesEffort.aspx”,
“Title”:”MakingPiTakesEffort”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fMakingPiTakesEffort.aspx”,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
},
{
“Rank”:0,
“DocId”:139,
“Path”:”http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fOptimusPrimePart.aspx”,
“Title”:”OptimusPrimePart”,
“OriginalPath”:”http:\u002f\u002fsp2013-dev:18480\u002fSitePages\u002fOptimusPrimePart.aspx”,
“PartitionId”:”\/Guid(0c37852b-34d0-418e-91c6-2ac25af4be5b)\/”,
“UrlZone”:0,
“AAMEnabledManagedProperties”:”AttachmentURI;deeplinks;DefaultEncodingURL;ExternalMediaURL;HierarchyUrl;OrgParentUrls;OrgUrls;OriginalPath;ParentLink;Path;PictureThumbnailURL;PictureURL;PublishingImage;recommendedfor;ServerRedirectedEmbedURL;ServerRedirectedPreviewURL;ServerRedirectedURL;SiteLogo;SitePath;SPSiteURL;UserEncodingURL”,
“RenderTemplateId”:”~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Default.js”,
“QueryRuleId”:”\/Guid(00000000-0000-0000-0000-000000000000)\/”
}
],
“ResultTitle”:null,
“ResultTitleUrl”:null,
“RowCount”:10,
“TableType”:”RelevantResults”,
“TotalRows”:179,
“TotalRowsIncludingDuplicates”:179
}
]
}
},
167,
{
“HasException”:false,
“ErrorInfo”:null
}
]
[/code]

This JSON structure contains a lot of information – certainly more information than is being displayed by the CSWP. The trick, of course, is in figuring out exactly “what” is “where” for purposes of building a paging system.

Page-Related Processing Within The Control Template

Thankfully, the CSWP provides us with a relatively easy mechanism for getting at the search result data we care about within the JSON object that is returned from the call to client.svc. When a Control display template is invoked by the CSWP, it is passed a context object as follows:

[code language=”javascript” light=”true”]
function DisplayTemplate_2aa45a743fd94e75a3e53c940628e2ec(ctx) { … }
[/code]

ctx Content ObjectThe ctx context object contains a number of useful methods, properties, and subordinate objects we can leverage in our attempts to manipulate search results and calculate paging information. We can use ctx to interact directly with the CSWP (which is accessible through the ClientControl property), as well as obtain the search results themselves (and information about them) through the ListData property.

Note: Even though Microsoft’s List with Paging Control template doesn’t provide anything more than relative paging forward and backward, the CSWP does appear to support some form of more advanced paging scheme through its get_pagingInfo() method. When this method is called, it returns an object that contains expanded paging information that includes information about the current page, as well as a subset of pages before and after the current page. The object does not, however, contain all of the information needed to determine the total number of items in the search result set, how to access those pages, etc.

Fortunately for us, though, the ctx.ListData object contains everything we need to implement an end-to-end paging system. Here’s how each of the critical paging information pieces is located or computed within the Control template so that the portion of the control seen below can be rendered:

Paging Line

  • ctx.ListData InspectionTotal number of all search results available. The totalRowCount variable is used within the Control display template scripting to store this value. And the value itself is readily available through the ctx.ListData.ResultTables[0].TotalRows property.
  • Maximum number of items to display per page. This value (represented within the Control display template scripting as rowsPerPageCount) is easily obtained through the ctx.ListData.Properties.RowLimit property. It identifies the maximum number of rows that may be returned in the search results table (through ctx.ListData.ResultTables[0]), and it is the value which should be used to determine the number of individual search results shown in the CSWP.
  • Number of items to display on the current page. In most cases, this value (defined through the rowsOnCurrentPageCount variable within the Control display template scripting) will be the same as the maximum number of items per page. On the last page of results, though, it isn’t uncommon for there to be fewer results available than the maximum number possible. To determine how many items should be shown on the current page of search results, the script simply determines how many rows are present in the current search results table (ctx.ListData.ResultTables[0].RowCount).
  • First page number for the result set. As my kids would say, “Easy peasy lemon squeezy.” This variable (firstPageNumber) always has the intuitive value of 1. This variable should not be confused with the firstPage variable that commonly appears in the OOTB display template scripting. The reference which is assigned to the firstPage variable is a PagingLink object that is meaningful within the context of the pagingInfo object returned from the call to the ctx.ClientControl.get_pagingInfo() – not the page calculations in the display templates associated with this post.
  • Last page number for the result set. This value (represented by the lastPageNumber variable) isn’t readily available in a way that can be “plucked” out of the search data, so the display template scripting simply does a little math to calculate it: Math.ceil(totalRowCount /rowsPerPageCount). In non-code terms: the last page of the total search results set is the total number of results in the set divided by the number of results displayed per page, rounded-down. As with the firstPageNumber and firstPage variables, don’t confuse lastPageNumber with the OOTB lastPage object reference.

In addition to the values described above, a series of calculations are performed to determine the currentPageStartItem and currentPageEndItem variables that represent the first and last item numbers on the current page. Once these values are known and assigned to their respective variables, it becomes a relatively straightforward exercise to display the paging information as is done within the SwissArmy_Control_Template.js Control template. The (not-so-heavy) lifting is accomplished with the following Javascript:

[code language=”javascript” collapse=”true” title=”JavaScript Snippet To Render Paging Information (click to expand)”]
// Figure out the current page information
for (var i = 0; i< pagingInfo.length; i++)
{
var pl = pagingInfo[i];
if (!$isNull(pl))
{
if (pl.startItem == -1)
{
currentPage = pl;
currentPageNumber = pl.pageNumber;
currentPageStartItem = ((currentPageNumber – 1) * rowsPerPageCount) + 1;
if (currentPageNumber == lastPageNumber) {
currentPageEndItem = totalRowCount;
} else {
currentPageEndItem = currentPageNumber * rowsPerPageCount;
}
}
}
}

// Generate page divs and their links
var getPageNumberDivs = function() {
var currentDiv, divBlocks, pageStartItem;
divBlocks = '';
for (var i = firstPageNumber; i <= lastPageNumber; i++) {
cellStyle = 'sas-paging-tablecell';
if (i == currentPageNumber) {
currentDiv = '

‘;
currentDiv += i;
currentDiv += ‘

‘;
} else {
pageStartItem = ((i – 1) * rowsPerPageCount) + 1;
currentDiv = ‘

‘;
}
divBlocks += currentDiv;
}
return divBlocks;
}

// Generate the block that indicates current page, item counts, etc.
var getPageSummary = function() {
var currentDiv;
var pageInfo = ‘Page {1} of {2}, items {3} to {4} of {5}.’;
pageInfo = pageInfo.replace(‘{1}’, ‘‘ + currentPageNumber + ‘‘);
pageInfo = pageInfo.replace(‘{2}’, ‘‘ + lastPageNumber + ‘‘);
pageInfo = pageInfo.replace(‘{3}’, ‘‘ + currentPageStartItem + ‘‘);
pageInfo = pageInfo.replace(‘{4}’, ‘‘ + currentPageEndItem + ‘‘);
pageInfo = pageInfo.replace(‘{5}’, ‘‘ + totalRowCount + ‘‘);
currentDiv = ‘

‘ + ‘‘;
currentDiv += pageInfo + ‘

‘;
return currentDiv;
}

ms_outHtml.push(”
,”
,’


,”
,’


,’


,’ ‘, getPageNumberDivs() ,”
,’ ‘, getPageSummary() ,”
,’


,’


,’


,’ ‘
);
[/code]

Creating The Page Links

One area does warrant a little more explanation, though, and that is how the hyperlinks are created for each of the clickable page numbers at the bottom of the CSWP. There really isn’t a whole lot of magic behind the creation of the hyperlinks themselves; the results are achieved on line 101 of the SwissArmy_Control_Template.js file:

[code language=”javascript” light=”true”]
currentDiv += ‘‘;
[/code]

The pageStartItem variable value is computed as follows …

[code language=”javascript” light=”true”]
pageStartItem = ((i – 1) * rowsPerPageCount) + 1;
[/code]

… where i is the value of the new result page being clicked by the user. For example, if the user clicks on “17” (to indicate a desire to move to page 17 of the search results), the value of i will be 161 when there are 10 items per page.

http://sp2013-dev:18580/DisplayTemplateStyling#k=#s=161

On the server side, SharePoint translates the request for 161 (which can also be reached directly via re-post by attaching a #s=161 to the query string portion of the URL as shown above) to package-up and send down a search results table containing all of the page 17 result items.

How Do I Use The Sample (Downloadable) Files?

Once you’ve downloaded the SwissArmyTemplates.zip file and opened it up, you’ll find that it contains five files:

  • SwissArmy_Control_Template.html
  • SwissArmy_Control_Template.js
  • SwissArmy_Item_Template.html
  • SwissArmy_Item_Template.js
  • SwissArmy_Styles.css

The two HTML files are each of the display templates in their HTML form. These HTML files can be processed through SharePoint 2013’s Design Manager to generate the corresponding JavaScript files that are actually used by the CSWP. Alternatively, the JS files that are included in the ZIP file can be used as-is and dropped directly into the Master Page Gallery > Display Templates > Content Web Parts folder within a site collection to make them available to the CSWP.

The remaining file is a CSS style sheet, and it provides the look and feel that is employed by the display templates. The style sheet itself is referenced from with the Control template files (SwissArmy_Control_Template.html and SwissArmy_Control_Template.js), and the Control template files assume that the style sheet will be available in the following location within the site collection:

~sitecollection/Style Library/en-us/SwissArmyStyles/SwissArmy_Styles.css

If you place the style sheet somewhere else in the site collection, ensure that the $includeCSS() calls in the SwissArmy_Control_Template.html file (line 25) and the SwissArmy_Control_Template.js file (line 164) are updated accordingly.

Final Request

Thanks for citing appropriatelyAs with all of the resources I make available, please feel free to use the display templates and style sheet I’ve provided within your own projects, either as-is or in a form that you’ve modified to suit your needs.

If you do share or redistribute what I’ve provided in some form, whether or not you’ve made modifications, I simply ask that you reference the original source (i.e., me and/or this blog post) in what you’re sharing. I do believe in citing sources where appropriate.

Thanks, and have fun paging through search results!

References and Resources

  1. SharePoint Interface: Files for the Swiss Army Knife CSWP example
  2. MSDN: Content Search Web Part in SharePoint 2013
  3. MSDN: SharePoint 2013 Design Manager display templates
  4. MSDN: Overview of Design Manager in SharePoint 2013
  5. SharePoint Interface: Presentation for SharePoint’s New Swiss Army Knife: The Content Search Web Part
  6. MSDN: 3.1.4.1 ProcessQuery
  7. JSON.org: Introducing JSON

Author: Sean McDonough

I am a consultant for Bitstream Foundry LLC, a SharePoint solutions, services, and consulting company headquartered in Cincinnati, Ohio. My professional development background goes back to the COM and pre-COM days - as well as SharePoint (since 2004) - and I've spent a tremendous amount of time both in the plumbing (as an IT Pro) and APIs (as a developer) associated with SharePoint and SharePoint Online. In addition, I've been a Microsoft MVP (Most Valuable Professional) in the Office Apps & Services category since 2016.

16 thoughts on “Advanced Paging with SharePoint Content Search Web Part Display Templates”

  1. Hey Sean… Was at the SPTechCon 2015 conference back in June in San Franciso and I caught your class there on this topic.
    I have managed to get most of this working, with the exception of using a Keyword Filter “Value of a field on the page”. I’m attempting to query using the client’s id from the client list’s dispform.aspx. And, I want to grab the current ClientID from the page and pass it to the CSWP.

    If I use “ListItem.CF_ClientID” (which is the static name of the field) or “Page.CF_ClientID”, I get the following error in the ULS logs…
    QueryTemplateHelper: Cannot replace parameters in template. The value for parameter Page.CF_ClientID is missing. Parameter : Page.CF_ClientID, Template: ClientLookup:{Page.CF_ClientID} AND (contentclass:STS_ListItem_10002) .

    Any thoughts?

    PS – Very much enjoyed the conference and appreciated your class.

    1. Hi Rich … and thanks for the note/feedback! I think I have a basic understanding of what you’re seeking, but I’m not exactly sure what to suggest. Does the client ID show up in the URL at all? You might be able to pluck it from there more reliably that trying to use a field.

      Regardless, have a look at this reference if you haven’t seen it. It provides an overview of query variables and their syntax, and it might provide some ideas on other ways to get at the value you’re seeking: https://technet.microsoft.com/en-us/library/jj683123.aspx

      Good luck!

  2. hi Sean,
    I am doing a project for a comapny’s FAQ site. They require to display most accessed items with ranking No(1~10), title, date. I used content Search web parts and managed to display the title and date for each FAQ item, but dont know how to put numbers (1~10) in front of each item. Any suggestions?
    Thanks in advance
    Mingyu

    1. Mingyu,

      If you’re talking about simply numbering the items, then the idea that immediately pops to mind for me is a simple modification to the underlying display template(s). It wouldn’t be hard to establish a JavaScript variable that tracks the item number; as the container template is iterating through each of the items, increment the counter by one and use that value to assign a ranking to each item.

      – Sean

  3. Hi Sean,

    Thanks alot for sharing your knowledge! I really needed it for the project I’m working on.

    Keep up the good work!

  4. Hi Sean,

    Thank you very much for sharing the knowledge.

    I am new to SP and love the content search web parts.

    Your article have added so much knowledge and this has solved few problems I had on customizing the web part.

    Thank you ones again.

    Shanaka.

  5. Sean – How much work would it be to have the paging control at the top of the list, as well as at the bottom?

    1. Tom,

      It should be minimal work, really. The chunk of paging HTML that gets emitted is represented by line 128 through 133 in the SwissArmy_Control_template.js template. The getPageNumberDivs() and getPageSummary() method calls generate the sections associated with their names, and the div tags wrap those sections.

      (Note: I originally tried to include the the entire section, but WordPress didn’t like the div tags and wouldn’t render them as straight text – even after I HTML-encoded them and attempted to use WordPress’ CODE tag. So, just reference the template cited and grab the lines I mentioned).

      I haven’t explicitly tried it, but that same chunk could be added to the top of the output (e.g., between lines 40 and 41) to produce the same row of selectable page numbers and ranges.

      I hope that helps!

  6. Hi Sean,

    Very interesting article. I’ve been looking into integrating your solution with that of the standard paging control template (Previous and next arrows) but am running into a few walls in terms of what pages to display.

    Say for instance my Control template’s paging requirements were .

    Can you recommend a method to limit the range of available pages to a fixed amount, say the first 8 + final page? At present, my adaptation is limiting it to the first 8, but this is never changes, even if I use the Next button to go to page 9.

  7. Great article. I’ve been able to upload the templates and can set my web part to use the template however I’m running into something that’s curious. If I modify the Title on the Control template, and modify the “Documents I Can Access Text”, I can see the change to the Title, but the text in the body isn’t reflected. I’ve also noticed that no matter what changes I make to the CSS (to the point that I deleted the CSS file just to see what would happen), changes are not reflected in the Control Template – I always how a red background. Any ideas why this would be occurring? Thanks in Advance

  8. Hi Sean,
    I’m not sure if my last post went through, so if this is a duplicate, sorry. Great article and it’s really helped me move away from a third party roll-up web part that was just really slow and buggy. I’ve run into a couple of issues and I’m not sure what’s going on. I’ve uploaded the templates, but seem to have issues with the Control Template. When I make a change to the title of that template, I can see the change in the dropdown when selecting the control. However, if I make a change to the text “Documents I Have Access To,” I do not see the change. Another curious thing is if I make a change to the CSS, those changes are not reflected (to the point that I’ve even deleted the CSS file to see what happens, and still it reflects the red background). For both the CSS and Control Template, I’ve made sure that I’ve checked in, published, and approved the files, but so far it’s a no go. Any ideas what I’m doing wrong?

    1. Michael,

      If I had to guess what’s happening without looking more closely, my hunch is that you’re getting bitten by some client-side caching. You indicated that file changes you made are checked-in and published, so that shouldn’t be an issue. Have you attempted to do a forced refresh (typically via in your browser) that will dump cached content? Items like CSS and JS files are normally going to be present in your browser cache, and the browser won’t attempt to re-fetch those files (due to max-age values that will essentially tell the browser to use its local copies) until they’ve expired out.

      So, give the approach a try, or use something like IE’s InPrivate (or Chrome’s Incognito) mode to start with an empty cache. If you still don’t see the changes you’re making, let me know and we’ll see if we can do some more troubleshooting to figure out what’s going on.

      Thanks!

      – Sean

      1. Sean, thanks for the quick response. I figured it out. I had to reset the object cache on the site collection. It’s strange I know that I did that when I was having issues yesterday, but it wasn’t working. Today it’s clearing everything out and after I make changes and approve the items, they are reflected in the browser. Again, thanks for the response and great article.

Leave a Reply to Rich Van RooyenCancel reply

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

Discover more from The SharePoint Interface

Subscribe now to keep reading and get access to the full archive.

Continue reading