admin管理员组

文章数量:1023838

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

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 -->
Share Improve this question edited Mar 29, 2019 at 16:29 user5854648 asked Mar 7, 2019 at 16:22 user5854648user5854648 16317 bronze badges 1
  • You should not pass unfiltered args directly to WP_Query. What happens when a user opens the devtools and runs $.ajax({ url : misha_loadmore_params.ajaxurl, data: { post_status: 'private' } }) ? They can read all your private posts. What if you have WooCommerce and they use { post_type: 'shop_order', post_status: 'any' }? Never trust user input. – Lachlan Arthur Commented Apr 14, 2020 at 1:55
Add a comment  | 

2 Answers 2

Reset to default 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?

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.

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

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 -->
Share Improve this question edited Mar 29, 2019 at 16:29 user5854648 asked Mar 7, 2019 at 16:22 user5854648user5854648 16317 bronze badges 1
  • You should not pass unfiltered args directly to WP_Query. What happens when a user opens the devtools and runs $.ajax({ url : misha_loadmore_params.ajaxurl, data: { post_status: 'private' } }) ? They can read all your private posts. What if you have WooCommerce and they use { post_type: 'shop_order', post_status: 'any' }? Never trust user input. – Lachlan Arthur Commented Apr 14, 2020 at 1:55
Add a comment  | 

2 Answers 2

Reset to default 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?

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.

本文标签: postsThis AJAX Code Doesn39t WorkLooking for elegant solution