1

I am trying to utilize AJAX on my WordPress site for posts as a user scrolls. I followed this tutorial but it has no effect on my WordPress website.

I have used a plugin that works, but I'd much rather learn how to do this without a plugin both to learn and also to avoid using unnecessary plugins. The below code does not work, instead all of my posts show up on the page despite posts per page being set to 5.

Help is appreciated!

functions.php

function misha_my_load_more_scripts() {

    global $wp_query; 
    wp_enqueue_script('jquery');
    wp_register_script( 'my_loadmore', get_stylesheet_directory_uri() . '/js/myloadmore.js', array('jquery') );

    wp_localize_script( 'my_loadmore', 'misha_loadmore_params', array(
        'ajaxurl' => site_url() . '/wp-admin/admin-ajax.php',
        'posts' => json_encode( $wp_query->query_vars ), 
        'current_page' => get_query_var( 'paged' ) ? get_query_var('paged') : 1,
        'max_page' => $wp_query->max_num_pages
    ) );

    wp_enqueue_script( 'my_loadmore' );
}

add_action( 'wp_enqueue_scripts', 'misha_my_load_more_scripts' );

function misha_loadmore_ajax_handler(){

      $args = array(
      'cat' => -21,
      'post_type' => 'post',
      'posts_per_page' => 5,
      'paged' => 1,
      'tax_query'      => array(
        array(
            'taxonomy' => 'topics',
            'operator' => 'NOT EXISTS'
        )
    )

    );
      $args = json_decode( stripslashes( $_POST['query'] ), true );
      $args['paged'] = $_POST['page'] + 1; //need next page to be loaded
      $args['post_status'] = 'publish';

    $the_query = new WP_Query ( $args ); 

    if($the_query->have_posts()) : 
      while($the_query->have_posts()) : 
         $the_query->the_post();

    get_template_part( 'template-parts/post/content', get_post_format() );

        endwhile;

    endif;
    die; 
}

add_action('wp_ajax_loadmore', 'misha_loadmore_ajax_handler'); // wp_ajax_{action}
add_action('wp_ajax_nopriv_loadmore', 'misha_loadmore_ajax_handler'); // wp_ajax_nopriv_{action}

myloadmore.js

jQuery(function($){
    var canBeLoaded = true, 
        bottomOffset = 2000; //I've played with this number to see if it was the offset calling posts too soon but it has no effect 

    $(window).scroll(function(){
        var data = {
            'action': 'loadmore',
            'query': misha_loadmore_params.posts,
            'page' : misha_loadmore_params.current_page
        };
        if( $(document).scrollTop() > ( $(document).height() - bottomOffset ) && canBeLoaded == true ){
            $.ajax({
                url : misha_loadmore_params.ajaxurl,
                data:data,
                type:'POST',
                beforeSend: function( xhr ){
                    canBeLoaded = false; 
                },
                success:function(data){
                    if( data ) {
                        $('#main').find('div:last-of-type').after( data ); 
                        canBeLoaded = true; 
                        misha_loadmore_params.current_page++;
                    }
                }
            });
        }
    });
});

content.php

<div class="row">
 <div class="col-sm-6">
    <?php $image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'single-post-thumbnail' ); ?>
    <a href="<?php the_permalink(); ?>"><img src="<?php echo $image[0]; ?>" class="img-fluid"></a>
  </div>

  <div class="col-sm-6">

    <?php
      $categories = get_the_category();
        if ( $categories ) :
            $deepChild = get_deep_child_category( $categories );
    ?>
    <p><?php echo $deepChild->name; ?></p>

      <?php endif; ?>

      <h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>

  </div>
</div><!-- END ROW-->

front-page.php

<div id="main" class="container-fluid">
  <?php misha_loadmore_ajax_handler(); ?>
</div><!-- END CONTAINER -->

UPDATED CODE

This following is updated code based on answers. I am posting this because it is still not working and I may be overlooking/misunderstanding something and I want to understand where I have gone wrong.

functions.php

function misha_my_load_more_scripts() {
    wp_register_script( 'my_loadmore', get_stylesheet_directory_uri() . '/js/myloadmore.js',
        array( 'jquery' ), '', true );
    wp_enqueue_script( 'my_loadmore' );
}
add_action( 'wp_enqueue_scripts', 'misha_my_load_more_scripts' );

