Ajax lazyloading posts in WordPress (example with text/template)

in #wordpress6 years ago

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 =)