gold stars

Bill Hunt Gold Star

Pagination, etc.

posted by: Nate :: Jan 7th 2007, 01:02

So the big noteworthy development of the week is pagination. With the exception of a couple additional methods and enhancements, pagination in both the controller and the view is basically complete.

Here's a basic rundown of how it works:

// controllers/posts_controller.php: //
class PostsController extends AppController {

	var $components = array('RequestHandler');
	var $paginate = array('limit' => 3, 'order' => array('Post.created' => 'desc'));

	function index() {
		// Fetches paged results; this is equivalent to calling findAll.
		$data = $this->paginate();
		$this->set(compact('data'));
	}
}

// views/layouts/default.ctp: //
<?=$html->docType('xhtml-strict'); ?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title><?=$title_for_layout?></title>
	<?=$html->charset('UTF-8'); ?>
	<?=$html->css('default'); ?>
	<?=$javascript->link(array('prototype', 'event-selectors')); ?>
	<style type="text/css">
		div.disabled {
			display: inline;
			float: none;
			clear: none;
			color: #C0C0C0;
		}
	</style>
</head>
<body>

<div id="main">
	<div id="spinner" style="display: none; float: right;">
		<?=$html->image('spinner.gif'); ?>
	</div>

	<div id="content">
		<?=$content_for_layout; ?>
	</div>
</div>

</body>
</html>

// webroot/img/spinner.gif: //
Loading...

// views/posts/index.ctp: //
<? $paginator->options(array('update' => 'content', 'indicator' => 'spinner')); ?>

<?=$paginator->prev('<< Previous', null, null, array('class' => 'disabled')); ?> |
<?=$paginator->next('Next >>', null, null, array('class' => 'disabled')); ?> |
Sort by 
<?=$paginator->sort('created'); ?> |
<?=$paginator->sort('updated'); ?> |
Page <?=$paginator->counter(); ?>

<? pr($data); ?>

And with that we end up with a basic pagination display, with the record data dumped below it:

Pagination example

So there you have it: CakePHP pagination with Ajax.

Overview

  • The Controller - In the controller, we start be defining the pagination defaults in the $paginate variable. It is important to note here that the 'order' key must be defined in the array structure given. Then, in the action, we call the paginate() method, which returns paged findAll() results from the model, and grabs some additional paging statistics, which are passed to the View behind the scenes. This method also adds PaginatorHelper to the list of helpers in your controller, if it has not been added already.
  • The Layout - A couple of things to pay attention to here: we're including the Prototype library in the header, we're setting up our status indicator image, spinner.gif, with a wrapper DIV called "spinner", and we're setting up our main content wrapper DIV, "content".
  • 'indicator' - The 'indicator' key (see the first line of the index.ctp view code) is actually a new feature in AjaxHelper, which you can include in any Ajax generator (links, buttons, etc). It allows you to specify a DOM ID of an element that will be shown while an Ajax request is loading, and hidden when it completes, which saves you from having to write an extra bit of JavaScript.
  • The View - We start by calling $paginator->options(), which defines a default set of options which will be added to all pagination links. In this case we're saying that for all pagination links, we want to update the element with the ID 'content' with the resulting data, and we want to show 'spinner' as the loading indicator. This brings up an important point: by specifying the 'update' key, PaginatorHelper knows that we want to generate Ajax links. If this key is not specified, we'll get plain old standard-issue non-Ajax links.

    The next lines are defining the 'previous' and 'next' links. The array parameters to each of those methods are the options that apply if the links are disabled, i.e. if there is no previous or next page, respectively. By default, these methods will render nothing if the corresponding page does not exist, but by passing them a set of options to use when disabled, they will render their link text (or alternate text, as is optionally specified in the 3rd parameter) in a wrapper DIV, with the given options.

    The following two method calls define sorting links, so users can choose how to sort the paged results. The final method, counter() generates a simple page counter so we can keep track of what page we're on, and how many pages there are in total.

The output of all these methods is highly configurable, and clearly this is a very simple overview. However, do not despair: documentation on all this and more will be forthcoming very shortly.



Side Note

"Well, okay, you're not privy to all the new shit..."
- The Dude
  The Big Lebowski

What is it that makes newcomers to this community and others outside it come in and think they know more about the core code or what to do with it than we do? (By "we" I mean the CakePHP team, not the royal we).

Don't get me wrong, I'm all for new ideas, but it never ceases to amaze me how people think that they have better ideas on how certain things should work, or that we somehow "need" their ideas. More often than not, these people have little to no knowledge or understanding of the philosophies on which Cake is built.

