Next & Previous

Pager Gotchas

One recurring problem I really don’t like to solve in software development is pagers. Whether it’s blog posts or API results, I’ll jump over the moon to avoid writing a pager. But why? For one thing, I remember all the finickity little problems that come up every time I try to write a pager. So maybe it’s time to take away the power of these pesky problems by naming them!

0. Which way does time run?

Firstly, design decisions:

OK then, if you’re going to have UI elements to navigate pages, what will the text say? ‘Older’ and ‘Newer’, ‘Previous’ and ‘Next’, ‘Earlier’ and ‘Later’? Are your UI elements honest and understandable?

1. What happens when page number isn’t a number?

For all the chaos monkeys out there who like to type ?p=shampoo into the address bar just to see what happens… you’re why we can’t have nice things! Always check that the page number is actually a number, by wrapping it in a safe conversion or cast like int.TryParse() in C#.

2. What happens when page number is out of bounds?

If the page goes below 0 (or 1 if you’ve decided that’s your lower bound) then you can amend it to 0. If you’re making a user interface, remember to hide the ‘Previous’ button if the current page number is 0, too.

If the page goes over the numOfPages, you can bound it down to the numOfPages. And remember to leave off the ‘Next’ link on that page.

A technical question - will you redirect to ?p={numOfPages} or will you keep what the user typed into the address bar and just serve them the view of the final set of results?

3. Speaking of which, how do we calculate numOfPages?

To find your numOfPages, you can divide the number of items by the page size (items per page)… just remember to add 1 if there is a remainder! Your programming language might have operators like Integer Division and Remainder (Modulo) that help with this.

10 articles, 5 per page -> 2 pages 11 articles, 5 per page -> 3 pages

4. How do we handle user-defined page size?

You’re asking for trouble with a custom page size, but you can limit the surface area of your grief by putting all the bounds checking we just discussed on the pageSize too: check it’s above a reasonable minimum, below a reasonable limit, and is numeric. You decide what’s a reasonable max and min page size for your use case. While an API might cap a page to 1000 results, a blog might need to cap at 50 or 100 to keep the load times fast. Likewise, 1 item per page is unreasonably few for a page, but what about 2… is that OK with you?

5. Can the final page load fewer results than the others without crashing?

If you’re using an ORM to get results from the database and you ask it to fetch 10 results for the last page, but only 4 are available, will it be handled gracefully, or will your program fall over? Make sure you test it and find out.

6. How do we avoid a page with 0 results?

Some coding mistakes can lead to a final page with 0 results. This might happen if you’re always adding 1 to the page size in Gotcha #3 without checking for a remainder first.

7. How do we avoid duplicate results across pages?

Make sure the last result on page 1 isn’t also the first result on page 2. Check your logic for retrieving posts from the database fills the pages exclusively:

Page 0 - Items 0 to 9 (Count = 10) Page 1 - Items 10 to 19 (Count = 10)

This algorithm could be expressed as: firstItemId = pageNum * pageSize - can you think of any edge cases?

8. How do we preserve the user’s query string?

If you ever have to change the user’s query string (the bit of the URL after a question mark), make sure you preserve the other parts of their query, like search terms or filters. It’s really annoying to go to Page 2 or change the page size and find out that the website has forgotten your ‘cute dogs’ filter and now you’re looking at Page 2 of everything!

Your language or framework might have tools to extract the querystring as key-value pairs, which you can then add-to and amend without having to do nasty string manipulation. The Node.js querystring library has a parse function for this, and in .Net you can use HttpUtility.ParseQueryString.

Wrap up

That’s all I could think of! I am on twitter @SteGriff if you think of something I should add! Cheers