Monday, May 23, 2011

Symfony2 + Twig pagination class

I was looking for some pagination class to use with Symfony2, but most of the scripts that I found included html in the class, after some googling I found this post, and it was very helpful to get what I was looking for. I wanted something like the old digg pagination so I modified this class a little bit to get something like this:





The modifications I made:

- Show a range of pages around the current page as in the old digg.
- Parameters can be passed in a route, so you get a nice URL.



This is the class after the modifications:

<?php
/**
* Class to paginate a list of items in a old digg style
*
* @author Darko Gole%u0161
* @author Carlos Mafla <gigo6000@hotmail.com>
* @www.inchoo.net
*/
namespace Paginator;

class Paginator {

//current displayed page
protected $currentpage;
//limit items on one page
protected $limit;
//total number of pages that will be generated
protected $numpages;
//total items loaded from database
protected $itemscount;
//starting item number to be shown on page
protected $offset;
//pages to show at left and right of current page
protected $mid_range;
//range initial page
protected $start_range;
//range end page
protected $end_range;


function __construct($itemscount, $currentpage = 1,$limit = 20,$mid_range = 7) {
//set total items count from controller
$this->itemscount = $itemscount;

$this->currentpage = $currentpage;

$this->limit = $limit;

$this->mid_range= $mid_range;

//Set defaults
$this->setDefaults();

//Calculate number of pages total
$this->getInternalNumPages();
//Calculate first shown item on current page
$this->calculateOffset();


$this->calculateRange();

}


private function calculateRange() {

$this->start_range = $this->currentpage - floor($this->mid_range/2);
$this->end_range = $this->currentpage floor($this->mid_range/2);

if($this->start_range <= 0)
{
$this->end_range = abs($this->start_range) 1;
$this->start_range = 1;
}
if($this->end_range > $this->numpages)
{
$this->start_range -= $this->end_range-$this->numpages;
$this->end_range = $this->numpages;
}
$this->range = range($this->start_range,$this->end_range);


}

private function setDefaults() {
//If currentpage is set to null or is set to 0 or less
//set it to default (1)
if (($this->currentpage == null) || ($this->currentpage < 1)) {
$this->currentpage = 1;
}
//if limit is set to null set it to default (20)
if (($this->limit == null)) {
$this->limit = 20;
//if limit is any number less than 1 then set it to 0 for displaying
//items without limit
} else if ($this->limit < 1) {
$this->limit = 0;
}
}

public function getNumpages() {
return $this->numpages;
}

private function getInternalNumPages() {
//If limit is set to 0 or set to number bigger then total items count
//display all in one page
if (($this->limit < 1) || ($this->limit > $this->itemscount)) {
$this->numpages = 1;
} else {
//Calculate rest numbers from dividing operation so we can add one
//more page for this items
$restItemsNum = $this->itemscount % $this->limit;
//if rest items > 0 then add one more page else just divide items
//by limit
$restItemsNum > 0 ? $this->numpages = intval($this->itemscount / $this->limit) 1 : $this->numpages = intval($this->itemscount / $this->limit);
}
}



private function calculateOffset() {
//Calculet offset for items based on current page number
$this->offset = ($this->currentpage - 1) * $this->limit;
}

public function getCurrentpage() {
return $this->currentpage;
}

public function getCurrentUrl() {
return $this->currentUrl;
}

//For using from controller
public function getLimit() {
return $this->limit;
}
//For using from controller
public function getOffset() {
return $this->offset;
}

public function getRange()
{
return $this->range;
}

public function getMidRange()
{
return $this->mid_range;
}

}







And this is how you can implement it in the controller:

use Path\to\class\Paginator;

class ListController extends SiteController
{
/**
* @extra:Route("/{offset}", name="_items")
* @extra:Template()
*/
public function listAction( $offset = 1)
{
$repository = new ListRepository();

$limit = 20;
$midrange = 7;

$itemsCount = $repository->getListCount();


$paginator = new Paginator($itemsCount, $offset , $limit, $midrange);

$items = $repository->getList ($offset);

.
.
.
return array('items' => $items, 'paginator' => $paginator);
}
}



And this is an example of a twig template you can use:


<div class="paginator">
<ul>
{% if paginator.currentpage != 1 %}
<li> <a class="previous" href="{{ path('_items', { 'offset': paginator.currentpage-1 }) }}">Previous</a>

{% endif %}
{% for i in 1..paginator.numpages%}

{% if paginator.range.0 > 2 and i == paginator.range.0 %}
...
{% endif %}


{% if(i==1 or i==paginator.numpages or i in paginator.range) %}

{% if i==paginator.currentpage %}
<li><a class="active" href="{{ path('_items', { 'offset': i })}}">{{i}}</a></li>
{% else %}
<li><a href="{{ path('_items', { 'offset': i }) }}"> {{i}}</a></li>
{% endif %}
{% endif %}

{% if paginator.range[paginator.midrange -1] < paginator.numpages -1 and i == paginator.range[paginator.midrange-1] %}
...
{% endif %}

{% endfor %}


<li> <a class="next" href="{{ path('_items', { 'offset': paginator.currentpage 1 }) }}">Next</a>
</ul>
</div>






I'm sure there are many improvements to be made, but it may help someone to have an idea.

You can find the files on git:

https://github.com/gigo6000/Symfony2-Pagination-Class

Thursday, May 12, 2011