Click here to Skip to main content
15,885,182 members
Articles / Web Development / ASP.NET
Article

Implementing a NextPrev Numeric Pager in ASP.NET

Rate me:
Please Sign up or sign in to vote.
4.33/5 (7 votes)
6 Dec 2007CPOL6 min read 100.6K   1.4K   55   14
Code for implementing a NextPrev numeric pager mode via a pager template class.

Introduction

So you're hacking away with your GridView and you're amazing. You're like a programming god. You've got your GridView displaying hundreds of pages of easily sorted data. Your product owner asks you for a specific kind of paging. No problem! You've got every pager known to man! You've got numeric, first last next previous, numeric first last, next previous. You tell your product owner, whatever they want, it's no problem. They want next previous numeric. Now, what is that?

Using the code

Please someone tell me how it is that Microsoft has *still* not implemented this trivial looking pager!? Seriously, these are the built-in choices:

  • Numeric
  • NumericFirstLast
  • NextPrevious
  • NextPreviousFirstLast

You get a cookie if you notice the one option they didn't include here. Ummm... hmmm. Yeah! NumericNextPrevious!!! Why not? I don't know. In this article, I'm going to provide you with the template I've created (as well as the unit tests that were used to build it), and explain how it works.

In case you didn't notice, this is actually a GridView and a custom pager. I made some tweaks to my Enhanced GridView to support very simple integration of this new pager. You just have a setting on the GridView of EnableNextPrevNumericPager="true" and voila! For those of you who care about the how's and unit tests, please read on.

C#
public class NextPrevNumericPagerTemplate : ITemplate
{
  public void InstantiateIn(Control container)
  {
     int pagerStartIndex = startPageIndex(_pageIndex, _pageCount);
     int pagerEndIndex = endPageIndex(_pageIndex, _pageCount);

     createResultsSummary(container);

     createPrevButton(container);
     createSpacer(container);

     createPrevEllipsisIfNeeded(container, pagerStartIndex);
     createCorrectPageButtons(container, pagerStartIndex, pagerEndIndex);
     createNextEllipsisIfNeeded(container, pagerEndIndex);

     createNextButton(container);
  }

So, that's really the core of the code. Basically, it will create a pager that looks like this:

Page 12 of 31
<a href="#">< Prev</a> <a href="#">...</a> <a href="#">11</a> 12 <a href="#">13</a> <a href="#">14</a> <a href="#">15</a> <a href="#">16</a> <a href="#">17</a> <a href="#">18</a> <a href="#">19</a> <a href="#">20</a> <a href="#">...</a> <a href="#">Next ></a>

The class itself inherits from the ITemplate interface, and the method that we use is the InstantiateIn method. All of the controls we want in our GridView's pager, we add to the control variable that the InstantiateIn method takes as a parameter. Where the code says createResultsSummary, that's where we create the part that tells us what page we are on. It takes in the control and updates it. Since it is updating the control directly, we don't need it to return any value.

The spacer method adds a space to the control. Please ignore the inconsistency of this being the only time at this level that we need a spacer. I will remedy that at a future time. The createPrevEllipsisIfNeeded only adds the ellipsis if there are results previous to the set of pages you are looking at etc. etc.

The most important methods being called here are actually the startPageIndex and endPageIndex. They determine what will be the start and end range of the pages that will show up in our pager. This was really where I leveraged TDD. It helps to program in a couple of test cases and see if the code behaves how you'd like, make changes to your code if it doesn't, and repeat until your tests go green. I'll show one of those methods now as well as one of its unit tests.

C#
[TestMethod]
public void PagerTemplate_startPageIndex_CeilingFloorTest()
{
   int totalPageCount = 6;
   int currentPageIndex = 5;
   int expected = 0;
   int actual = accessor.startPageIndex(currentPageIndex, totalPageCount);
   Assert.AreEqual(expected, actual, "The expected value was not returned.");
}

This unit test basically describes what my start and end range should be when the currently viewed page is near the high end of a small range. This particular test was coded to reproduce a bug I found while doing some manual testing. In English, it says, if you're on page 6 (currentPageIndex+1) of six total pages, I expect the starting page number to be one. In my tests before I fixed this, the lower end was -3... hehe. Yeah and by the way, I know what you're thinking... expecting to see some NUnit? Not anymore. Unfortunately, I'm working to embrace Microsoft's Team System unit testing framework, so I'm going to be showing less if not no more of the NUnit tests.

C#
private int startPageIndex(int currentPageIndex, int totalPageCount)
{
   int startingPageToDisplay = 0;
   startingPageToDisplay = currentPageIndex - 4;

   if ((pageIndex(totalPageCount) - currentPageIndex) < 5)
   {
      startingPageToDisplay = pageIndex(totalPageCount) - 9;
   }
   if (startingPageToDisplay < 0)
   {
      startingPageToDisplay = 0;
   }
   return startingPageToDisplay;
}

Hopefully, this helps you see why unit tests can be a life saver. This code basically gives the following rules:

