Persisting Pagination Limits in CakePHP

If you find yourself paginating a lot of results, you may want to give your users the ability to change the number of results per page they see. Easy enough, I know, but the user’s preference should also persist through the session no matter what data the application is serving up. And, ideally, we don’t want to POST every time we want to change the pagination limit: I like having it right in the URL for easy reading.
So, I’m just going to do a little quickie here to show you how easy it is to put in user-modifiable pagination limits. We’ll write a common view element for pagination, then we’ll turn to AppController and write a couple of functions to handle setting the pagination limit and interpreting changes to that limit. Finally, we’ll slightly modify the way we set up pagination in individual controllers to handle the new code. It’ll go something like this:

  • Setting up Common View Elements
  • New Functions in AppController
  • Modifying Individual Controller Pagination Calls

Setting up a Common View Element
OK, so if all of our paginated results are going to look the same then we might as well have a common view element. This element will present the choices for the number of results per page, as well as the usual “display X out of X results” line. It looks like this (app/views/elements/pagination/top.ctp):

Results per page:
<?php
$results = array();
foreach ((array)$paginationOptions as $option) {
if ($paginationLimit == $option) {
$results[] = $option;
} else {
$args = $this->passedArgs;
$args['Paginate'] = $option;
$results[] = $htmla->link($option, $args);
}
}
echo implode(" | ", $results);
?>

<?php
echo $paginator->counter(array(
'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%.', true)
));
?>

That’ll produce a string of plain text links – not a form – that the user can use to change pagination limits. The output looks like this:
Results per page: 1 | 5 | 10 | 25 | 50 | 100

Page 1 of 105, showing 10 records out of 1042 total... (etc)
There are two variables in the pagination/top.ctp that we need to define, as well – $paginationOptions and $paginationLimit. You just need to set these somewhere in your AppController before the view is rendered, so in, say, beforeFilter() – I actually set mine in conjunction with my quick and dirty widget component (/end shameless plug ):

// Pagination options
$this->set('paginationOptions', array(1, 5, 10, 25, 50, 100));
$this->set('paginationLimit', $this->_paginationLimit());

We’ll write the _paginationLimit() function in the next section.
We’ll also make a new element for the bottom of paginated results too (app/views/elements/pagination/bottom.ctp):

<?php echo $paginator->prev('<< '.__('previous', true), array('url' => $this->passedArgs), null, array('class'=>'disabled'));?>
| <?php echo $paginator->numbers(array('url' => $this->passedArgs));?>
<?php echo $paginator->next(__('next', true).' >>', array('url' => $this->passedArgs), null, array('class'=>'disabled'));?>

Now to insert the elements into a typical view with typical paginated results. I like to insert top.ctp as a table caption, but you can do anything you’d like. If we’re paginating in, say, PlayersController::admin_index, it might look something like this (app/views/players/admin_index.ctp):

<?php __('Players');?>

<?php echo $this->element('pagination/top'); ?>

.... (data in table) ...
<?php echo $this->element('pagination/bottom'); ?>

The views are finished – now we just need the methods to make it all work.
New Functions in AppController
Two new functions here, one that we’ll use in the individual controllers in place of defining $this->paginate, and another one called by that function to set the pagination limit depending on possible user input and the existing session-written value.
The first method, _setPaginate(), is just a shortcut for setting $this->paginate in controllers, so we don’t need to set the limit every time. It’s short and sweet (app/app_controller.php):

protected function _setPaginate($options = array()) {
$defaults = array(
'limit' => $this->_paginationLimit()
);

$this->paginate = array_merge($defaults, $options);
}

Notice that _setPaginate() calls _paginationLimit(), which is the function that sets and gets the limit in the user’s session:

protected function _paginationLimit() {
if (isset($this->params['named']['Paginate'])) {
$this->Session->write('Pagination.limit', $this->params['named']['Paginate']);
}

// On two lines for blog readability.
return ($this->Session->check('Pagination.limit') ? $this->Session->read('Pagination.limit') :
Configure::read('SiteSettings.default_pagination_limit'));
}

The system is done – now we just have to use it!
Modifying Individual Controller Pagination Calls
This is the easy part – just call _setPaginate() instead of explicitly defining $this->paginate when setting up your pagination. For example, in our PlayersController::admin_index() function we’re used to doing this:

$this->paginate = array(
'limit' => 20,
'contain' => array('Player, 'Position')
);
$this->set('players', $this->paginate());

But now we’ll just do this (app/controllers/players_controller.php):

function admin_index() {
$this->_setPaginate(array(
'contain' => array('Team', 'Position')
));

$this->set('players', $this->paginate());
}

Pretty simple, eh? I prefer using helper functions to set variables anyway, so this kills two birds in one stone by automatically setting the pagination limit and getting rid of the “$this->paginate =” business in controllers.
As always, comments, suggestions, improvements and helpful criticisms welcome!