Ajax lazyload posts in WordPress (example with text/template)
Intro
In this post I like to show you a way to implement an ajax lazyload button on your WordPress based website. As i needed it for a project I'm working on, i felt it is a good idea to recap my work and share it with other people. The goal of this post is, to deliver an easy and understandable way to do a layzloading in WordPress for everyone, either beginner or pro.
It's a good way to improve my english aswell as english is not my main language.
What can you learn:
- PHP
- Write an ajax-route in php
- Query posts (with a given offset)
- Return statuscodes and messages
- JavaScript (jQuery)
- Do an ajxa call
- Use a text/template script tag to append your response
- Add an eventlistener to an element
In this example we assume, we're on an overview-page. I like to put my posts into cards, half the grid of your framework of choice (i.E. Bootstrap or Foundation), to show them on the page but it's up to you how to style your page.
Let's say we take a value of 20 posts for an initial load at the first visit. It's up to you to decide when the search should be triggered. Play around with scroll top or onclick events as you prefere.
The theoretic/assumed overview page
Let's take a container like the example below where allready a list of 20 posts are rendered.
Notice the data-result-container attribute on the overview-container element. We'll need this attribute to select the target where we append the newly loaded posts.
You should consider to check if there are enought posts in reserve to trigger the lazyloading. If there are no more posts to load, we should let the visitor know about and shouldn't trigger the ajax call.
Set the data-offset attributes on the initial load to the count of your already loaded posts. This offset helps our query to get the next posts after the ones, already loaded and displayed in frontend.
After every ajax call, make sure you update the data-offset attribute to the new offset (shown posts + new loaded posts).
Later on, you can handle different elements which, for example, represents a counter (shown posts / total posts) with this data-offset attribute.
Here is an example of a base HTML for the overview:
<main class="main-content">
<section class="overview">
<header class="overview-header">
<h1 class="overview-title">Overview</h1>
</header>
<div class="overview-container" data-result-container data-offset="20">
/*
Initial 20 post-articles here
maby in a grid like "colum medium-6" for foundation or "col-md-6" for bootstrap
*/
</div>
<footer class="overview-footer">
<a class="button" href="#" data-load-posts>Click me for more posts!</a>
/*
You can add the text/template tag here if you like,
it wont be displayed in frontend
*/
</footer>
</section>
</main>
Text/template
The text/template tag will be used to create new post entries for our overview. You should load this template somewhere in the section footer to keep the section elements together as mentioned above.
The idea of a text/template tag is, to load an invisible template to the DOM. The fact, that this template is not visible for the casual wesite visitor, allows us to select this template with JavaScript and replace the placeholders with data recieved from our ajax call, described later in this post.
I prefere to put my placeholders in doubled curly brackets {{}} as you will see in the template below. You are absolutley free in choosing the placeholders look or the text/template content.
Notice the data-post-template attribute. We'll use this later to select it with JavaScript
HTML template for <script type="text/template">
<script type="text/template" data-post-template>
<article class="article">
<header class="article-header">
<h2 class="article-title">{{post-title}}</h2>
</header>
<main class="article-main">
<div class="article-excerpt">
{{post-excerpt}}
</div>
</main>
<footer class="article-footer">
<a class="post-link" href="{{post-link}}">{{post-link-text}}</a>
</footer>
</article>
</script>
The ajax route
Add the following code to your functions.php -> only as long as you'r trying to get your dedicated goal. Afterwards you should outsource it to a more logical place (think of object oriented programming). The fact, that we'll use the HTTP nounce POST to send the data to the server, we can access this data with the $_POST varibale.
Stay sure that your ajax calls always return a status. I know it's cool if your call works but please tell everyone that your call succeeded or failed. It's easyer to handle errors afterwards aswell as for future developement to debug your code.
Don't forget to add the function to the ajax action hooks as described below in the code.
Ajax route in PHP
// this function will get executed for every ajax call
function lazyLoadPosts()
{
// prevent tampering with our data-attribute
if(isset($_POST['offset']) && $_POST['offset'] !== '' && $_POST['offset'] !== 'NaN' && !is_array($_POST['offset']))
{
$offset = $_POST['offset'];
} else {
// return 400 Bad Request -> we can't proceed
wp_send_json_error('Offset not defined', 400);
exit;
}
// setup the query arguments
// we assume 20 more posts to load -> decide for your own how many posts should be loaded
$args = array(
'numberposts' => 20, // default 5 / -1 for infinite
'orderby' => 'menu_order', // change this to your needs (most customers like to handle the order for their own)
'offset' => $offset // the magic happens here
);
// query the posts
$posts = get_posts($args);
if(!empty($posts)){
// add some custom values
foreach($posts as $post) {
// get the link to the post
$post->post_link = get_permalink($post->ID);
// add text to the link for frontend output (can be an options field which is defined in backend if you like)
$post->post_link_text = 'Read more';
}
// success -> send data back
wp_send_json_success([
'message' => 'Posts successfully loaded',
'posts' => $posts
], 200);
exit;
} else {
// return 404 Not found -> no posts loaded
wp_send_json_error('No posts loaded', 404);
exit;
}
}
// finally we can add this function with add_action
add_action('wp_ajax_lazyLoadPosts', 'lazyLoadPosts');
add_action('wp_ajax_nopriv_lazyLoadPosts', 'lazyLoadPosts');
The ajax call
Now, that we have the setup to create our ajax call in JavaScript, -> let's do it.
First we define our event which will trigger the ajax call. In this case it is an onclick event on a button inside our section footer, described above in the theoretic/assumed overview page section of this post.
Add the eventlistener
// wait until DOM is loaded
jQuery('document').ready(function() {
// listen for click events on our button
// first params are events
// second is a selector (in this case it points to our button with the data-attribute data-load-posts)
// third param is our callback function where we decide what to do after our events where triggered
jQuery('[data-load-posts]').on('click touchstart', function(event) {
// prevent window scrolling to the top on click on the button
event.preventDefault();
// get the offset
var offset = parseInt(jQuery('[data-offset]').attr('data-offset'));
// check if we have a number
if(!isNaN(offset)) {
// get the posts with ajax
loadMorePosts(offset);
}
});
});
Ajax call with jQuery
As soon as our defined event is triggered, we do a request to the server. The parameter needed are the HTTP method for accessing the data on the server, The URL where to send the request, the datatype we expect as a response, the action to call on the server and of course the offset from where we want to get the new posts.
function loadMorePosts(offset) {
jQuery.ajax({
method: 'POST', // HTTP Method
url: '/wp-admin/admin-ajax.php', // WordPress ajax url
dataType: "json",
data: {
'action': 'lazyLoadPosts', // our function definied in php
'offset': offset // the offset -> remember to adjust this value after a successful call
},
success: function(response) {
// here we define what happens on a successful call
// in our case we take the text/template, replace the placeholders and append the created HTML to the result-container
// finally we update the data-offset attributes aswell
parseCards(response.data.posts);
},
error: function() {
// add your error handling here
}
});
}
The Templating function
On a successfull call, we can then finally add the responded data from our request to our result-container. We get this running by first create an empty string as a result variable. Step two is, to get the text/template as a HTML-pattern. Afterwards we take this template and replace the placeholders with the dedicated values and append this to our result string (postListHTML). Last but not least, we append this string as HTML to our reslut container.
function parseCards(posts)
{
// prepare an empty string for the output
var postListHTML ='';
// get the text/template of a post
var postTemplate = jQuery('[data-post-template]').html();
// count of posts retrieved from call
// will be used to update the data-offset attributes
var postsLoaded = posts.length;
// check if posts are loaded
if(postsLoaded > 0 )
{
// take the post template and replace the placeholders with the right values
for (var index = 0; index < postsLoaded; index++)
{
postListHTML += postTemplate.replace(/{{post-title}}/g, posts[index].post_title)
.replace(/{{post-excerpt}}/g, posts[index].post_excerpt)
.replace(/{{post-link}}/g, posts[index].post_link)
.replace(/{{post-link-text}}/g, posts[index].post_link_text)
}
// append the newly generated posts to the result-container
jQuery('[data-result-container]').append(postListHTML);
// set the new offset value
var actualOffset = parseInt(jQuery('[data-offset]').attr('data-offset'));
jQuery('[data-offset]').attr('data-offset', actualOffset + postsLoaded);
}
}
Conclusion
So, with this code examples you should be able to implement lazyload functionality to your WordPress site. Well this was my goal but it's up to you guys to let me know if i reached this goal or not.
Things you should consider:
- Depending on the initial load of posts you prefere -> adjust the data-offset attributes
- Depending on the count of posts loaded by each call -> adjust this in your lazyLoadPosts function
- If there are not enought posts on your page to fit the values -> posts never gets loaded
- Add a timeout or something to your click event trigger to prevent hardcore clickers loading the **** out of your page
- Add an empty state to your button if all posts are shown. Always let your visitors know about whats happening. It's even more important if nothing happens;)
I had a lot of fun to write this post and I hope you guys have fun while reading the result aswell.
Cheers =)