Reusable Pagination in Play! 2

On the Play! Framework mailing lists I’ve seen reference to the sample Computer Database as a canonical example of paginating data.  It’s a good start but it’s pretty specific to one data type.  Follow along and we’ll make a more general utility.

If we think abstractly about paginating a web application using Model-View-Controller:

  • M – A way to filter the dataset
  • V – A UI element for displaying the pagination and linking to others
  • C – A way to get Request parameters that define the page, the length of the page, and other filters

A screen shot of what we will be creating

Model

The specifics here depend upon how you are retrieving the data to display.

Squeryl provides a page(offset, pageLength) method that uses the DB’s LIMIT and OFFSET.  I use this to create a subset collection that I pass to view for iterating over.  I also have a helper method to get a total data count.

Controller

Play’s route and reverse routes take care of passing the page number around:

GET  /list       controllers.Notifications.list(page:Int=1)
GET  /list/help  controllers.Notifications.help
# NOTE: /list/:page MUST COME AFTER /list/[string] due to route priorities
GET  /list/:page controllers.Notifications.list(page:Int)

We get pretty URLs like /list /list/2 list/3 etc. Note that @routes.Notification.query(1) will result in /list, which is a nice touch. If /list/1 is your preference you may consolidate to one route. Also note that route priority (order) may affect things depending on the URL parameters you are using.

In the application controller, you need to pass an offset and pageLength to the Model. This returns a collection of a page worth of items.

Then, pass it all through to the view:

  def list(page:Int) = withUser { user => implicit request =>
    val pageLength = 10
    val notifications = Model.getNotifiactionsByUser(user, (page-1)*pageLength, pageLength)
    val count = Model.getCountByUser(user)
    Ok(views.html.notifications.index(notifications, count, page, pageLength))
  }

View

In our list view, we pass some variables through and iterate over the filtered collection of data to display.  Then, we call a helper which creates the pagination UI element.  The biggest thing to note here is the use of Scala’s first class functions, specifically partial application, to delegate the page parameter to the paginate helper:

@(notifications:List[models.Notification], count:Int, page:Int, pageLength:Int)
  @for(n <- notifications) {
    <li>@n</li>
  }
  @includes.paginate(page, pageLength, count, routes.Notification.index(_))

View helper

“I am sorry I have had to write you such a long letter, but I did not have time to write you a short one” — Blaise Pascal

This template code is pretty awful.  I may refine it on the gist if time permits.  Please comment if you have suggestions!

We take the page we’re on, the items per page, the total query count, and the partial route and build a UI wiget.  The lowbound and highbound helpers functions define how many pages to link.

Conclusion

I’m very interested in your take, as well as ways to clean this up!  I’ll update the post with good suggestions.

Be Sociable, Share!
  • email
  • Reddit
  • HackerNews

9 thoughts on “Reusable Pagination in Play! 2”

  1. Hey,
    couldn’t you please post a working example of this to study it more. It is hard for a newbie to figure everything out on his own.

  2. Hi,
    I had trouble to find out what “withUser” means. Then I found something like withComputer in a computer DB sample application so I decided to understand the code there first of all. In my opinion it would be sufficient to have a set of some users data and a single web page with paging to demonstrate the concept. Thanks!

  3. Only slightly related, but…

    MVC – learned all about this programming concept over a decade ago at Apple’s WWDC. I remember people on the WebObjects and Cocoa team would put songs together about MVCs, and I found one on YouTube. I guess I should go dig out my WWDC DVDs and find the others, as they were interesting.

    Enjoy!
    David

  4. Hi – tried to get this working from Java but have had problems with delegating the page parameter to the paginate helper.

    I changed the parameter on the paginate helper to route:Int => play.api.mvc.Call, plus the last parameter on the @includes.paginate to list from index and it all works well.

    Thanks!

  5. Thank you very much.
    I changed the method highbound() as below:
    @highbound() = @{
    if ((lowbound() + bound) * pageLength >= collectionLength)
    math.ceil(collectionLength.toDouble / pageLength).toInt
    else
    lowbound() + bound
    }

  6. i am looking for same type of pagination but i didn’t understand this . Actually i am working in java if you have any tutorial for this with java using play frmaework give me link for that.

  7. Note that there is no need for using double. You can do that in integer arithmetics:

    ((page.toDouble / bound).floor * bound) toInt

    equals to:

    (page / bound) * bound

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>