CakePHP 2-0 Ajax Pagination WITHOUT The Pages CakePHP 2-0 Ajax Pagination WITHOUT The Pages

As I promised in Creating AJAX Pagination WITHOUT The Pages, I have created a full CakePHP example of performing AJAX pagination without the pages.  The goal of this article is to display news articles to a user.  As the user scrolls down, we will dynamically load in additional content so they can continue to scroll and read. This example focuses on PHP but if you're looking for Node.js tutorial I've also implemented this exact feature with Node.js as the server-side code.

One of our challenges is to not load too much or too little content.  For more details on this, please review the Creating AJAX Pagination WITHOUT The Pages.  Let's begin.


Start by downloading a fresh copy of CakePHP 2.0 dev edition.  I saved my copy to a folder called cake-2-0 in my htdocs folder allowing me to load: http://localhost/cake-2-0/.  I'm not sure if this issue will be resolved by the time you get your fresh copy, but I immediately see this error:

Fatal error: Class 'Debugger' not found in C:\xampp\htdocs\cake-2-0\cake\libs\view\pages\home.ctp on line 26

To fix this, I had to add the following line in app/config/bootstrap.php:

App::import('Core', 'Debugger');

Reloading now looks a little better.  The next thing we need to do is create a new database and update our database config file.  Create your new database now and rename app/config/database.php.default to database.php.  Open this file and update your information accordingly.

Next, we'll create a new table called "events":

CREATE TABLE `events` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`title` varchar(255) NOT NULL,

`description` text NOT NULL,

`created` datetime NOT NULL,

`modified` datetime NOT NULL,

PRIMARY KEY (`id`)

) ENGINE=MyISAM;

Because I'm lazy (or enjoy saving time - depends how you look at it) I decided to use CakePHP's baking console to create my controller, model, and views.  Below is a very quick overview of what I did:

1. Open a command prompt - Start | Run | cmd | <enter>

2. cd \xampp\htdocs\cake-2-0 <enter>

3. cake\console\cake bake <enter>

4. m <enter> - to bake our model

5. <enter> - use our default database config

6. 1 <enter> - bake our Event model

7. n <enter> - don't need validation for our purposes

8. n <enter> - no relationships needed

9. <enter> - our model looks good and can be baked

10. n <enter> - we don't need unit tests created

11. c <enter> - to bake our controller

12. <enter> - use our default database config

13. 1 <enter> - bake our Events controller

14. n <enter> - don't want to bake it interactively

15. y <enter> - we want to bake our basic view functions (add, edit, delete, index)

16. n <enter> - we don't need admin routing

17 <enter> - looks good, bake it

18. n <enter> - we don't need unit tests

19. v <enter> - bake our views

20. <enter> - default database is good

21. 1 <enter> - bake our event views

22. n <enter> - don't want to bake interactively

23 q <enter> - all done

The above is just to quickly create our model, controller, and views with the most basic functionality to get us started.  Feel free to spend more time here if you wish to add validation, create relationships (perhaps to a user table), etc...

Next, we need to create some Javascript events so we can paginate through them.  Visit the add page now: http://localhost/cake-2-0/events/add.  I created 10 basic events.  You can grab some fake text from http://www.lipsum.com/ to make your descriptions nice and long.  I did 5 paragraphs for each one.

The example below will create an array, if you're looking for information about JavaScript arrays I've compile an entire page dedicated with over 10 different examples that will considerably improve your skills with Javascript arrays.

Now that we have our content, let's alter our controller to only display two events at a time.  Open up app/controllers/events_controller.php and add the following code:


var $paginate = array(
'limit' => 2
);

This should go above our index() function, beneath the var $name = 'Events'.  Now if we reload our page, our basic table should only display two events and 5 page options beneath it.  Let's continue by altering our app/views/events/index.ctp to not display it in a table listing and make it look more like a flow of events.  Below is my basic HTML to do this, feel free to alter as required:


<div id="newsfeed">
<h2><?php echo __('Events');?></h2>
<?php foreach ($events as $event):?>
<h3><?php echo h($event['Event']['title']); ?></h3>
<h4>Posted: <?php echo h($event['Event']['created']); ?></h4>
<p><?php echo nl2br(h($event['Event']['description'])); ?></p>
<hr/>
<?php endforeach; ?>
</div>
<div>
<h3><?php echo __('Actions'); ?></h3>
<ul>
<li><?php echo $this->Html->link(__('New Event'), array('action' => 'add')); ?></li>
</ul>
</div>

We now have a basic event listings page.  Next, we need to download Jquery and save it to app/webroot/js/jquery.js.  Let's continue editing our index.ctp and add our basic Javascript to perform the loading of our content when we scroll down.  This is mostly taken from my previous post with a few basic additions:


<?php echo $this->Html->script('jquery', false);?>
<script type="text/javascript">
$(window).scroll(function () {
$.get('events/index/page:2', function(data) {
$('#newsfeed').append(data);
});
});
</script>

If you reload your events list and scroll down, you will see that we are now constantly loading page 2 of our events with each scroll.  This is definitely not optimal.  We don't need new content with every scroll.  Let's alter our Javascript to only load new content every 300 pixels:


<script type="text/javascript">
var lastX = 0;
var currentX = 0;
$(window).scroll(function () {
currentX = $(window).scrollTop();
if (currentX - lastX > 300) {
lastX = currentX;
$.get('events/index/page:2', function(data) {
$('#newsfeed').append(data);
});
}
});
</script>

That's a bit better.  However, you may notice that our full layout is being included every single time we scroll down.  Let's fix this up by adding the following in our events controller:


var $components = array('RequestHandler');

This can go below our previous paginate code, still above our index() function.  Next, let's improve our Javascript to keep track of our pages and load the next page each time we need it.  I found that 300 pixels was a bit too much, so I lowered it to 200 pixels in the code below:


<script type="text/javascript">
var lastX = 0;
var currentX = 0;
var page = 1;
$(window).scroll(function () {
currentX = $(window).scrollTop();
if (currentX - lastX > 200 * page) {
lastX = currentX;
page++;
$.get('events/index/page:' + page, function(data) {
$('#newsfeed').append(data);
});
}
});
</script>

If we reload and try again, something isn't quite working right.  Even though we are incrementing the page number, it's still loading page 2 each time.  The reason for this is our entire page is being returned in our Ajax including our Javascript causing it to restart over and over again and always use page 2.  What we want to do in this scenario is to not return our Javascript in our Ajax call.  I'm not sure if this is the best way to do, but it was the simplest I found.  In your events controller, inside of our index() function, add the following line of code after we set our events:


$this->set('isAjax', $this->RequestHandler->isAjax());

Now in our view, we need to not return our Javascript if it is being called through AJAX:


<?php if (!$isAjax):?>
<?php echo $this->Html->script('jquery', false);?>
<script type="text/javascript">
var lastX = 0;
var currentX = 0;
var page = 1;
$(window).scroll(function () {
currentX = $(window).scrollTop();
if (currentX - lastX > 200 * page) {
lastX = currentX;
page++;
$.get('events/index/page:' + page, function(data) {
$('#newsfeed').append(data);
});
}
});
</script>
<?php endif;?>

That is much better.  Our pages are being properly loaded each time.  We can even scroll up and down and extra pages are not being requested because of this!

Uh oh, what happens if we scroll down past 5 pages though?  It appears CakePHP is pulling the same last two results over and over again!  To solve this, we need to determine our max pages and ensure we don't perform any more AJAX if our page count exceed this value.  Let's update our index.ctp again:


<?php if (!$isAjax):?>
<?php
echo $this->Html->script('jquery', false);
$maxPage = $this->Paginator->counter('%pages%');
?>
<script type="text/javascript">
var lastX = 0;
var currentX = 0;
var page = 1;
$(window).scroll(function () {
if (page < <?php echo $maxPage;?>) {
currentX = $(window).scrollTop();
if (currentX - lastX > 200 * page) {
lastX = currentX;
page++;
$.get('events/index/page:' + page, function(data) {
$('#newsfeed').append(data);
});
}
}
});
</script>
<?php endif;?>

In the above code we are determining the "maxPages" by using the Paginator counter() function to tell us how many pages there are.  We then update our Javascript to ensure our Javascript "page" variable is less than our max pages.  If it's not, stop loading additional pages through AJAX.

We almost have a completely working example.  One final thing we should do, purely for aestics is to exclude a bit more data from being displayed when we are loading our events via AJAX.  Below is my final code for the index.ctp file that hides a bit more content when our view is being loaded via AJAX:


<?php if (!$isAjax):?><div id="newsfeed">
<h2><?php echo __('Events');?></h2><?php endif;?>
<?php foreach ($events as $event):?>
<h3><?php echo h($event['Event']['title']); ?></h3>
<h4>Posted: <?php echo h($event['Event']['created']); ?></h4>
<p><?php echo nl2br(h($event['Event']['description'])); ?></p>
<hr/>
<?php endforeach; ?>
<?php if (!$isAjax):?></div><?php endif;?>
<?php if (!$isAjax):?>
<div>
<h3><?php echo __('Actions'); ?></h3>
<ul>
<li><?php echo $this->Html->link(__('New Event'), array('action' => 'add')); ?></li>
</ul>
</div>
<?php
echo $this->Html->script('jquery', false);
$maxPage = $this->Paginator->counter('%pages%');
?>
<script type="text/javascript">
var lastX = 0;
var currentX = 0;
var page = 1;
$(window).scroll(function () {
if (page < <?php echo $maxPage;?>) {
currentX = $(window).scrollTop();
if (currentX - lastX > 200 * page) {
lastX = currentX;
page++;
$.get('events/index/page:' + page, function(data) {
$('#newsfeed').append(data);
});
}
}
});
</script>
<?php endif;?>

Enjoy!

Published on Feb 28, 2011

Tags: AJAX | CakePHP Tutorial | pagination | jQuery Tutorial

Related Posts

Did you enjoy this article? If you did here are some more articles that I thought you will enjoy as they are very similar to the article that you just finished reading.

Tutorials

Learn how to code in HTML, CSS, JavaScript, Python, Ruby, PHP, Java, C#, SQL, and more.

No matter the programming language you're looking to learn, I've hopefully compiled an incredible set of tutorials for you to learn; whether you are beginner or an expert, there is something for everyone to learn. Each topic I go in-depth and provide many examples throughout. I can't wait for you to dig in and improve your skillset with any of the tutorials below.