AJAX Load Posts

Today, we’re going to replace the standard “Older Posts” links on your blog. Our plugin will create a button to instantly load the next page of posts, without reloading the page (Similar to what Twitter used to do at the bottom of profiles)

Click here to load the demo site and see the final project in action.

You can also download the completed files as a plugin here (Just upload and activate it).

Now, let’s get started. Our plugin is going to have several features:

  • Multiple clicks – First clicks will load page 2′s posts, second will load page 3 etc.)
  • Check for posts first – If there are no more posts that can be loaded, we’ll tell the user.
  • Degrade gracefully – If a visitor doesn’t use JavaScript, we won’t change the site at all.

Plugin Structure

There will be 3 files in this plugin (One PHP, one CSS, one JS). For good practice, we’ll keep the CSS and JavaScript in their own folders.

And I’ve called the plugin “pbd-ajax-load-posts”.

File Structure

Now let’s start by putting the necessities at the top of our pbd-ajax-load-posts.php file:

1
2
3
4
5
6
7
8
9
10
<?php
/**
 * Plugin Name: PBD AJAX Load Posts
 * Plugin URI: http://www.problogdesign.com/
 * Description: Load the next page of posts with AJAX.
 * Version: 0.1
 * Author: Pro Blog Design
 * Author URI: http://www.problogdesign.com/
 * License: GPLv2
 */

All good so far? Cool, time to get started for real.

Loading Files and Passing Values