  • (Assumed throughout the code) There are only to be, at most, ten pages displayed on the pager at one time.
  • The current page should be as close to the center of the range as possible. This is the reason we subtracted 4 from the starting page. We can either have 4 or 5 pages on the left to keep this as centered as possible.
  • I'd rather see more pages to the right of the current page. This is the reason I chose to have only 4 on the left side, which will leave 5 pages for the right side.
  • If the last page index and the current page index are less than 5 pages apart, then show all the pages to the left (up to the maximum). This is where the bug was happening. To account for that, I added the following, which passed my unit tests.
  • If our starting index is less than zero, then it should be set to zero. We'll just have less than ten pages to show.

Now, as far as using this with my GridView goes... all you should have to do is set the custom pager template of your GridView to an instance of this class. It's the same thing to get this to work with your custom GridView as well. I just needed to override the correct event. This is all the code I needed to add to my Enhanced GridView to hook the two up together:

C#
protected override void InitializePager(GridViewRow row, int columnSpan, 
                        PagedDataSource pagedDataSource)
{
  if (this.EnableNextPrevNumericPager)
  {
     base.PagerTemplate = new NextPrevNumericPagerTemplate(this.PageIndex, 
                                                           this.PageCount);
  }
  base.InitializePager(row, columnSpan, pagedDataSource);
}

Points of interest

If you'll notice, I'm utilizing unit tests on private methods. The reason for this is I am trying my best to use Test Driven Development more and more. If I am writing code that requires design, I'd rather do that via tests. There may be an argument made for methods being made to be simpler or perhaps moved to a different class. That's possible. I just know that there are plenty of instances where testing private methods made sense to me and making them into new classes did not.

I've also heard that private code changes a lot more than the public code, and it can become a hassle to keep the tests up to date. In my opinion, those tests should be updated first. You should be programming your expectations into those tests and then programming the implementation into your code. That's my two cents on this controversy, for the mean time. Take it for what it's worth.

I like to keep my articles fairly concise, focusing on giving you the gist of how I programmed things. I'm assuming an intermediate to advanced level developer is reading this (creating custom server controls isn't a beginner's task), and I try my best to explain what I do explain at the level of an intermediate beginner. I'd like to explain more than I think is necessary to ensure I don't glance over complexities. If you have any questions, feel free to give me a holler!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Justin Bozonier is 25 years old and lives with his wife in Bellevue, WA. He currently works for LexisNexis Courtlink as a Software Engineer on the UI team for File & Serve/Total Litigator.

Justin started dabbling in ColdFusion and eventually grew that knowledge into working with ASP and eventually into ASP.Net.

His current passions are exploring Test Driven Development, elegant unit test development, and design patterns. You can read more about his current pursuits by visiting his blog at http://www.aboutjustin.com/

Comments and Discussions

 
Questionhi.. Pin
Finamori16-Feb-12 5:50
Finamori16-Feb-12 5:50 
Questionhi.. Pin
Rohan Chikhale10-Nov-11 18:32
Rohan Chikhale10-Nov-11 18:32 
GeneralNext,prev image button Pin
mike_hac1-May-10 23:06
mike_hac1-May-10 23:06 
GeneralTrouble with Prev Button Pin
Member 466183621-Jan-10 10:30
Member 466183621-Jan-10 10:30 
GeneralRe: Trouble with Prev Button Pin
Thorbjörn Karlström9-Feb-10 22:01
Thorbjörn Karlström9-Feb-10 22:01 
Generalimplement Pin
pupilstuff5-Jul-09 23:30
pupilstuff5-Jul-09 23:30 
GeneralRe: implement Pin
Eric Kazda2-Dec-10 9:58
Eric Kazda2-Dec-10 9:58 
GeneralI had to make this change for the code to work with ImageButton: Pin
Member 268000019-Sep-08 6:31
Member 268000019-Sep-08 6:31 
GeneralUsing a custom control Pin
bleermakers12-Jul-07 0:09
bleermakers12-Jul-07 0:09 
GeneralRe: Using a custom control - update PinPopular
bleermakers26-Jul-07 3:36
bleermakers26-Jul-07 3:36 
QuestionRe: Using a custom control - update Pin
Zena20019-Oct-07 7:12
Zena20019-Oct-07 7:12 
AnswerRe: Using a custom control - update Pin
5acred Phoenix26-Oct-07 6:30
5acred Phoenix26-Oct-07 6:30 
GeneralIt is not quite true... Pin
Mykola Tarasyuk29-May-07 1:24
Mykola Tarasyuk29-May-07 1:24 
GeneralRe: It is not quite true... Pin
Justin Bozonier29-May-07 3:47
Justin Bozonier29-May-07 3:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.