A simple method to paginate data seperately on a single page using the cakePHP paginator helper/object.
The Problem
In this case I want to paginate the news items, but I have a flag in the news item table for is_archive, so basically want to seperate the current news items from the archived ones and display both sets of data on the same page in two seperate tables. Then use cakePhp’s built in pagination to navigate through both sets of data independently of each other.
I used AJAX and the paginator options to get a very simple implementation working quite quickly.
Using the options of the paginator helper and AJAX you should be able to paginate several different sets of data for several different models on a single page:
/**
* Holds the default options for pagination links
* - <i>$options['url']</i> Url of the action. See Router::url()
* - <i>$options['url']['sort']</i> the key that the recordset is sorted.
* - <i>$options['url']['direction']</i> Direction of the sorting (default: 'asc').
* - <i>$options['url']['page']</i> Page # to display.
* - <i>$options['model']</i> The name of the model.
* ...(+ more options)
*/In this case I just use the url option in order to retrieve the correct data for each seperate pagination. But if you wanted to paginate two seperate models on a single page you could pass the model option in theory (I didn’t need to test this).
Controller:
function index() { $this->News->recursive = 0; $this->set('news', $this->paginate(null, array('is_archive'=>0))); $this->set('archive', $this->paginate(null, array('is_archive'=>1))); }
Simply get the two sets of data and pass them to the View:
<div id="news_data"> <div class="news index"> <h2><?php __('News');?></h2> <table cellpadding="0" cellspacing="0"> <tr> <th><?php echo $paginator->sort('id', null, array('url'=>array('action'=>'get_news')));?></th> <th><?php echo $paginator->sort('title', null, array('url'=>array('action'=>'get_news')));?></th> <th><?php echo $paginator->sort('date', null, array('url'=>array('action'=>'get_news')));?></th> <th><?php echo $paginator->sort('created', null, array('url'=>array('action'=>'get_news')));?></th> <th class="actions" colspan="2"><?php __('Actions');?></th> </tr> <?php $i = 0; foreach ($news as $news): $class = null; if ($i++ % 2 == 0) { $class = ' class="altrow"'; } ?> <tr<?php echo $class;?>> <td> <?php echo $news['News']['id']; ?> </td> <td> <?php echo $news['News']['title']; ?> </td> <td> <?php echo $news['News']['date']; ?> </td> <td> <?php echo $news['News']['created']; ?> </td> <td class="actions"> <?php echo $html->link($html->image('up.gif'), array('action'=>'moveup', $news['News']['id']), array('title'=>'up'), false, false); ?> <?php echo $html->link($html->image('down.gif'), array('action'=>'movedown', $news['News']['id']), array('title'=>'down'), false, false); ?> </td> <td class="actions"> <?php echo $html->link(__('View', true), array('action'=>'view', $news['News']['id'])); ?> <?php echo $html->link(__('Edit', true), array('action'=>'edit', $news['News']['id'])); ?> <?php echo $html->link(__('Delete', true), array('action'=>'delete', $news['News']['id']), null, sprintf(__('Are you sure you want to delete news item: "%s"?', true), $news['News']['title'])); ?> </td> </tr> <?php endforeach; ?> </table> </div> <div class="paging"> <div class="paging_left"> <?php echo $paginator->counter(array('format' => __('Page %page% of %pages%', true)));?> </div> <div class="paging_right"> <?php echo $paginator->prev('<< '.__('previous', true), array('url'=>array('action'=>'get_news')), null, array('class'=>'disabled'));?> | <?php echo $paginator->numbers(array('url'=>array('action'=>'get_news')));?> <?php echo $paginator->next(__('next', true).' >>', array('url'=>array('action'=>'get_news')), null, array('class'=>'disabled'));?> </div> </div> </div>
This is the portion of the view where I’m displaying the current news items. It differs only slightly from the standard cakePHP index view, firstly I wrapped all the data I want to substitue in a div:
<div id="news_data"></div>
I’ve also changed the paginator links by passing the url option:
<?php echo $paginator->numbers(array('url'=>array('action'=>'get_news')));?>
Now all I do is bind the click events of the paginator links to a javascript function which performs an AJAX call, retrieving the data from the url and substituting the content of:
<div id="news_data"></div>
with the html passed back from the AJAX call.
Using jQuery for this:
$(document).ready(function () { $('.paging a').click(paginate); $('th a').click(paginate); }); var paginate = function(event) { event.preventDefault(); var href; href = $(this).attr('href'); $.ajax({ url: $(this).attr('href'), cache: false, success: function(html){ if (href.match(/.*\/get_archive\/.*/i)) { $('#archive_data').html(html); } if (href.match(/.*\/get_news\/.*/i)) { $('#news_data').html(html); } $('.paging a').click(paginate); $('th a').click(paginate); } }); }
So I bind all the anchor tags of the paging div and all the anchor tags inside the th cells so they all make AJAX calls for pagination. The href.match is because I have the archive news item data in the same view paginating in the same way.
So, the controller code for handling these AJAX calls is straightforward, just returning the paginated data of the news items:
function get_news() { if ($this->RequestHandler->isAjax()) { $this->set('news', $this->paginate(null, array('is_archive'=>0))); $this->viewPath = 'news/ajax/'; } }
The View just returns a big chunk of html identical to the view above, so its not very light and quick. But its about as straightfroward as you can get
HTH.
NOTE:
I have come across a limitation of this technique which I haven’t had time to look into – if the paginator object is shared between the two sets of data on the page (when you load index view) then it shares attributes. So the archive data might paginate to several pages, while the news might only have a couple of items in there, BUT, the news paginator will show pagination links to several pages…or so it seems (I’ve just noticed this).
{ 15 comments… read them below or add one }
isn’t it easier jsut to do it in “plain” php?
well, possibly! the cakephp built in pagiantion has proved very useful however, just in this case I wanted to paginate two sets of data on the same page which is a bit odd.
if you were to tackle this without using the built in pagination then you would have to juggle lots of variables around in the URL I would think which gets messy when you are editing data etc.
In the JS rather than rebinding the events you could just use jQuery.live()
@saintberry good tip! thanks
Hi There,
Did you find a work around for the limitation you mentioned above? (Separating the shared paginator attributes?)
No I didn’t, though I didn’t look very hard its not obvious how to get around that limitation from memory.
Well my turn to try … should be fun
And well although not that much fun my solution was quick and simple.
moved $this->set(‘archive’, to a secondary method, then called that using javascript->event and ajax->remotefunction to update the second element on the page load.
And all is hunky dory.
By the way, thanks for the article above.
no probs, thanks for the solution
in your script you will have a wrong count of pages.
This topic is useless, the problem ‘CakePHP paginator for multiple sets of data on one page using AJAX’ is not solved
Yes, I think it will be the wrong count of pages for the set of data with the least number of pages (from memory). I think Jacques got around this issue using a separate AJAX call to update the page with the correct count of the smallest data set.
sorry jacques but that is just not a solution. that is crap. no offense, but googling this problem for hours and then finding this, just makes me wanna cry.
@pere I’m concerned that it took hours to find this post on google. This post doesn’t so much solve the problem of multiple pagination but it provides an interesting approach and highlights some limitations with the cakephp paginator.
Do you know of a non-AJAX solution for this?
Sorry Nick, I don’t