Twitter released recently their brand new Bootstrap and they changed radically many things and notably the paging system. In this post, I will show how to create an ASP.NET MVC4 HTML Helper that renders an adaptive pager.
The first thing I have to explain is what is an adaptive pager ? Imagine a collection of 1000 records, if the pager uses a page size of 20 records, the pager will renders 50 links which is not a very positive rendering manner according to the modern web page rules. Accordingly, an adaptive pager is constituted by three sections : the first section contains the three first pages, the last section contains the three last pages and the middle section contains either the current page (if the current page is neither in the first par nor the last part) or the middle pages.
An adaptive pager style by bootstrap would look like this :
To create the helper, we have to create a static class called “HtmlRenderExtensions”.
In this class, we will create an extension method callded “RenderPager” like this :
public static HtmlString RenderPager(this HtmlHelper html, string controllerName, string actionName, int recordNumber, int pageSize, int currentPage)
{
}
Let’s calculate the number of pages with this simple formula:
// calculate the number of pages
var numberOfPages = recordNumber / pageSize;
if (recordNumber % pageSize != 0)
++numberOfPages;
Of course, if there not enough pages (at least 2) we don’t need pagination :
if (numberOfPages < 2)
return new HtmlString(string.Empty);
The first thing to do is to create an URL helper to generate the links URLs :
// create an URL helper to generate urls
var urlHelper = new UrlHelper(html.ViewContext.RequestContext, html.RouteCollection);
var link = urlHelper.Action(actionName, controllerName);
After that, we need a string builder to generate the HTML markup:
StringBuilder builder = new StringBuilder();
After that, the bootstrap, the pagination is built using an unordered list (with the class “pagination”), that’s why we’ll append with an opening “ul” tag :
builder.Append("<ul class=\"pagination\">");
We need a method that generates the list item with the associated links, we’ll call this method “AppendPagerTag” and its code is as follow:
private static void AppendPagerTag(StringBuilder builder, int targetPage, UrlHelper helper, string controllerName, string actionName, int currentPage, string tagText = null)
{
// the link markup
string linkTag = "";
// the active css
string activeCss = "";
// the page text
if (tagText == null)
tagText = targetPage.ToString();
// a positive value of targetPage points to a real page while a negative value points to a simple text (span)
if (targetPage > 0)
{
// if the target page is the current page, then we'll add the "active" class to the item
if (targetPage == currentPage)
activeCss = "active";
var link = helper.Action(actionName, controllerName, new { page = targetPage });
// generate the link markup
linkTag = string.Format("<a href=\"{1}\">{0}</a>", tagText, link);
}
else
// generates the separator markup
linkTag = string.Format("<span>{0}</span>", tagText);
// embed the generated markup in a list item
builder.AppendFormat("<li class=\"{1}\">{0}</li>", linkTag, activeCss);
}
When we pass negative value, this method will generate separators instead of links. Otherwise, it will generate a link with a different page parameter.
The next thing to do in the extension method is to generate the “previous” link if the current page is higher than “1”.
if (currentPage > 1)
AppendPagerTag(builder, currentPage - 1, urlHelper, controllerName, actionName, currentPage, "«");
The pager is divided generally in three sections : the first that contains the first pages, the last that contains the last pages and the middle section that contains some calculated pages.Let’s set the first section :
// the first section contains the first pages
IEnumerable<int> section1 = new int[] { 1, 2, 3 }.ToList();
Let’s now set the last section :
// the last section contains the last pages
IEnumerable<int> section3 = new int[] { numberOfPages - 2, numberOfPages - 1, numberOfPages }.ToList();
Let’s now calculate the middle section which is a special section :
// calculate the floating middle section. If the current page is in the middle, the floating section is a region that
// contains the current page otherwise, it's the region that contains the middle pages
int middleStart;
if ((currentPage <= 2) || (currentPage >= numberOfPages - 1))
{
middleStart = numberOfPages / 2;
if (middleStart < 5)
middleStart = 5;
}
else
if ((currentPage >= 3) && (currentPage < 6) && (currentPage < numberOfPages - 2))
{
middleStart = 5;
}
else
middleStart = currentPage;
var middle = new int[] { middleStart - 1, middleStart, middleStart + 1 };
Using the magic of Linq, we’ll constitute the full pages list that is composed of the three sections and eventually some separators if the current page do not belong to the first or the last section.
// create the list of pages that are composed of the three sections and eventual separators that are represented by negative numbers (-99 and -98)
IEnumerable<int> pages = section1;
if (middle.First() > 4)
pages = pages.Union(new int[] { -98 });
pages = pages.Union(middle);
if (middle.Last() < numberOfPages - 3)
pages = pages.Union(new int[] { -99 });
Let’s browse the generated collection and eliminate the pages that are not coherent according to the collection :
// filter the pages to take into account only the coherent pages by eliminating redundancies and illogical pages
foreach (var page in pages.Where(e => (e <= numberOfPages && e > 0) || e == -99 || e == -98).Distinct())
{
if (page > 0)
AppendPagerTag(builder, page, urlHelper, controllerName, actionName, currentPage);
else
AppendPagerTag(builder, page, urlHelper, controllerName, actionName, currentPage, "...");
}
If we are not in the last page, we need to generate the “next” page :
// generate the next page if we are not in the last page
if (currentPage < numberOfPages)
AppendPagerTag(builder, currentPage + 1, urlHelper, controllerName, actionName, currentPage, "»");
Finally, let’s close the “ul” and stop the rendering process :
// generate the next page if we are not in the last page
if (currentPage < numberOfPages)
AppendPagerTag(builder, currentPage + 1, urlHelper, controllerName, actionName, currentPage, "»");
builder.AppendFormat("</ul>");
return new HtmlString(builder.ToString());
The complete method code is as follow:
public static HtmlString RenderPager(this HtmlHelper html, string controllerName, string actionName, int recordNumber, int pageSize, int currentPage)
{
// calculate the number of pages
var numberOfPages = recordNumber / pageSize;
if (recordNumber % pageSize != 0)
++numberOfPages;
if (numberOfPages < 2)
return new HtmlString(string.Empty);
// create an URL helper to generate urls
var urlHelper = new UrlHelper(html.ViewContext.RequestContext, html.RouteCollection);
var link = urlHelper.Action(actionName, controllerName);
// create a string builder to generate HTML
StringBuilder builder = new StringBuilder();
builder.Append("<ul class=\"pagination\">");
// generate the previous "link"
if (currentPage > 1)
AppendPagerTag(builder, currentPage - 1, urlHelper, controllerName, actionName, currentPage, "«");
// the first section contains the first pages
IEnumerable<int> section1 = new int[] { 1, 2, 3 }.ToList();
// the last section contains the last pages
IEnumerable<int> section3 = new int[] { numberOfPages - 2, numberOfPages - 1, numberOfPages }.ToList();
// calculate the floating middle section. If the current page is in the middle, the floating section is a region that
// contains the current page otherwise, it's the region that contains the middle pages
int middleStart;
if ((currentPage <= 2) || (currentPage >= numberOfPages - 1))
{
middleStart = numberOfPages / 2;
if (middleStart < 5)
middleStart = 5;
}
else
if ((currentPage >= 3) && (currentPage < 6) && (currentPage < numberOfPages - 2))
{
middleStart = 5;
}
else
middleStart = currentPage;
var middle = new int[] { middleStart - 1, middleStart, middleStart + 1 };
// create the list of pages that are composed of the three sections and eventual separators that are represented by negative numbers (-99 and -98)
IEnumerable<int> pages = section1;
if (middle.First() > 4)
pages = pages.Union(new int[] { -98 });
pages = pages.Union(middle);
if (middle.Last() < numberOfPages - 3)
pages = pages.Union(new int[] { -99 });
pages = pages.Union(section3);
// filter the pages to take into account only the coherent pages by eliminating redundancies and illogical pages
foreach (var page in pages.Where(e => (e <= numberOfPages && e > 0) || e == -99 || e == -98).Distinct())
{
if (page > 0)
AppendPagerTag(builder, page, urlHelper, controllerName, actionName, currentPage);
else
AppendPagerTag(builder, page, urlHelper, controllerName, actionName, currentPage, "...");
}
// generate the next page if we are not in the last page
if (currentPage < numberOfPages)
AppendPagerTag(builder, currentPage + 1, urlHelper, controllerName, actionName, currentPage, "»");
builder.AppendFormat("</ul>");
return new HtmlString(builder.ToString());
}
The pager would look like this :
The source code is attached with the post
The source code is available here
Enjoy !