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 screenshot of what we will be creating

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.


Comments

comments powered by Disqus