Creating an adaptive pager using ASP.NET MVC 4 HTML helpers and Twitter Bootstrap 3

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 :

aaa

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, "&laquo;");

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, "&raquo;");

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, "&raquo;");

            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, "&laquo;");
            // 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, "&raquo;");

            builder.AppendFormat("</ul>");
            return new HtmlString(builder.ToString());
        }

The pager would look like this :

Index 2013-10-26 18-56-45

 

The source code is attached with the post

The source code is available here

Enjoy !

Comments (3) -

  • I'm a newbie in MVC and have been looking for a guide in implementing a pager control for quite some time.
    The sample code is inspiring and would like to see more tools from you in the future Smile
    Keep it up!
  • Very very nice. Thank's a lot.
    ciao e grazie.
    (from Italy)
  • Great helpful article for paging !

    감사합니다.

Add comment

Loading