function misha_loadmore_ajax_handler() {
    $args = json_decode( wp_unslash( $_POST['query'] ), true );
    $args['paged'] = $_POST['page'] + 1; // load the next page

    $the_query = new WP_Query( $args );

    if ( $the_query->have_posts() ) :
        while ( $the_query->have_posts() ) : $the_query->the_post();
            get_template_part( 'template-parts/content', get_post_format() );
        endwhile;
    endif;

    wp_die();
}
add_action( 'wp_ajax_loadmore', 'misha_loadmore_ajax_handler' );        // Authenticated users
add_action( 'wp_ajax_nopriv_loadmore', 'misha_loadmore_ajax_handler' ); // Non-authenticated users

myloadmore.js

jQuery(function($){
    var canBeLoaded = true, // this param allows to initiate the AJAX call only if necessary
        // the distance (in px) from the page bottom when you want to load more posts,
        bottomOffset = ( $( '#main > div.post:last' ).offset() || {} ).top;

    $(window).scroll(function(){
        if ( misha_loadmore_params.current_page >= misha_loadmore_params.max_page ) {
//          console.log( 'max_page reached; AJAX canceled' );
            return; // we've already reached the last page, so let's do no more AJAX.
        }
        var data = {
            'action': 'loadmore',
            'query': misha_loadmore_params.posts,
            'page' : misha_loadmore_params.current_page
        };
        if( $(document).scrollTop() > ( $(document).height() - bottomOffset ) && canBeLoaded == true ){
            $.ajax({
                url : misha_loadmore_params.ajaxurl,
                data: data,
                type: 'POST',
                beforeSend: function( xhr ){
                    // you can also add your own preloader here
                    // you see, the AJAX call is in process, we shouldn't run it again until complete
                    canBeLoaded = false;
                },
                success:function(data){
                    if( data ) {
                        $('#main').find('div.post:last-of-type').after( data ); // where to insert posts
                        canBeLoaded = true; // the ajax is completed, now we can run it again
                        misha_loadmore_params.current_page++;

                        bottomOffset = ( $( '#main > div.post:last' ).offset() || {} ).top
                    }
                }
            });
        }
    });
});

content.php

<div class="row post">
 <div class="col-sm-6">
    <?php $image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'single-post-thumbnail' ); ?>
    <a href="<?php the_permalink(); ?>"><img src="<?php echo $image[0]; ?>" class="img-fluid"></a>
  </div>

  <div class="col-sm-6">

    <?php
      $categories = get_the_category();
        if ( $categories ) :
            $deepChild = get_deep_child_category( $categories );
    ?>
    <p><?php echo $deepChild->name; ?></p>

      <?php endif; ?>

      <h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>

  </div>
</div><!-- END ROW-->

front-page.php

 <?php
  $current_page = max( 1, get_query_var( 'paged' ) );
  $the_query = new WP_Query( array(
    'cat'            => '-21',
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'paged'          => $current_page,
  ) );

  wp_localize_script( 'my_loadmore', 'misha_loadmore_params', array(
    'ajaxurl'      => admin_url( 'admin-ajax.php', 'relative' ),
    'posts'        => json_encode( $the_query->query_vars ),
    'current_page' => $current_page,
    'max_page'     => $the_query->max_num_pages
  ) );
?>
  <div id="main" class="container-fluid">
    <?php
      if ( $the_query->have_posts() ) :
          while ( $the_query->have_posts() ) : $the_query->the_post();
              // Should match the one in misha_loadmore_ajax_handler().
              get_template_part( 'template-parts/content', get_post_format() );
          endwhile;
      endif;
    ?>
  </div>

<?php wp_reset_postdata(); ?>

</div><!-- END CONTAINER -->
1

Working Code Based On My Previous Answer

Without using nonce.. but you can check the previous answer on how you can implement a nonce check. And the code is based on your code.

misha_my_load_more_scripts() in functions.php

function misha_my_load_more_scripts() {
    wp_register_script( 'my_loadmore', get_stylesheet_directory_uri() . '/js/myloadmore.js',
        array( 'jquery' ), '', true );
    wp_enqueue_script( 'my_loadmore' );
}
add_action( 'wp_enqueue_scripts', 'misha_my_load_more_scripts' );

