For today’s post I’m assuming you’ve read the Jobeet Tutorial, or at least made it as far as Day 7. If you didn’t read about basic pagination here.

OK, so we’ve got the basics. But once you start implementing this into some projects or several places into one project you’ll notice that there will be a lot of copying. And once you want to change the layout or the structure you have to adapt a lot of code. I decided to first refactor a bit so that all the code is nicely in one place. Aside from that I’ll introduce very simple ajax to load the pages.

Setup

For this example I’m going to use my module ‘offers’ with an index and a _list partial

$offers, 'pager' => $pager)) ?>

The action is something like this.
// /apps/frontend/modules/offers/actions/actions.class.php
class OfferActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$query = Doctrine_Core::getTable('Offer')->createQuery('o');
$this->offers = $query->execute();
}
}

myDoctrinePager

I chose to extend the sfDoctrinePager with myDoctrinePager (under /apps/frontend/lib) to make it easier to add custom functionality. Here’s how:

// /apps/frontend/lib/myDoctrinePager.class.php
class myDoctrinePager extends sfDoctrinePager
{
/**
* Custom pager that uses a query to do paging
* @param Doctrine_Query $query a doctrine query for the data
* @param string $url the url of the script (defaults to current script)
* @param $max_pages the number of pages (defaults to app config value paging_limit)
*/
public function __construct(Doctrine_Query $query, $max_pages=null)
{
if (is_null($max_pages))
$max_pages = sfConfig::get('app_paging_limit');
if (!$max_pages)
throw new Exception("Please set application config paging_limit to a value above 0");

$this->query=$query;
parent::__construct($this->query, $max_pages);
}
}

Simplifying the actions

Now we could have just made a global function getPager() or put even more in the myDoctrinePager __construct() function, but I’ve chosen this way to still keep it customizable.

Here’s how I modified the action
// /apps/frontend/modules/offers/actions/actions.class.php
class OfferActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$query = Doctrine_Core::getTable('Offer')->createQuery('o');
$this->pager = $this->getPager($query);
$this->offers = $this->pager->getResults();
}

/**
* Returns an initialized myDoctrinePager
* @param Doctrine_Query $query
* @return myDoctrinePager
*/
public function getPager(Doctrine_Query $query)
{
$pager = new myDoctrinePager($query);
$pager->setPage($this->getRequestParameter('page', 1));
$pager->init();
return $pager;
}
}

This could later have any custom setting for this pager, either standard pager config or customized methods.

Reusable Template

Ok not much has changed yet – you might still be wondering why you’re going through the trouble of reading (or doing) this. Now comes the interesting part. We’ll add a generalized template to our /apps/frontend/templates/ directory:



haveToPaginate()): ?>

haveToPaginate()): ?>
- page getPage() ?>/getLastPage() ?>

And of course to be able to use it we’ll need to modify our pager class
// /apps/frontend/lib/myDoctrinePager.class.php
public function __construct(Doctrine_Query $query, $ajax_url, $max_pages=null)
{
// ..
$this->setTemplate("global/paging");
}

public function setTemplate($template)
{
$this->template = $template;
}

public function render()
{
echo $this->renderPartial($this->template);
}

protected function renderPartial($template, array $vars=array())
{
sfContext::getInstance()->getConfiguration()->loadHelpers('Partial');
return get_partial($template, array_merge(array("pager"=>$this), $vars));
}

The only thing that might be a little confusing here is how to render a partial from your model. I use the same approach as sfAction does: loading the partial helper through sfContext and simply printing the partial.

The last change is we’ll need to modify our partial to use the render() method.




render(); ?>

Adding ajax

OK, so far we’ve only refactored paging to make it reusable on different parts of your application. You might not even have needed all that. It might be you are skipping here. Well just for that I’ve kept adding the ajax very very easy. I’ll use jQuery.

First we’ll add some javascript to our index template. You might want to change how you’ve named jquery (or if you don’t have it yet, download it from the jquery website and place it under /web/js/jquery.js)


$offers, 'pager' => $pager)) ?>


So with that out of the way we’ll need to use the new loadPage(page) function.

haveToPaginate()): ?>


Adding filters

Next time I’ll introduce filters into this system. This will also be with javascript and will make the code much more complex.

Symfony Tips & Tricks

  1. Symfony I18n and messages extraction
  2. Use gmail with swift mailer
  3. Generalizing and formatting forms
  4. Generalized & Ajax Pagination