Loading WordPress Posts Dynamically With AJAX
This technique allows us to refresh “Like” counters, add items to a shopping cart, create dynamic forms and much more – all without reloading the page.
In this post, I’ll show you how to load posts in place with AJAX using the Twenty Fifteen default theme as our base.
We’ll look at why AJAX is used and, starting with a simple example, building AJAX loading functionality into Twenty Fifteen.
Note: If you run into any issues trying to set up AJAX on your site, we can help! Our support team is available 24/7 to help you with any WordPress issue (not just questions about our own plugins!), so whether you’re having problems with AJAX or want some advice on how to do CSS tweaks, get in touch!
Why Use AJAX?
As you can see in the image above (taken from Chrome Developer Tools), a fair number of assets are loaded. There is room for optimization here and some assets like scripts will be cached, but even then it is a lot.
When page two of our posts is loaded it all happens again. WordPress retrieves the posts and displays them using our markup. It also loads all the outlying elements of the page all over again. In many cases (but not all) this is a waste of bandwidth and detrimental to user experience. After all, no one likes waiting around for pages to load.
Getting Started: Creating a Child Theme
Before we modify Twenty Fifteen we should create a child theme. This ensures we can continue updating the theme without losing our changes. You can read all about how to do in in out guide How to Create a WordPress Child Theme.
Let’s begin with a simple example that demonstrates how AJAX Works. We’ll target the links inside the pagination strip at the bottom of the page so that when you click on a page number it will dynamically load that page using AJAX. When a pagination link is clicked we will send a request to the server and alert the result.
I created a
js folder and a
ajax-pagination.js file in it. Once you have done the same, open your functions file and add the script to the already existing
If you’re confused about enqueuing read our article on adding scripts and styles to WordPress the right way. In a nutshell, we’ve told WordPress what we’d like to call our script (parameter one), where it is located (parameter two), what the pre-requisites are (parameter three), the version number (parameter four) and that we’d like to load it in the footer (parameter five).
Note that when enqueueing the stylesheet I used
get_template_directory_uri(). This function always points to the directory of the parent theme. When enqueueing our script I used
get_stylesheet_directory_uri(). This points to the child theme’s directory if used within a child theme.
Since we’re loading the script in the footer you can paste
alert( 'Script Is Enqueued' ) into
ajax-pagination.js and reload the page to check if it works. If it does, the text should be alerted properly.
Creating an Event
The next task is to create an event which will trigger an AJAX call. In our case the event is the clicking of a specific link. To target the link we’ll need to find out a bit about the element classes and IDs around it.
In case you’re wondering how I got this to show up, I pressed Shift + Command + C on my Mac (Shift + Control + C on Windows), hovered over the element I wanted to inspect and clicked it.
This tells me that our pagination links have the class
page-numbers, the next link also has the class of
next and these are all contained in a
nav element with the class of
nav-links. Not shown here is the previous link, which has the class of
prev in addition to the regular
For the time being, let’s not worry about all that, let’s just target any link within the pagination container. We can create a simple alert like this:
Note that everything is wrapped in an anonymous function. I recommend you do the same. Take a look at this thread on why this is helpful. I’ve created a click event, prevented the default functionality from firing (i.e. loading the page) and I’ve alerted some text.
Creating an AJAX Call
By adding this code inside the
my_enqueue_assets() function we will have defined the
ajaxpagination object (parameter 2). The object will receive its members according to the array supplied as the third parameter in the
wp_localize_script() function. In other words, once we’ve added this code we should be able to use
ajaxpagination.ajaxurl to define the URL to the
admin-ajax.php which we use to handle AJAX calls.
As you can see the
$.ajax() function is what we’re using here. There are special functions for post and get methods but I prefer using this function because of its flexibility. You can read about all the parameters in the jQuery Documentation.
url parameter we pass the URL of the script we want to send data to. This should be the
admin-ajax.php file which can be found in the
wp-admin directory. We defined this above via the
type is set to
post. We could also use
get, our query is not too sensitive but I prefer to post data unless the user needs access to the parameters.
data parameter is an object which contains the data you want to pass. In our case I will be able to access a
$_POST['action'] variable, the value of which would be
ajax_pagination. You can pass as many as your application requires of course.
success parameter is a function which alerts the result of our AJAX call. We’ll make this a bit fancier below, for now this is sufficient for testing. If you try clicking on a link now it actually works but won’t be very useful since we haven’t defined the server side code. In fact, what you should see alerted is
So why does this happen? When I said “we haven’t defined server side code,” I wasn’t entirely truthful. We haven’t, but WordPress has. There is some content in the
admin-ajax.php file we are using. If you take a look at the source code of that file you should see that the script uses
die( '0' ) in a couple of cases.
1.6 million WordPress Superheroes read and trust our blog. Join them and get daily posts delivered to your inbox - free!
If we don’t supply an action the
admin-ajax.php script dies and returns 0. If we do supply an action but we don’t hook into the required WordPress hooks nothing happens and at the very end of the file we die again, returning 0. In conclusion we are already communicating with the server.
Communicating With WordPress
To get a meaningful answer from WordPress we need to define some WordPress actions. This is done using a set pattern. Let’s dive in by continuing our example in the functions file of our theme:
I’ve hooked a function to two hooks. Hooks that take on the
wp_ajax_[action_name] format are only executed for logged in users. Hooks that take on the
wp_ajax_norpiv_[action_name] format are only executed for non-logged in users. The great benefit of this is that you can very easily separate functionality.
action: 'ajax_pagination') – they must match. The function name can be anything you like, I used
my_ajax_pagination for clarity.
The final step is to use
die(). If we don’t define this, the die function defined in
admin-ajax.php at the very end of the file will kick in and you will end up echoing
0 in addition to whatever else you are echoing. If you try out the code above you should now see the title of your website returned.
That concludes our basic example! Before we move on to pulling in posts via AJAX, let’s quickly recap the steps necessary to perform an AJAX action:
wp_localize_script()to pass the URL of your
- Hook a function using the appropriate hook name
Loading Posts With AJAX
This is much the same as our basic example. The first thing you’ll notice is that I’ve added a way to detect which page the user wants to request. Each link has a
span element in it which is hidden (it is there for screen readers). I make a clone of the element to make sure I don’t modify the original, remove the span and parse the remainder as an integer. This gives us the page number we need.
I will also need to know the query parameters used. On the main page this is pretty simple, it’s just the
paged parameter since we’re working with the default query. If we start off on an archive page (like a category archive) we’ll need to know the category name as well.
We’ll pass the query variables using the localization method we learned earlier. For now we’ll use
ajaxpagination.query_vars even though this is not yet defined. Finally, on success we remove all
article elements from the main container, we remove the pagination element and append the return value of our AJAX call to the main container.
This return value will contain the posts and the new navigation element. Note that I’ve changed the name of the parameter from
html because it makes a bit more sense. To finish up we use the localization array to pass the original query parameters.
The following function should be placed in our
my_enqueue_assets() function replacing the localization we had earlier:
All we need to do now is flesh out the
my_ajax_pagination() function. Whatever this function echoes will replace the content on our page. Here’s the final code with an explanation below:
Using our passed parameters we build a custom query. This involves basically taking the query variables we passed and making sure that the page number we passed overwrites the
paged parameter. We then use our final
$query_vars array to create a new query.
We need to make the
$GLOBALS['wp_query'] variable equal to our new posts object. The reason we need to do this is that the
the_posts_pagination() function uses this global variable.
Next, notice that I’ve added a function to the
editor_max_image_size filter and a few rows down I remove it. This was something unexpected that came up. I actually created a WordPress Trac Ticket. We may see some progress on it! Here’s the issue:
When images are loaded in the post they all look just fine. However, if you complete this tutorial without these filters your images will be narrower, only 660px wide instead of the necessary 825px. The reason for this is that the function that loads the images eventually calls a function named
image_constrain_size_for_editor(). This function makes sure that images in the post editor aren’t too wide. To determine weather this size reduction should take place it uses the
is_admin() function. Since our code runs through
admin-ajax.php which technically is in the admin, WordPress scales our images down, mistakenly thinking we are using them in the editor.
Luckily we can use the
editor_max_image_size filter to determine the maximum size for the editor. Since we want to leave everything as is, except for during our AJAX call we add the filter using our custom values (
array( 825, 510 )) and then immediately remove it just to make sure it doesn’t cause trouble anywhere else.
The next step is to use our query to list our posts. I copied a lot from the
index.php file in the parent theme. if there are no posts we use the template which is meant to handle that, otherwise we loop through the posts and use the post display template. Finally we use the same pagination format we see in the index file.
A Note About AJAX Calls
It’s important to remember that AJAX calls are always considered to originate from the admin. What this means is that draft, scheduled and private posts may be returned with this call. If you don’t want this to happen, you’ll need to control the behaviour with appropriate parameters such as
Better User Experience
With AJAX solutions like this, it is extremely important to focus on user experience. I’m working in a local environment so everything loads really quickly, but on a production server images and other assets may take more time to load.
Due to this you should at least add a loader or loading text and disable further clicks on the navigation elements. We will take care of these by making the posts and the navigation disappear right after the user clicks and displaying the text “loading new posts.” When the success event fires we remove the loading text and display the posts. Here’s our updated AJAX call:
We now have a separate
success function. The former is performed as soon as you click the link, before the AJAX call is sent to the server. The later is performed once we receive data back from the server.
Before the call is sent we remove the articles and the navigation. This makes sure users can’t keep clicking on navigation links while they’re waiting for things to load. Next we scroll to the top of the document. Then, we append a loading notification to make it clear to users what’s going on. I’ve used the same markup as Twenty Fifteen uses on post-not-found pages. In the success function we remove the loader and load our content, all done!
AJAX is extremely powerful; apart from loading posts you can perform all sorts of actions via AJAX calls. There are quite a number of dangers and things to look out for while using it, here are a few:
Safety can be a major concern. If you want to delete a post via AJAX you need to make sure the user has the intent and the permission (using nonces), for example. When using regular methods WordPress has built-in protections in some cases but with AJAX you usually have to think of this on your own.
User experience is very frequently overlooked. AJAX functionality is indeed cool, but a reliably working website is cooler. Users are used to pages loading when they click links. You need to make everything very transparent, users should know what is going on and why. You should use AJAX to enhance your work, not to bring as much bling as you can to the table.
As you can see, implementing AJAX requires a bit of preparation and practice but once it’s second nature you’ll find that it comes easily. It probably took you a while to read through this and it will take even more time to do it for the first time, but I coded the whole example in about 15 minutes.
AJAX is one of those techniques which can be difficult because it encompasses almost all programming languages used in a framework like WordPress. Things are spiced up further by having to adhere to conventions like hooks and localization.
Practice makes perfect. I guarantee you’ll fall in love AJAX if you start to use it.
Have you implemented AJAX on your site? What other uses are there for AJAX in WordPress? Let us know what you think in the comment below.