misha_loadmore_ajax_handler() in functions.php

function misha_loadmore_ajax_handler() {
    $args = json_decode( wp_unslash( $_POST['query'] ), true );
    $args['paged'] = $_POST['page'] + 1; // load the next page

    $the_query = new WP_Query( $args );

    if ( $the_query->have_posts() ) :
        while ( $the_query->have_posts() ) : $the_query->the_post();
            get_template_part( 'template-parts/post/content', get_post_format() );
        endwhile;
    endif;

    wp_die();
}
add_action( 'wp_ajax_loadmore', 'misha_loadmore_ajax_handler' );        // Authenticated users
add_action( 'wp_ajax_nopriv_loadmore', 'misha_loadmore_ajax_handler' ); // Non-authenticated users

front-page.php (the #main DIV)

$current_page = max( 1, get_query_var( 'paged' ) );
$the_query = new WP_Query( array(
    'cat'            => '-21',
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'paged'          => $current_page,
) );

wp_localize_script( 'my_loadmore', 'misha_loadmore_params', array(
    'ajaxurl'      => admin_url( 'admin-ajax.php', 'relative' ),
    'posts'        => json_encode( $the_query->query_vars ),
    'current_page' => $current_page,
    'max_page'     => $the_query->max_num_pages
) );
?>
    <div id="main" class="container-fluid">
        <?php
            if ( $the_query->have_posts() ) :
                while ( $the_query->have_posts() ) : $the_query->the_post();
                    // Should match the one in misha_loadmore_ajax_handler().
                    get_template_part( 'template-parts/post/content', get_post_format() );
                endwhile;
            endif;
        ?>
    </div>
<?php
wp_reset_postdata();

myloadmore.js and content.php

No changes.

UPDATE

Actually, in my code (the one I actually tested with), I don't have the tax_query parameter, but I mistakenly included it in the above code in the previous version of this answer (not the other one on this page).

Because a tax_query like the following — which doesn't specify the required terms parameter — would result in an 0 = 1 in the MySQL query, and eventually leads to no results (i.e. no posts):

$the_query = new WP_Query( array(
    ...
    'tax_query'      => array(
        array(
            'taxonomy' => 'topics',
            'operator' => 'NOT EXISTS',
            // missing the required 'terms' parameter
        ),
    ),
) );

So make certain to use tax_query with the proper parameters.

And you may also want to use/check my myloadmore.js script?

  • This code doesn't return any errors but none of the posts load. – user5854648 Mar 21 at 19:21
  • Have you tried inspecting the XHR request - in Chrome, Ctrl + Shift + I and go to the Network -> XHR tab. Click on the request name and check its Response. – Sally CJ Mar 21 at 22:56
  • The XHR tab has a name of admin-ajax.php and it's response: "This request has no response data available" Under Headers it has a green dot with a Status Code of 200 OK. I have updated my question with the new code under my original code which may help find if I am doing something wrong. Thank you for your help! – user5854648 Mar 22 at 18:03
  • Sorry, I should've not included the tax_query in my answer. Nonetheless, check the updated answer. Hopefully this time it works.. – Sally CJ Mar 23 at 5:22
  • Have you checked the update? Did it work? – Sally CJ Mar 27 at 23:02
2

There are several issues with your code, such as:

  1. The original code relies upon the global $wp_query object; see the global $wp_query; in the misha_my_load_more_scripts() function. But your code is using a custom WP_Query instance, which is $the_query and which is used in the misha_loadmore_ajax_handler() function.

  2. The misha_loadmore_ajax_handler() is an AJAX handler/callback and hence it shouldn't be called from div#main like so:

    <div id="main" class="container-fluid">
      <?php misha_loadmore_ajax_handler(); ?>
    </div><!-- END CONTAINER -->
    

So the original code does work; however, for custom WP_Query requests such as the $the_query in your case, you'd need to put/define the AJAX JS variables after you make the WP_Query request.

And here's how you could do that:

  1. First off, I'm using the default code (JS/PHP) as provided on the page, and I'm using the "load posts on scroll (lazy load)" script/option.

  2. Secondly, this is my misha_my_load_more_scripts() function:

    function misha_my_load_more_scripts() {
        // register our main script but do not enqueue it yet
        wp_register_script( 'my_loadmore', 'URL/to/the/load-more-script', array('jquery') );
    
        wp_enqueue_script( 'my_loadmore' );
    }
    add_action( 'wp_enqueue_scripts', 'misha_my_load_more_scripts' );
    
  3. And my "main" div (the posts container):

    $current_page = max( 1, get_query_var( 'paged' ) );
    $the_query = new WP_Query([
        'posts_per_page' => 5,
        'paged'          => $current_page,
    ]);
    ?>
        <div id="main">
            <?php while ( $the_query->have_posts() ) : $the_query->the_post();
                // This should match the one in misha_loadmore_ajax_handler().
                get_template_part( 'template-parts/post/content', get_post_format() );
            endwhile; ?>
        </div>
        <script>
            var misha_loadmore_params = {
                ajaxurl: '<?php echo admin_url( 'admin-ajax.php', 'relative' ); ?>',
                posts: '<?php echo json_encode( $the_query->query_vars ); ?>',
                current_page: <?php echo $current_page; ?>,
                max_page: <?php echo $the_query->max_num_pages; ?>
            };
        </script>
    <?php
    wp_reset_postdata();
    
  4. Those are the only changes I made and the original misha_loadmore_ajax_handler() code (on that page) can be used as it is.

I have tried and tested the/my code, and it is working as expected. And although I didn't use your code, I believe you can easily implement my code with your code/concept.

UPDATE

If you'd like to use wp_localize_script() (and it might be better particularly if you wish to include translated text in the JS object/data), then you need to enqueue the load-more script in the footer:

wp_register_script( 'my_loadmore', 'URL/to/the/load-more-script', array('jquery'),
  'version', true );

And then in the "main" div, remove the <script>...</script> and add this before the wp_reset_postdata():

wp_localize_script( 'my_loadmore', 'misha_loadmore_params', [
    'ajaxurl'      => admin_url( 'admin-ajax.php', 'relative' ),
    'posts'        => json_encode( $the_query->query_vars ),
    'current_page' => $current_page,
    'max_page'     => $the_query->max_num_pages,
    // These two are optional.
    'my_text'      => __( 'My text', 'text-domain' ),
    'security'     => wp_create_nonce( 'my-load-more-posts' ),
] );

And as you can see, I've included one translation there: My text (misha_loadmore_params.my_text).

Using a Nonce

In the above localized data, I've also included a nonce (security), which is specifically useful when your AJAX PHP handler is making some write operations — e.g. updating a metadata.

And you can send it to the PHP handler like so:

// This is set in the load-more script.
var data = {
  'action': 'loadmore',
  'query': misha_loadmore_params.posts,
  'page' : misha_loadmore_params.current_page,
  'security': misha_loadmore_params.security
};

And then in the PHP handler, you can do so to verify the nonce:

function misha_loadmore_ajax_handler(){
    check_ajax_referer( 'my-load-more-posts', 'security' );

    ...
    wp_die();
}

PS: This is my load-more script, with only one difference compared to the original one, which is the misha_loadmore_params.security part.

  • 1
    an improvement could be creating a nonce and using wp_localize_script() to set the ajax_url and the nonce for the ajax call – alo Malbarez Mar 7 at 19:42
  • 1
    Thanks @aloMalbarez. I've updated the answer. – Sally CJ Mar 8 at 1:53
  • @SallyCJ Sorry for getting back to this late - I was side tracked. I am following your code but I receive a 400 (Bad Request) when calling for admin-ajax.php. I have checked my wp-admin folder and admin-ajax is certainly there. Unfortunately I cannot seem to test this code until this is resolved. This happened with both the original code you provided and the Update. – user5854648 Mar 19 at 21:41
  • @user5854648, I've posted a new answer.. try the code and if it works, you can accept the answer.. ;) – Sally CJ Mar 20 at 11:26
  • @SallyCJ could you specify where the PHP handler goes (the last block of code)? I tried putting it as the first line of code in the misha_loadmore_ajax_handler() function in the functions.php (the same functions.php you have in your answer). I'm also not sure where the 'my-load-more-posts' is referring to. Right now it's loading the first 5 posts but nothing else is entering upon scroll. If it sounds like I'm doing it right please let me know if I should provide my current code. – user5854648 Mar 21 at 19:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.