News flash: we've been around for a couple of years now, and in that time we've grown to become the biggest and most popular PHP framework available (by a long shot, depending on which figures you follow), and we did so by keeping our own counsel on decisions related to code and functionality. If you have new ideas, great. By all means, let us hear them; but maintaining a quality framework is all about being picky. If you think you're right and we think you're wrong, chances are, you're wrong.

Now, dear readers, please understand, and take the preceding thoughts in the spirit which they are meant. The fact is, they are not intended for most of you, but those they are intended for, you know who you are.

27 comments

bottom

1. Felix Geisendörfer :: on Jan 07, 2007 Hey nate, that's cool stuff - I'm looking forward to use it soon.

However, I think one shouldn't use the short tags syntax in public/educational posts (the one that echo's it's contents directly) because not all hosts support it which could lead to portability issues.
2. Othman Ouahbi :: on Jan 07, 2007 This is nice in fact,
I guess it's possible to tweak the code in the controller more to be able to paginate a model, that isn't the default one ( included in uses or loadModel()..), I suppose there is support for paginating multiple models too, I'll dig in that more
3. Nate :: on Jan 07, 2007 Felix: I use short tags because I prefer them. The code is meant to be instructive on Cake, not general PHP. I can make a couple of assumptions about my readership, and take it for granted that they will know better ; )

Othman: You can paginate any model loaded in the controller with $uses. You can also define pagination defaults on a per-model basis, and instructions on how to do these things are all forthcoming in the documentation.
4. Ludge :: on Jan 07, 2007 I'm glad there's built-in pagination support forthcoming. It'll be handy.

However, after reading the last couple of paragraphs, including the bit saying "take the preceding thoughts in the spirit which they are meant", I can't help but feel very disappointed in your writing.

Yes it's "your" framework and hence you are free to do what you wish with it. It's just a shame you sound so, well, big-headed about it. You have a public issue tracker (Trac), which accepts enhancement requests, so either restrict access or don't complain. Yes it's a pain when people keep reopening tickets, but they're probably not the ones reading your blog. It's just something you have to learn to live with.

Meh, anyway, regardless of this text, if I find something I feel needs changing and it's not been mentioned, I'll still create a ticket and send a patch if possible.
5. Mladen Mihajlovic :: on Jan 07, 2007 Hey Nate,

Great posts lately, thanks a lot. Could you maybe write about the i18n and the L10n functionality in cake? I mean a bit more info that just "use __('text')". Like how db info works with it and stuff? Also how to make the .mo and .po files and such stuff.

Thanks again.
6. Nate :: on Jan 07, 2007 Hi Ludge, this topic really deserves it's own post, but it's not about being big-headed, it's about being passionate about the philiosophies that drive this framework forward. I wasn't talking specifically about people who keep reopening tickets, although we deal with that and much more every day, but that doesn't keep me from being amazed at their audacity.

For me, it's important for other people to understand why we code the way we code, but when people are willing to sacrifice a vision they don't see or understand for their own narrow gain, this is not only self-centered, but ignorant.
7. Ludge :: on Jan 07, 2007 Rightyo, I thought you were mainly talking about those persistent buggers that keep posting the same thing going "but *i want* feature X!"
8. jon baer :: on Jan 07, 2007 Good stuff Nate.

As a side note it is probably (maybe) a good idea to eventually remove much of the "snippets" on CakeForge made for older Cake versions. Or at least the stuff that has made it into core to prevent confusion :-) Had someone asking me about his pagination problems and had no idea he was using something very old.
9. Dr. Tarique Sani :: on Jan 07, 2007 A very educative post. A similar one on using Behaviors would be appreciated
10. oliver stark :: on Jan 14, 2007 cool stuff.
As Mladen (comment 6) said afore, a i18n article would be great...
11. Nate :: on Jan 14, 2007 Jon: yeah, even short of removing them, some kind of notice as to their status would definitely be appropriate.

Dr. Sani & Oliver: I'll try and get some info out on that stuff as soon as I can, however, i18n is still under heavy development, and some aspects of the features have not yet been completed, i.e. db-based translations.
12. Othman Ouahbi :: on Jan 14, 2007 Just noticed:
1)
$paginator->options(array('update' => 'content', 'indicator' => 'spinner'));

There is no such function in Paginator nor parent classes.

2)
$paginator->counter();

gives an error, page not defined..

using the version 1.2 in the trunk


13. Nate :: on Jan 14, 2007 The trunk hasn't been updated. Grab the version in branches.
14. Tristan Bendixen :: on Jan 23, 2007 I've been using Cake v1.1 for a while now, and I love it, but moving forward is important, so going to make a separate "branch" of my development where I look into v1.2 of the code, which I hope will result in some really nice code. Already seeing tons of optimizations to my code, especially in the templates, where the FormHelper::input() function saves a LOT of typing. :)

