Sunday, October 21, 2007

Try Zend Framework vol.6 "Simple Pager"

When I look back in my old days, the pagination was my first obstacle to understand how it works like my friend introduced here.
PEAR::Pager was so impressive for me to make pagination. :-)
So, I wrote a simple pager for Zend Framework view helper.

Let's say you have data like below. The total record is 1000.
mysql> select * from data limit 10;
+----+---------+
| id | data |
+----+---------+
| 1 | data 1 |
| 2 | data 2 |
| 3 | data 3 |
| 4 | data 4 |
| 5 | data 5 |
| 6 | data 6 |
| 7 | data 7 |
| 8 | data 8 |
| 9 | data 9 |
| 10 | data 10 |
+----+---------+


Here the pager helper.
It basically requires total number of record for pagination.
library/Hoge/View/Helper/Pager.php
<?php
require_once 'Zend/View/Helper/Url.php';

/**
* Pager Helper
*/

class Hoge_View_Helper_Pager
{
/**
* Current page
* @var integer
*/
protected $currentPage = 1;

/**
* Per page
* How many items to display per pages.
* @var integer
*/
protected $perPage = 20;

/**
* Display links
* How many page to display in the pagination links.
* @var integer
*/
protected $pageRange = 10;

/**
* Total number of items
* @var integer
*/
protected $total = 0;

/**
* Start page
* @var integer
*/
protected $startPage = 1;

/**
* End page
* @var integer
*/
protected $endPage = 1;

/**
* Pager format.
*/
const FORMAT_FIRST = '<a href="%s" class="pager_first">&#171;</a>';
const FORMAT_PREVIOUS = '<a href="%s" class="pager_previous">&#139;</a>';
const FORMAT_PREVIOUS_RANGE = '<a href="%s" class="pager_previous_range">...</a>';
const FORMAT_NAVIGATION_LINK = '<a href="%s" class="pager_navigation_link">%d</a>';
const FORMAT_NAVIGATION_CURRENT = '<b class="pager_navigation_current">%d</b>';
const FORMAT_NEXT_RANGE = '<a href="%s" class="pager_next_range">...</a>';
const FORMAT_NEXT = '<a href="%s" class="pager_next">&#155;</a>';
const FORMAT_LAST = '<a href="%s" class="pager_last">&#187;</a>';

/**
* Base link formats
* @var array
*/
protected $baseFormats =
array(
'first' => self::FORMAT_FIRST,
'previous' => self::FORMAT_PREVIOUS,
'previousRange' => self::FORMAT_PREVIOUS_RANGE,
'navigationLink' => self::FORMAT_NAVIGATION_LINK,
'navigationCurrent' => self::FORMAT_NAVIGATION_CURRENT,
'nextRange' => self::FORMAT_NEXT_RANGE,
'next' => self::FORMAT_NEXT,
'last' => self::FORMAT_LAST
);

/**
* Pager instance
* @var object
*/
static protected $pager;

/**
* Pager
*
* @access public
*
* @param integer $total Total records
* @param integer $perPage How many to display per page
*
* @return object pager
*/
public function pager($total = null, $perPage = 20)
{
if(isset(self::$pager)) return self::$pager;

$page = Zend_Controller_Front::getInstance()->getRequest()->getParam('page');

if ($page > 0) {
$this->currentPage = $page;
}

// Number of total data.
$this->total = $total;

// How many rows to display per page.
$this->perPage = $perPage;

// Number of total page
$this->totalPage = ceil($this->total / $this->perPage);

// If total page is smaller than called page.
if ($this->totalPage < $this->currentPage) $this->currentPage = 1;

// Page numnber of core pages.
$corePage = ceil($this->currentPage / $this->pageRange);

// Max page number.
$this->endPage = ($this->pageRange * $corePage);

// Start page number.
$this->startPage = ($this->endPage - $this->pageRange) + 1;

// Set to static instance
self::$pager = $this;

return $this;
}


/**
* Display a simple pagination ex:[<< < ... 1 2 3 ... > >>]
*
* @param array $formats An array of links to use sprintf()
* @return string A link to the desired page page of the pagination
*/
public function paginate(array $formats = array())
{
$formats = array_merge($this->baseFormats, $formats);

$this->pager($this->total, $this->perPage);
$pagination = array();
$pagination[] = $this->pager()->first($formats['first']);
$pagination[] = $this->pager()->previous($formats['previous']);
$pagination[] = $this->pager()->previousRange($formats['previousRange']);
$pagination[] = $this->pager()->navigation($formats['navigationLink'], $formats['navigationCurrent']);
$pagination[] = $this->pager()->nextRange($formats['nextRange']);
$pagination[] = $this->pager()->next($formats['next']);
$pagination[] = $this->pager()->last($formats['last']);

return join('', $pagination);
}

/**
* Display link of the navigation links ex:[1 2 3]
*
* @param string $linkFormat Format string to display page number links to use sprintf()
* @param string $currentFormat Format string to display current page to use sprintf()
* @return string A link to the desired page page of the pagination
*/
public function navigation($linkFormat = self::FORMAT_NAVIGATION_LINK, $currentFormat = self::FORMAT_NAVIGATION_CURRENT)
{
$return = null;
// Display [1,2,3__]
for ($p = $this->startPage; $p <= $this->endPage; $p++) {
// Unlink if the page is selected
if ($p <= $this->totalPage) {
if ($this->currentPage != $p) {
$url = $this->url(array('page' => $p));
$return[] = sprintf($linkFormat, $url, $p);
} else {
$return[] = sprintf($currentFormat, $p);
}
}
}
return join('', $return);
}

/**
* Display link of the next range ex:[...]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the previous range of the pagination
*/
public function previousRange($format = self::FORMAT_PREVIOUS_RANGE)
{
$return = null;
if ($this->pageRange < $this->currentPage) {
$url = $this->url(array('page' => ($this->startPage - 1)));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the next range ex:[...]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the next range of the pagination
*/
public function nextRange($format = self::FORMAT_NEXT_RANGE)
{
$return = null;
if ($this->endPage < $this->totalPage) {
$url = $this->url(array('page' => ($this->endPage + 1)));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the previous page ex:[<<]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the first page of the pagination
*/
public function first($format = self::FORMAT_FIRST)
{
$return = null;
if ($this->currentPage != 1) {
$url = $this->url(array('page' => 1));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the previous page ex:[<]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the previous page of the pagination
*/
public function previous($format = self::FORMAT_PREVIOUS)
{
$return = null;
if ($this->currentPage != 1) {
$url = $this->url(array('page' => ($this->currentPage - 1)));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the next page ex:[>]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the next page of the pagination
*/
public function next($format = self::FORMAT_NEXT)
{
$return = null;
if($this->currentPage != $this->totalPage) {
$url = $this->url(array('page' => ($this->currentPage + 1)));
$return = sprintf($format, $url);
}
return $return;
}

/**
* Display link of the last page ex:[>>]
*
* @param string $format Format string of link to use sprintf()
* @return string A link to the last page of the pagination
*/
public function last($format = self::FORMAT_LAST)
{
$return = null;
if($this->currentPage != $this->totalPage) {
$url = $this->url(array('page' => $this->totalPage));
$return = sprintf($format, $url);
}
return $return;
}


/**
* Generates an url given the name of a route.
*
* @access public
*
* @see Zend_View_Helper::url()
* @param array $urlOptions Options passed to the assemble method of the Route object.
* @param mixed $name The name of a Route to use. If null it will use the current Route
* @param bool $reset Whether or not to reset the route defaults with those provided
* @return string Url for the link href attribute.
*/
public function url(array $urlOptions = array(), $name = null, $reset = false)
{
$obj = new Zend_View_Helper_Url();
$url = $obj->url($urlOptions, $name, $reset);
return $url;
}

}


Pager needs to know how many total record the object paged has.
In this case, I want to count DB record.
So I added a method to count simple record of Zend_Db_Table object.
library/Hoge/Db/Table/Abstract.php
public function numRows($where = null)
{
$select = $this->_db->select();
$select->from($this->_name, "COUNT(*)", $this->_schema);
// the WHERE clause
$where = (array) $where;
foreach ($where as $key => $val) {
// is $key an int?
if (is_int($key)) {
// $val is the full condition
$select->where($val);
} else {
// $key is the condition with placeholder,
// and $val is quoted into the condition
$select->where($key, $val);
}
}
// return the results
$stmt = $this->_db->query($select);
$data = $stmt->fetchColumn(0);
return $data;
}


To use it.
It is not related to Db model. So you need to calculate the offset and limit.
But it not so difficult, isn't it? :p
application/controllers/HogeController.php
function pagerAction()
{
$this->view->setHelperPath(dirname(dirname(dirname(__FILE__))) . '/library/Hoge/View/Helper', 'Hoge_View_Helper');
$this->view->addHelperPath(dirname(dirname(__FILE__)) . '/View/Helper', 'Hoge_View_Helper');

require_once 'Hoge/Db/Table/Data.php';
$data = new Data();
$perPage = 20; // Should be defined.
$offset = ($this->_getParam('page') > 0) ? $perPage * ($this->_getParam('page') - 1) : 0;
$this->view->data = $data->fetchAll(null, null, $perPage, $offset)->toArray();
$this->view->total = $data->numRows();
}


In the view script. Write like this.
In case you want to handle each parts of pager.
<html>
<head></head>
<body>
<? $this->pager($this->total) ?>
<?= $this->pager()->first() ?>
<?= $this->pager()->previous() ?>
<?= $this->pager()->previousRange() ?>
<?= $this->pager()->navigation() ?>
<?= $this->pager()->nextRange() ?>
<?= $this->pager()->next() ?>
<?= $this->pager()->last() ?>

<ul>
<? foreach($this->data as $k => $v): ?>
<li><?= "{$v['id']} : {$v['data']}" ?></li>
<? endforeach ?>
</ul>
</body>
</html>


In case you want to display simple pager.
<html>
<head></head>
<body>
<?= $this->pager($this->total)->paginate() ?>
<ul>
<? foreach($this->data as $k => $v): ?>
<li><?= "{$v['id']} : {$v['data']}" ?></li>
<? endforeach ?>
</ul>
</body>
</html>

Both of them look like this.
«‹...11121314151617181920...›»

The helper class only have responsibility creating pagination, NOT dataset, so you need to set offset and limit.
If you want to get more sophisticated "Pagination helper" see here :^)
The author is introducing a pagination helper which related to dataset.

(weight 88.6kg BMI 31%)

Labels:

8 Comments:

At 12:27 AM, Anonymous Anonymous said...

Wonderful Pager, thank a lot for example.
I do some improvements to your Pager, to be more similar to PEAR Pager, when its done then I can send it back to you if you're interested.
However, it'll take couple o days, no time for it now :(

 
At 8:43 PM, Blogger kawadu said...

Hi! It sounds so interesting that you improve my pager. And I'm so happy to hear it.
Please take your time to improve this pager. Best regards :-)

 
At 4:07 AM, Blogger Kotek said...

Very useful pice of code. Thanks Kawadu.

Say hello to reindeers in Canada from me :)

 
At 8:47 AM, Blogger kawadu said...

Ah ha! I recall my life in Canada and wanna drink "Kokanee" again!! :)
I wish my code helps you.

 
At 3:06 AM, Anonymous Anonymous said...

Just to say that with the new version of Zend Framework there is a notice from calling a non static methode (URL)

So, to fix this bug, in the method url, you have to write this :

$obj = new Zend_View_Helper_Url();
$url = $obj -> url($urlOptions, $name, $reset);
return $url;

Thank you so much with this helper !

 
At 7:33 AM, Blogger kawadu said...

Hi! dator.
Thank you for telling me about the notice.
I didn't set the error_reporting as E_STRICT, actually it's my bad habit. :P
I corrected the code to prevent from the notice. :)

 
At 3:24 AM, Blogger Unknown said...

Hi there,

about the "more sophisticated "Pagination helper"", which I happen to be the author, I'm sorry but I'm not planning to maintain it anymore. Please have a look to the Zym project (http://www.zym-project.com/) instead :-)

 
At 8:35 PM, Anonymous Anonymous said...

Very useful code. Thanks,
Do you have any example of new pagination code provided by Zend Framework itself?

 

Post a Comment

<< Home