The first thing we need to do is ensure that our JavaScript and CSS files are loaded on the right pages. Stay in the pbd-ajax-load-posts.php file and paste this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 /**
  * Initialization. Add our script if needed on this page.
  */
 function pbd_alp_init() {
 	global $wp_query;
 
 	// Add code to index pages.
 	if( !is_singular() ) {	
 		// Queue JS and CSS
 		wp_enqueue_script(
 			'pbd-alp-load-posts',
 			plugin_dir_url( __FILE__ ) . 'js/load-posts.js',
 			array('jquery'),
 			'1.0',
 			true
 		);
 
 		wp_enqueue_style(
 			'pbd-alp-style',
 			plugin_dir_url( __FILE__ ) . 'css/style.css',
 			false,
 			'1.0',
 			'all'
 		);

The code above begins by creating a new function, pbd_alp_init() (alp = AJAX Load Posts), which we will then hook into place later on.

You’ll notice that we also call the global $wp_query variable, which we’ll use in the next step.

The important parts begin on line 8. The first statement is a conditional statement. It means that on any page that isn’t an individual post or Page, we are going to run this code.

This is a broad brush to make sure our code runs on the homepage, tag pages, search pages etc. You can adapt it to be more specific, e.g. if you don’t want the code included on your homepage.

We then use wp_enqueue_script() and wp_enqueue_style() to tell WordPress about our two files (As well as the fact that we will be using jQuery).

Now, we need to pass some values to our script, namely:

  • The page number we’re on right now (Going to page 1 99% of the time, but let’s be sure).
  • The total number of pages (So we know when we’ve hit the limit).
  • The link to the next page (e.g. site.com/tag/example/page/2/)

We will use the wp_localize_script() function to calculate each of these values in PHP, and then print them into the webpage so that our script can access them later (Hat tip to Professional WordPress Plugin Development for introducing me to this great function!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// What page are we on? And what is the pages limit?
$max = $wp_query->max_num_pages;
$paged = ( get_query_var('paged') > 1 ) ? get_query_var('paged') : 1;
 
// Add some parameters for the JS.
wp_localize_script(
	'pbd-alp-load-posts',
	'pbd_alp',
	array(
		'startPage' => $paged,
		'maxPages' => $max,
		'nextLink' => next_posts($max, false)
	)
);

We begin by working out the first 2 values. $max is the maximum number of pages the current query can return (e.g. if each page shows 5 posts and there are 12 posts in the current category, then max will be 3).

The $paged variable will store the page we are currently on (The whole point of our plugin is to ensure people never load a second page, but it doesn’t hurt to make sure).

If you skip down to line 12, you’ll see where I’ve worked out the 3rd value (The link to the next page). next_posts() is a built-in WordPress function that will return the URL we need.

The wp_localize_script() function is great because it makes it easy to pass values from PHP to JavaScript. The first value, ‘pbd-alp-load-posts’ must match the first value in the wp_enqueue_script() call.

The second value, ‘pbd_alp’, is the name we will use in our JavaScript later on.

Finally, we send over an array of the data. If you view the HTML source of your webpage later, you’ll see something like this right before your JavaScript file is loaded:

1
2
3
4
5
6
7
8
9
<script type='text/javascript'>
/* <![CDATA[ */
var pbd_alp = {
	startPage: "1",
	maxPages: "6",
	nextLink: "http://www.problogdesign.com/demo/ajax-load-posts/page/2/"
};
/* ]]> */
</script>

Now, we just need to close up our if statement, our pbd_alp_init() function, and then hook it all into place.

1
2
3
 	}
 }
 add_action('template_redirect', 'pbd_alp_init');

We use the template_redirect hook because with the init hook, the $wp_query variable won’t be set yet.

jQuery – The Heart of our Plugin

We have now loaded our scripts and passed the values we need. It’s time to get to the real meat of our tutorial.

Open the load-posts.js file. The first thing we do is access the 3 variables we passed in with our PHP function.

1
2
3
4
5
6
7
8
9
10
jQuery(document).ready(function($) {
 
	// The number of the next page to load (/page/x/).
	var pageNum = parseInt(pbd_alp.startPage) + 1;
 
	// The maximum number of pages the current query can return.
	var max = parseInt(pbd_alp.maxPages);
 
	// The link of the next page of posts.
	var nextLink = pbd_alp.nextLink;

The way to access our values is to use the format: pbd_alp.valueName (pbd_alp was the second value we entered in wp_localize_script(), remember?).

The important thing to remember is that our numbers have been sent over as strings, so we use JavaScript’s parseInt() function to convert them back to numbers.

With pageNum, we add one to the number because it is going to store the number of the next page to load (Not the current page).

Most themes already have navigation to move between pages, in the form of Older Posts/Newer Posts links. We want to replace that with our AJAX button, so our first step will be to remove those navigation links, and insert our button instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Replace the traditional navigation with our own,
 * but only if there is at least one page of new posts to load.
 */
if(pageNum <= max) {
	// Insert the "More Posts" link.
	$('#content')
		.append('<div class="pbd-alp-placeholder-'+ pageNum +'"></div>')
		.append('<p id="pbd-alp-load-posts"><a href="#">Load More Posts</a></p>');
 
	// Remove the traditional navigation.
	$('.navigation').remove();
}

We start out with a conditional check. Remember that pageNum is the number of the next page, so if it is greater than max, there are no more pages to load. In that case, we don’t want to add the button.

If there is new content to load though, then we look for the #content div, and add two things to the end of it. The first is an empty div, which we will later use to insert our posts into.

The second is the button itself (A regular HTML link), wrapped up in a paragraph.

Finally, we look for the .navigation div and remove it. If your theme uses a different class for the navigation buttons, you will need to change that (.navigation is the default that the 2010 theme uses). The same thing applies to the #content div too!

The result of the code above is that our button is now in place, though it won’t do anything yet.

And because we did it all with JavaScript, we know our plugin degrades gracefully (Because if the JavaScript isn’t loaded, no changes will be made to the page).

Now, let’s take care of what happens when the user actually clicks the button.

1
2
3
4
5
6
7
8
9
10
/**
 * Load new posts when the link is clicked.
 */
$('#pbd-alp-load-posts a').click(function() {
 
	// Are there more posts to load?
	if(pageNum <= max) {
 
		// Show that we're working.
		$(this).text('Loading posts...');

The first line of code is a jQuery event handler, which runs when a user clicks the button.

On line 7, we run the same check as before. This is important because our script would load in a 404 error message if there were no more posts. Definitely not what we want!

Line 10 updates the text on our button to read “Loading posts…” This is good practice because users will get an instant reaction when they click the button.

The next step is to make the AJAX call. Quite a lot happens here, so copy and paste the following into your script and I’ll walk through it all afterwards.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$('.pbd-alp-placeholder-'+ pageNum).load(nextLink + ' .post',
	function() {
		// Update page number and nextLink.
		pageNum++;
		nextLink = nextLink.replace(/\/page\/[0-9]?/, '/page/'+ pageNum);
 
		// Add a new placeholder, for when user clicks again.
		$('#pbd-alp-load-posts')
			.before('<div class="pbd-alp-placeholder-'+ pageNum +'"></div>')
 
		// Update the button message.
		if(pageNum <= max) {
			$('#pbd-alp-load-posts a').text('Load More Posts');
		} else {
			$('#pbd-alp-load-posts a').text('No more posts to load.');
		}
	}
);

The first line is the most important. We use a jQuery selector to select our placeholder div. The reason we have added the pageNum number to the end of the class name is so that if the user clicks the button again, we’ll add the new posts to a new placeholder (Not overwrite the old one).

The .load() function loads the URL we pass (Remember that nextLink is the URL for the next page), and because we have added .post to the end, it will only copy over the .post divs that it finds (Not the whole page!)

On line 2, we start a new function which will run when the AJAX call completes. The first thing it does is update our values for the next time the button is clicked.

pageNum is increased by one (To point to the new next page), and nextLink is updated using a regular expression. This searches the URL for /page/2/ (Or any number), and replaces the number portion with the new next page number.

On line 8, we add a new placeholder div. This will be used the next time the button is clicked.

Finally, on line 12, we update the text on the button again. If there are more posts that can be loaded, we revert back to the original text. If there aren’t, then we’ll update with a message saying so.

Now, we just need to round things off:

1
2
3
4
5
6
7
		} else {
			$('#pbd-alp-load-posts a').append('.');
		}	
 
		return false;
	});
});

This code begins by closing off the first if-statement (Are there more pages to load?). If there aren’t, it adds a ‘.’ to the button’s message. This is simply to give some sort of visual response to the user when the button is clicked (Look at the screenshot below to see what happens).

No More Posts

And last of all, we use return false; to prevent the link from the button itself loading.

Style It

Your button is now fully working! The only thing remaining is to style it. You can do this however you like with CSS. I’ve used CSS3 to round off the corners, and add a gradient and drop shadow.

You can look these up more online (Or post in the comments if you’d like to see some CSS3 tuts here), but a big thanks is due to CSS Tricks for making the cross-browser gradient code easy.

Add this to your css/style.css file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#pbd-alp-load-posts a:link, #pbd-alp-load-posts a:visited {
	display: block;
	text-align: center;
	padding: 4px 0;
	color: #444;
	text-decoration: none;
 
	/** Rounded Corners **/
	-moz-border-radius: 8px;
	border-radius: 8px;
 
	/** Drop shadow **/
	-moz-box-shadow: 1px 1px 1px #999;
	-webkit-box-shadow: 1px 1px 1px #999;
	box-shadow: 1px 1px 1px #999;
 
	/** Gradients : http://css-tricks.com/css3-gradients/ */
	/* fallback */
	background-color: #f1f1f1;
 
	/* Firefox 3.6+ */
	background: -moz-linear-gradient(100% 100% 90deg, #e4e3e3, #f1f1f1);
 
	/* Safari 4-5, Chrome 1-9 */
	/* -webkit-gradient(<type>, <point> [, <radius>]?, <point> [, <radius>]? [, <stop>]*) */
	background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f1f1f1), to(#e4e3e3));
 
	/* Safari 5.1+, Chrome 10+ */
	background: -webkit-linear-gradient(#f1f1f1, #e4e3e3);
 
	/* Opera 11.10+ */ background: -o-linear-gradient(#f1f1f1, #e4e3e3);
}
 
#pbd-alp-load-posts a:hover, #pbd-alp-load-posts a:active {
	/** Drop shadow **/
	-moz-box-shadow: 1px 1px 1px #bbb;
	-webkit-box-shadow: 1px 1px 1px #bbb;
	box-shadow: 1px 1px 1px #bbb;
 
	/** Gradients : http://css-tricks.com/css3-gradients/ */
	/* fallback */
	background-color: #f5f5f5;
 
	/* Firefox 3.6+ */
	background: -moz-linear-gradient(100% 100% 90deg, #eaeaea, #f5f5f5);
 
	/* Safari 4-5, Chrome 1-9 */
	/* -webkit-gradient(<type>, <point> [, <radius>]?, <point> [, <radius>]? [, <stop>]*) */
	background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f5f5f5), to(#eaeaea));
 
	/* Safari 5.1+, Chrome 10+ */
	background: -webkit-linear-gradient(#f1f1f1, #eaeaea);
 
	/* Opera 11.10+ */ background: -o-linear-gradient(#f5f5f5, #eaeaea);
}

And that’s you done! Just save your work and activate the plugin.

If you have any issues, check that your theme uses the #content and .navigation divs in the way that we assumed. Most themes will, but not all. If yours is different, update those values in the JavaScript file.

Let me know what you thought of the effect! And don’t forget you can grab the completed plugin here.

And if you have any feedback on the tutorial, I’d love to hear it! Too long? Or more explanation needed? (The demo site exists now purely because some of you asked for it in previous tutorials, so thank you!)

Enjoy this post? You should follow me on Twitter!