AJAX pagination made simple

Paginated lists in web pages imply specific controls to navigate across pages (link to previous and next page, link to a specific page, etc.). And lists are paginated because they are often too big to load. But when AJAX comes in, this last constraint disappears, and the specific controls can easily be replaced by a more intuitive tool: The browser's scroll bar.

The happy users of Firefox and Greasemonkey may already know the great user experience of the GoogleAutoPager script. It's a little extension that makes your Google search results so much easier to browse. When the user scrolls down to the bottom of the page, looking for more results, an AJAX request is made in the background to load the next page and display it in the current page - even before the user reaches the pagination controls.

If you still don't understand how it works, have a look at this online demo. Yes, this is a symfony demo, and that means that symfony provides a way to implement such an AJAX pagination.

AJAX pager demo

The good news is, it works with Propel pagers AND other pagers, provided that you have an action that can produce any page. It works for lists in <ul> as well as for list in <table>, or in any tag you may want to use. It works with IE and Firefox. It degrades gracefully. And, most of all, it is so easy to use that you will want to try it immediately.

See for yourself. The AJAX pagination demo uses two very simple actions, both passing a pager to their template:

class pagerActions extends sfActions
  public function executeIndex()
    $this->pager = $this->getPager();
  public function executeAjaxPager()
    $this->pager = $this->getPager();    

  private function getPager()
    $c = new Criteria();
    $pager = new sfPropelPager('WeblogPost', 10);
    $pager->setPage($this->getRequestParameter('page', 1));
    return $pager;

The template indexSuccess.php displays the list of results through a partial:

  <tr><td>List of posts - <?php echo $pager->getNbResults() ?> results</td></tr>

  <?php include_partial('pager_list', array('pager' => $pager)) ?>

  <tr id="ajax_pager"><td></td></tr>

The pagerlist.php partial is as simple as you can imagine:

<?php foreach ($pager->getResults() as $post): ?>

    <?php echo $post->getCreatedAt() ?> -
    <?php echo $post->getTitle() ?>

<?php endforeach ?>

Where is the pager navigation, you may ask? You have to add the following to the indexSuccess.php template:

<?php echo remote_pager(array(
  'url'      => 'pager/ajaxPager',
  'update'   => 'ajax_pager',
  ), $pager) ?>

  <?php echo pager_navigation($pager, 'pager/index') ?>

For it to work, you will need the new PagerHelper.php in your application's lib/helper/ directory. Get it from here until we decide to commit it in the trunk or not.

By the magic of the remote_pager() helper, an observer triggers the AJAX request when the scrollbar reaches the bottom of the page, and uses the response to fill the element of id ajax_pager. The template of the AjaxPager action, called ajaxPagerSuccess.php, is short:

<tr><td>posts <?php echo $pager->getFirstIndice() ?> to <?php echo $pager->getLastIndice() ?></td></tr>

<?php include_partial('pager_list', array('pager' => $pager)) ?>

One last thing: the view.yml of the module has to specify that this AJAX action has no layout.

And that's it. The remote_pager() helper provides ways to customize everything, from the frequency of the AJAX call to the number of pixels from the bottom of the pager triggering the call. If you want to know more, you are invited to browse the source of the helper for the description of options.

Not only does symfony provide web tools for lazy devs, it also provides web tools for lazy users!

Help the Symfony project!

As with any Open-Source project, contributing code or documentation is the most common way to help, but we also have a wide range of sponsoring opportunities.


Doesn't work in Opera 9 on Windows XP.
The PagerNavigation helper is now available as a plugin.

i can't get it to work:
var scrollpos = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
always 0 so trigger till all entries are loaded

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.