Ajax Pagination (and applying it to Wordpress)

25 November 2006 Tags  ,

Update: Whoops, somehow I forgot to include a precious precious function. Its in there now, and the download is updated.

One of the 'problems' with a blog, or a forum, or whatever involves lots of data is how to organise it.
The most common way is to break up the data into pages; this is called pagination.
That is, page 1 will have the first X entries, page 2 the next X, etc etc.


This is done dynamically (sure, it could be done manually, if you're crazy), and you've probably seen it more often that you'd thing.
Something like ?p=5 will be in the URL, after you've clicked 'next page' (or 'older entries' in wordpress).
This is a good system, but it requires loading the entire page again.
In the case of forums, you might just be looking at titles, so that could be a lot of overhead (the rest of the HTML) for very little actual information.

One AJAX approach is to click the 'next' button, and have a XHR loads the next 'set' of data in. It either replaces the existing data (poor way to do it - if the data is somewhat similar, it may appear not to of loaded, and you are now at the bottom of the next lot of data, instead of the top), or appends it, and moves the 'next/previous' buttons down. Get K2 have a slider bar which gets around the 'bottom of data' problem.

The trendiest new way to do it, which recently featured on Ajaxian, has been described on occasions as 'lazy pagination' (or at the least, that's what I'm calling it) or 'endless pageless'.
Instead of requiring the user to click any buttons, it loads the data as they scroll.
When they reach the bottom of the current page, an AJAX request is sent, and fetches the next lot of results.
When they reach the end of the new data, rinse and repeat.

The result is a 'longer' page once all the data is loaded, but it feels more natural.
Google's Reader (RSS aggregator) currently employs this, and I think its a fantastic use.
Then I thought, why can't I do the same thing for Wordpress?

What's needed

  1. Javascript "trigger" - triggers would be nice, but its just a periodic check
  2. XHR calls - we'll use Prototype to handle this
  3. "AJAX Backend" - this is the easy bit, take a parameter, output the text we need
  4. Problems

1. Javascript trigger

I lied, its not a trigger as such, just periodical checks to see where the user is in relation to the bottom of the page.

function updatePage()
{
   setTimeout("updatePage()",1000);
}

function init()
{
   //This checks it every 1000ms
   setTimeout("updatePage()",1000);
}
window.onload = init;

Pretty straight forward. Just checks updatePage ever second.
Lets expand on that

function updatePage()
{
   if (updating == false && endOfItems == false)
   {
      if (getScrollHeight() >= (0.9 * document.documentElement.offsetHeight))
      {
         $("loading").style.display = "block";
         new Ajax.Request("/wp-content/plugins/lazyScroller/lazyScrollerAAJAX.php", {method: "get", parameters: "lpn="+loaded,onComplete: appendPage});
         loaded += toLoad;
         updating = true;
      }
      else
         setTimeout("updatePage()",1000);
      }
}

First, is it already updating, or the end of items?
If not, use getScrollHeight() function (see the download files for what this actually is) against the height of the document (well, 90% of the document).
If the current scroll height is equal to or larger than the documents height (or 90% of it in this case) show the 'loading' block (see below), send the AJAX request (see below), increment how many have been loaded, and set updating to true.
Otherwise, set the timeout again.

The onComplete method is appendPage, so lets look at that

function appendPage(val)
{
   $("loading").style.display = "none";
   if (val.responseText == "END")
      endOfItems = true;
   else
   {
      $("content").innerHTML += val.responseText;
      updating = false;
      setTimeout("updatePage()",1000);
   }
}

This hides the loading block, checks if its the end of the items (and sets the right flag), appends the content block, sets updating to false, and starts the timeout again. Pretty simple.

To make things prettier, I threw in a 'loading' element.

<div id="loading">Loading...</div>

is placed at the bottom of the page, and I styled it with

#loading { position: absolute; bottom:0px; display:none; right:0px; background: #0000; border:1px solid #cacaca;}

2. XHR Calls

Although covered in the above section, I'll break it down a little here.
Prototype's AJAX.REQUEST syntax:

new Ajax.Request('<target>',{<options>});

We've used it like so:

new Ajax.Request("/wp-content/plugins/lazyScroller/lazyScrollerAAJAX.php", {method: "get", parameters: "lpn="+loaded,onComplete: appendPage});

It calls the target via get, with the parameter "lpn=loaded" (where loaded is the offset to start from). The target is the backend (below) which returns the remaining posts.
The onComplete function is what adds the data to the page.

3. AJAX Backend

This is pretty darn easy, the 'hard' bit is getting the template specific code.
You'll need to open up your index.php in your template folder, and get the template specific code for the post.
If you don't, the posts received from the lazy pagination won't look like the rest of your posts.

<?
require('../../../wp-blog-header.php');

$posts = get_posts("numberposts=10&offset=".$_GET['lpn']);
if( $posts )
{
   foreach( $posts as $post )
   {
      setup_postdata( $post );

      //Template specific stuff goes here:
      ?>
      <div class="post" id="post-<?php the_ID(); ?>">
      <div class="header">
         <div class="date"><em>by</em> <?php the_author() ?> <br/><em>on</em> <?php the_time('M jS, Y') ?></div>
         <h3><a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title(); ?>"><?php the_title(); ?></a></h3>
      </div>
      <div class="entry">
         <?php the_content('Continue Reading »'); ?>
      </div>
      <div class="footer">
         <ul>
            <li class="readmore"><?php the_category(' , ') ?> <?php edit_post_link('Edit'); ?></li>
            <li class="comments"><?php comments_popup_link('Comments(0)', 'Comments(1)', 'Comments(%)'); ?></li>
         </ul>
      </div>
      </div>
		<?

      //:Template specific stuff ends here
   }
} else print "END";

?>

4. Problems

Problem the first involves DIV's.
In some cases, it makes sense to use overflow:auto or overflow:scroll on the style of an element (DIV/Spans' primarily), which can synthesise a frame (or more particularly, IFrame).
The page doesn't scroll, the element does.
This is easily solved, by converting document.scrollTop to $('elementID').scrollTop (assuming Prototype, of course)

The other 'major' problem is the template in use.
Not all templates have a "content" element, which would break this plugin. There isn't much I can do about it (while still maintaining decent DOM structure), so you'll have to edit it either your template or this plugin if you don't have an element with an ID of content.

Conclusion

That explains the basics of it - rather simple, but it was a fun exercise to explain it.

Download

Comments

2 Comments

  1. Josef says:

    Hi, I am trying to implement this excellent plugin on my wordpress blog, however i am having some trouble, i am trying to contact you to ask you whether you can have a once over of my code and see what i have done wrong, i followed your instructions as closely as possible but im getting no response from the functions. Hmm please help! :) My URL is http://www.burninthespotlight.com the plugin is installed and activated you can see in my source. Please contact me via my email address which i added to this post. Many thanks. I have a DIV with ID content around all my posts.

    Josef

  2. Paul says:

    I checked out your site, it is firing away well actually.
    It doesn't work because I'm an idiot.

    For some reason, I decided to get rid of a very important function.

    Add the below code to the script, or just download a new copy (I'll send this via email to you too)

    			function _getWindowHeight()
    			{
    				if (self.innerWidth)
    				{
    					frameWidth = self.innerWidth;
    					frameHeight = self.innerHeight;
    				} else if (document.documentElement && document.documentElement.clientWidth) {
    					frameWidth = document.documentElement.clientWidth;
    					frameHeight = document.documentElement.clientHeight;
    				} else if (document.body) {
    					frameWidth = document.body.clientWidth;
    					frameHeight = document.body.clientHeight;
    				}
    				return parseInt(frameHeight);
    			}
    

Trackbacks / Pingbacks

Comments are closed