Integrating Pear Pager
PEAR (http://pear.php.net) has a decent Pager class that is easy to integrate in Cake.
1 - Download and install
Download the PEAR pager package from http://pear.php.net/package/Pager. Unpack it and put the libraries in (e.g.) /vendors/Pear/Pager.
2 - Make a component
Make a component "pager.php". This will act as a wrapper between CakePHP and Pager. This way, we can do all the Pager setup outside of the controller. Most parameters will probably remain unchanged between the different invocations. We have the possibility to overwrite specific parameters later from the controller.
Component Class:
Download code
<?php
class PagerComponent extends Object
{
/**
* The (calling) controller object.
*
* @access public
* @var object
*/
var $Controller;
/**
* The pager object.
*
* @access public
* @var object
*/
var $Pager;
/**
* Configuration parameters
*
* @access public
* @var array
*/
var $params;
/**
* Component pseudo controller
*
* @access public
* @param object $controller Calling controller object
* @return void
*/
function startup(&$controller) {
$this->Controller =& $controller;
}
/**
* Initializes the pager. Must be called before using the component.
*
* Takes user configuration and creates pager object ($this->Pager)
*
* @access public
* @param array $config Configuration options for Pager::factory() method
* @see http://pear.php.net/manual/en/package.html.pager.factory.php
* @return void
*/
function init($config)
{
// Get the correct URL, even with admin routes
$here = array();
if (defined('CAKE_ADMIN') && !empty($this->Controller->params[CAKE_ADMIN])) {
$here[0] = $this->Controller->params[CAKE_ADMIN];
$here[2] = substr($this->Controller->params['action'], strlen($this->Controller->params[CAKE_ADMIN]) + 1);
} else {
$here[2] = $this->Controller->params['action'];
}
$here[1] = Inflector::underscore($this->Controller->params['controller']);
ksort($here);
$url = implode('/', $here);
// Set up the default configuration vars
$this->params = array(
'mode' => 'Sliding',
'perPage' => 10,
'delta' => 5,
'totalItems' => '',
'httpMethod' => 'GET',
'currentPage' => 1,
'linkClass' => 'pager',
'altFirst' => 'First page',
'altPrev '=> 'Previous page',
'altNext' => 'Next page',
'altLast' => 'Last page',
'separator' => '',
'spacesBeforeSeparator' => 1,
'spacesAfterSeparator' => 1,
'useSessions' => false,
'firstPagePre' => '',
'firstPagePost' => '',
'firstPageText' => '<img src="'.$this->Controller->base.'/img/first.gif" alt="">',
'lastPagePre' => '',
'lastPagePost' => '',
'lastPageText' => '<img src="'.$this->Controller->base.'/img/last.gif" alt="">',
'prevImg' => '<img src="'.$this->Controller->base.'/img/prev.gif" alt="">',
'nextImg' => '<img src="'.$this->Controller->base.'/img/next.gif" alt="">',
'altPage' => 'Page',
'clearIfVoid' => true,
'append' => false,
'path' => '',
'fileName' => $this->Controller->base . DS . $url . DS . '%d',
'urlVar' => '',
);
vendor('Pear/Pager/Pager');
// Merge with user config
$this->params = array_merge($this->params, $config);
// sanitize requested page number
if (!in_array($this->params['currentPage'], range(1, ceil($this->params['totalItems'] / $this->params['perPage'])))) {
$this->params['currentPage'] = 1;
}
$this->Pager =& Pager::factory($this->params);
// Set the template vars
$this->Controller->set('pageLinks', $this->Pager->getLinks());
$this->Controller->set('currentPage', $this->params['currentPage']);
$this->Controller->set('isFirstPage', $this->Pager->isFirstPage());
$this->Controller->set('isLastPage', $this->Pager->isLastPage());
}
?>
3 - More Features
You should add other template vars as needed. Those you see are just the most often needed (by me).
4 - Load the component
Don't forget to load the component in your application! Set
PHP Snippet:
Download code
<?php
<?php var $components = array('Pager'); ?>
?>
in your controller.
5 - Controller setup
In your posts_controller.php, do the following:
Controller Class:
Download code
<?php
function index($page = 1) {
// setup the pager
$params = array(
'perPage' => 10,
'totalItems' => $this->Post->findCount(),
'currentPage' => $page,
);
$this->Pager->init($params);
// get the data
$this->set('data', $this->Post->findAll(null, null, 'Post.created DESC', $this->Pager->params['perPage'], $this->Pager->params['currentPage']));
}
?>
I have chosen the more efficient of the two methods available to feed data to pager. Instead of selecting all records of a table and let Pager decide which records to show (which can be very memory intensive with large result sets), we give Pager the total number of items
PHP Snippet:
Download code
<?php
$this->Post->findCount();
?>
and fetch only the rows we need for our page to display:
PHP Snippet:
Download code
<?php
$this->Post->findAll(null, null, 'Post.created DESC', $this->Pager->params['perPage'], $this->Pager->params['currentPage'];
?>
6 - In the view
You will output the pager (if appropriate) in your index.thtml as follows:
View Template:
Download code
<?php
// Display pager if there are pages to display
if ($pageLinks['all']) {
echo '<div id="pager" class="pager">Pages: ' . $pageLinks['all'] . '</div>';
}
?>
7 - The End
That's all, folks :)
Comments
Comment
1 Navigation links
I added a function in the component:
Component Class:
<?phpfunction getNavLinks()
{
return $this->Pager->linkTags;
}
?>
In the controller:
Controller Class:
<?php$this->set('navLinks', $this->Pager->getNavLinks());
?>
And in the header of my default layout:
View Template:
<?php if(!empty($navLinks)) echo $navLinks;?>
Comment
2 Its nice example but it isnt work when you use admin.
There is problem:
...
'path' => '',
'fileName' => $this->Controller->base.DS.$this->Controller->params['controller'].DS.$this->Controller->params['action'].'/%d',
'urlVar' => '',
);
Because:
$this->Controller->params['controller'] return admin_name-actin, this generate bad url for pagination url.
Comment
3 Component enhanced
You are right, thank you for reporting. I added a patch to make it work with Cake admin routes.
Bug
4 Error if admin route is undefined
Notice: Use of undefined constant CAKE_ADMIN - assumed 'CAKE_ADMIN' in /app/controllers/components/pager.php
So, I fixed the init() function with one little addition. Instead of...
Component Class:
<?phpif (!empty($this->Controller->params[CAKE_ADMIN])) {
?>
I check to see if the constant has been defined:
Component Class:
<?phpif (defined('CAKE_ADMIN') and !empty($this->Controller->params[CAKE_ADMIN])) {
?>
Otherwise, great work! I appreciate this contribution very much.
Comment
5 Fixed
Thank you for this fix, I integrated it in the code :)
Comment
6 Keep GET variables
// Keep variables defined in GET , except for 'url'
$args = isset( $this->Controller->params['url'] ) ? $this->Controller->params['url'] : '';
$get = '';
foreach($args as $name => $value ) {
if($name != 'url' ) $get .= $name . '=' . $value;
}
if( $get!='') $get = '?'.$get;
// .....
// ....
// Add to fileName parameter
'fileName' => $this->Controller->base . '/' . $url . '/' . '%d' . '/' . $get,
Question
7 Paging urls out of wack
What am I doing wrong?
Thanks
Comment
8 Not to worry I found it
I changed
'fileName' => $this->Controller->base . DS . $url . DS . '%d',
to
'fileName' => $this->Controller->base . "/" . $url . "/". '%d',
and it is working now
Question
9 How can paginate multiple columns
example:
item a | price a
-----------------
aa1 | 5.99
aa2 | 1.99
aa3 | 0.99
Thanks in advance.
Aldo
Question
10 How can paginate multiple columns
Excuse me, I was trying to say a table like this:
--------------------------------------
| item a | price a | item a | price a |
--------------------------------------
| aa1 | 5.99 | aa2 | 4.99 |
| aa3 | 3.99 | aa4 | 1.50 |
| aa5 | 5.99 | aa6 | 1.99 |
---------------------------------------