I’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
Before 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).
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
One 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)”]
- Title of Document
[/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
… the CSWP on that page would request search results for display by posting a request to the following endpoint:
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]
The 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:
Total 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 = ‘
currentDiv += ‘‘;
currentDiv += i;
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
As 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
- SharePoint Interface: Files for the Swiss Army Knife CSWP example
- MSDN: Content Search Web Part in SharePoint 2013
- MSDN: SharePoint 2013 Design Manager display templates
- MSDN: Overview of Design Manager in SharePoint 2013
- SharePoint Interface: Presentation for SharePoint’s New Swiss Army Knife: The Content Search Web Part
- MSDN: 3.1.4.1 ProcessQuery
- JSON.org: Introducing JSON