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
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.




















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.
@Zenius – can you describe what you are having trouble with? I’d have to make a full demo app to show much more than this.
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!
Ahh, withUser comes from an Action Composition but it is unnecessary for this example. See: http://www.playframework.org/documentation/2.0.4/ScalaActionsComposition if you are curious.
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
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!
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
}