However, with this pagination, I was kinda wondering if it would be possible to pull out the page-number, and the total number of pages, in individual functions? I'm asking because I sometimes code pages in a different language (specifically Danish), and the "1 of 4" looks kinda silly when the rest is in another language. I've so far just used some string replacing hacking to avoid editing the Paginator class itself, but if there's another option I haven't seen, or if it's perhaps coming with the i18n part, then please feel free to post it if you get the time.

Anyway, good work on v1.2 so far! It really rocks, and it's going to be ever so cool when it gets done. :)
15. Nate :: on Jan 24, 2007 Hi Tristan, good question. Actually, PaginatorHelper::counter( ) takes parameters that allow you to specify a different separator string, or a completely custom counter string with embedded variables.

PaginatorHelper also includes methods which allow you to query page and count values directly.
16. Tristan Bendixen :: on Jan 24, 2007 Hi Nate,

Thanks! I had actually not even thought to look in the PaginatorHelper source, which is kinda odd, because I did that with some of the other things, though there are some things I still find kinda confusing about the v1.2 release, possibly because I don't get the idea behind them yet. One of those is the model behaviors, but I guess more information about those will come at a later time.

Oh, and looking at the source for the PaginatorHelper, I find myself a bit confused about the range format, but I think I'm just going to fiddle with it, and try it out to see what it looks like. :)
17. Chris Domigan :: on Feb 08, 2007 Thanks for this informative article, Nate.

One thing I've found is that when setting conditions in the controller using $this->paginate($conditions), the paginator "forgets" its conditions when sorting or changing pages. i.e. The first page is displayed filtered correctly, but when clicking Next, Prev or sorting, it loses all the conditions.

I know I can hardcode conditions into the $paginate property, but I need to set them dynamically through the controller action.

Any thoughts?
18. Nate :: on Feb 09, 2007 Hey Chris, conditions is one thing that Paginator doesn't deal with directly, although we might consider an enhancement to automatically persist arbitrary variables in pagination links.

In the meantime, there are several approaches to dealing with conditions, the quickest and easiest of which would be to just store them in the session. Another way would be to pass them as named arguments into the paginator URLs you generate, like so:

$paginator->next("Next >>", array("url" => array("q" => $searchString)));

Or, pass it in the query string:

array("url" => array('?' => "q=" . urlencode($searchString)))

(I know, I know, the syntax for that last one was ugly. I'm working on it).
19. Mariano Iglesias :: on Feb 09, 2007 A quick way to paginate any model in the controller and set up options on a model basis:

class MyController extends AppController
{
var $name = 'My';
var $uses = array('Model');
var $paginate = array();

function index()
{
$this->paginate['Model'] = array(
'limit' => 2,
'order' => array ('Model.name' => 'asc')
);

$data = $this->paginate('Model');
}
}
?>
20. Andy :: on Feb 12, 2007 Hi Nate,

I hit a couple of hitches and had a flick through the code to see if it was user-solvable. seems like no, so looking for input

1) A string constraint produces sql with "Array" as the constraint
2) It's not possible to specify the fields array

I don't know if there are greater plans afoot, but both of these are necessasry to be able to paginate a result set that include any aggregate functions (afaik)

As I don't know if this is by design I didn't put it in a ticket.

Comments?

Cheers,

AD
21. Nate :: on Feb 14, 2007 Hey Mariano: how is that different from defining it as a var in the controller itself? I find it helps to keep my code cleaner to put all the default settings in var $paginate (since you can index it by model anyway), and just change the values that you need to change on a per-action basis.

Andy: you should be able to specify a 'fields' key in the $paginate property, and assign it a value just like you would pass to the $fields parameter in findAll. As for your other issue, try wrapping the conditions string in an array.
22. vunguyen :: on Feb 16, 2007 Uhm, I want to paginate using custom query on multiple tables, how can I do that with paginate?

Cheers
23. osffco :: on Mar 13, 2007 Hi Nate
Can you help my please with this :
Call to undefined function paginate()
I have cakephp1.2.0.4451alpha.

thaks
24. Nate :: on Mar 16, 2007 osffco: No, I'm sorry, I'm afraid I have no way of helping you, because I have no idea what you're doing. If you're looking for support, please head over here: http://groups.google.com/group/cake-php/

Or here: irc.freenode.net #cakephp
25. Nate :: on Mar 16, 2007 vunguyen: This is not currently supported.

read more comments, then add one of your own.

 
more comments
 
top