0

I'm looping through custom taxonomy terms to create a WP_Query for each term, then displaying the posts on the front-end.

I'm also attempting to adapt this helpful tutorial(and accompanying comments) to add an ajax load more button to each loop iteration. This is what I believe distinguishes this issue from other posts on WP-StackExchange: adding a load more button to a looped query, rather than a single query or simply multiple hard-coded(non-looped) WP_Query's on one page.

The original tutorial uses wp_localize_script to pass php data to a registered & enqueued jQuery script, but since I need unique variables created with each loop, it didn't appear that I could use it(and the tutorial author himself says not to use wp_localize_script if using a custom WP_Query). The comments on that post also allude to the use of unique variables for multiple queries, but I couldn't figure it out for looped queries.

The button that is included in each loop iteration:

// don't display the button if there are not enough posts
if (  $unique_query->max_num_pages > 1 )
    echo '<button class="load-more-btn">Load more</button>';

I realize that the following code is not as concise as it could be yet(hence why I'm here), but I'm including in each foreach'd query the same jQuery so I can create unique query vars for each loop (ie. current_page_<?php print $cat_string; ?>):

The jQuery included at the end of each loop iteration:

$cat_string = the current taxonomy term string - ie. los_angeles

<script>
var ajaxurl = "<?php print site_url() . '/wp-admin/admin-ajax.php'; ?>",
var posts_<?php print $cat_string; ?> = '<?php echo serialize( $unique_query->query_vars ) ?>',
current_page_<?php print $cat_string; ?> = <?php echo $unique_query->query_vars['paged'] ?>,
max_page_<?php print $cat_string; ?> = <?php echo $unique_query->max_num_pages ?>

jQuery(function($){
    $('.load-more-btn').click(function(){
        var button = $(this),
        data = {
            'action': 'loadmore',
            'query': posts_<?php print $cat_string; ?>,
            'page' : current_page_<?php print $cat_string; ?>
        };

        $.ajax({
            url : ajaxurl, // AJAX handler
            data : data,
            type : 'POST',
            beforeSend : function ( xhr ) {
                button.text('Loading...');
            },
            success : function( data ){
                if( data ) { 
                    button.text( 'Load more' ).prev().before(data); // insert new posts
                    current_page_<?php print $cat_string; ?>++;

                    if ( current_page_<?php print $cat_string; ?> == max_page_<?php print $cat_string; ?> ) 
                        button.remove(); // if last page, remove the button
                } else {

                    /* This seems to be where it jumps currently when the button is clicked */

                    button.remove(); // if no data, remove the button as well
                }
            }
        });
    });
});
</script>

The ajax action loadmore is hooked in my functions.php like so:

function bos_loadmore_ajax_handler(){

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

    // it is always better to use WP_Query but not here
    query_posts( $args );

    if( have_posts() ) :

        // run the loop
        while( have_posts() ): the_post();

            get_template_part( 'template-parts/event', 'small' ); 

        endwhile;

    endif;
    die; // here we exit the script and even no wp_reset_query() required!
}

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

The unique query vars are instantiating and populating correctly—I can see the correct output for each tax term loop in the Inspector.

The "Load More" button is shown correctly since the query has 26 posts and is showing 20, but when I click the button it changes to "Loading..." then disappears. From some console.log's, it seems to jump right to the else branch for "no data found", so I'm thinking the hooked action in my functions file isn't getting the correct query vars to run the query. Does that seem correct?

I can't figure out, though, how the query vars should be different to get the correct query.

And to be thorough, the WP_Query $args for each looped query are:

$paged = ( get_query_var('page') ) ? get_query_var('page') : 1;
$args = array(
    'post_type'              => array( 'tribe_events' ),
    'post_status'            => array( 'publish' ),
    'posts_per_page'         => 20,
    'nopaging'               => false,
    'paged'                  => $paged,
    'tax_query'              => array(
        array(
            'taxonomy'         => 'tribe_events_cat',
            'field'            => 'name',
            'terms'            => $the_cat,
        ),
    ),
);

Any help is much appreciated.

3
  • look what append with the Network Monitor of your browser. If you use Firefox, you can show it with Ctrl + Shift + E developer.mozilla.org/en-US/docs/Tools/Network_Monitor – Kaperto Dec 26 '19 at 20:33
  • @Kaperto Sorry for the delay in my reply. The Network tab in Inspector shows a 200 status on the call to admin-ajax.php—when I click the "Load More" button. Seems to be calling that file fine, that's why I still think it's something in the $args of my 'bos_loadmore_ajax_handler' function. – dmoz Jan 7 '20 at 16:07
  • I finally figured this out after some awesome hints from another post. Posting my final code in an answer below. – dmoz Jan 7 '20 at 22:27
0

I finally found some helpful information in this post: How to "Load More" posts via AJAX?

The issue ended up being a combination of counting the number of total pages of posts, and sharing that number with the hooked ajax function in functions.php.

Here's my page template loop:

<div class="events">

    <?php
    $socal_cats = array( 'Los Angeles', 'San Diego', 'Las Vegas' );

    foreach( $socal_cats as $the_cat ) {
        $cat_slug = str_replace(' ', '-', strtolower($the_cat));
        $cat_string = str_replace('-', '_', $cat_slug);
    ?>

    <div id="<?php echo $cat_slug; ?>-events">
        <header>
            <h2><?php echo $the_cat; ?></h2>
        </header>
    <div class="events-list">

    <?php
    // WP_Query arguments
    $posts_per_page = 20;
    $paged_main = ( get_query_var('page') ) ? get_query_var('page') : 1;
    $args = array(
        'post_type'              => array( 'tribe_events' ),
        'post_status'            => array( 'publish' ),
        'posts_per_page'         => $posts_per_page,
        'paged'                  => $paged_main,
        'tax_query'              => array(
            array(
                'taxonomy'         => 'tribe_events_cat',
                'field'            => 'name',
                'terms'            => $the_cat,
            ),
        ),
    );

    // The Query
    $socalquery = new WP_Query( $args );

    // The Loop
    if ( $socalquery->have_posts() ) {

        $total_posts = $socalquery->found_posts;
        $total_pages = ( ceil( $total_posts / $posts_per_page ) + 1 );
    ?>

    <div class="posts-wrap" id="<?php echo $cat_slug; ?>" data-totalpages="<?php echo $total_pages; ?>">

    <?php
    while ( $socalquery->have_posts() ) {
        $socalquery->the_post();
        get_template_part( 'template-parts/event', 'small' );
    }

    // don't display the button if there are not enough posts
    if ( $socalquery->found_posts > $posts_per_page )
        echo '<div class="load-more-wrap"><button class="loadmore"><span class="moretext" data-page="'.$total_pages.'">Load more</span></button></div>';
    ?>
    </div><!-- .posts-wrap -->
    <?php

    } else { 

        get_template_part( 'template-parts/event', 'none' );

    }
    ?>

    </div><!-- .events-list -->
</div><!-- .*category*-events -->

<? } // end foreach ?>

<script type="text/javascript">
// Set starting values which to send from Javascript to WP query function...
var ajaxurl = "<?php echo admin_url( 'admin-ajax.php' ); ?>";

jQuery(function($) {

// When this selector is clicked
$('body').on('click', '.loadmore', function() {

    // Get ID of clicked link parent
    var clicked_cat = $(this).parent().parent().attr('id');

    // Get total pagination pages for this section
    var total_pages_for_section = $('#'+clicked_cat+'.posts-wrap').data().totalpages;
    // alert(total_pages_for_section);

    // Change link text to provide feedback
    $('#'+clicked_cat+'.posts-wrap .load-more-wrap .loadmore .moretext').text('Loading...');
    $('#'+clicked_cat+'.posts-wrap .load-more-wrap .loadmore .moretext').after('<i class="icon fas fa-sync fa-spin"></i>');
    // $('#'+clicked_cat+'.load-more-wrap .loadmore i').attr('class', 'fas fa-cog fa-spin');

    // Pick up data-page attribute from the clicked link (defaults to 2 at load)
    var clicked_page = $('#'+clicked_cat+'.posts-wrap .load-more-wrap .loadmore').find('span').data().page;

    // 1. Send this package of variables to WP query function
    var data = {
        'action': 'loadmore',
        'page': clicked_page, // page of posts to get is the number set in data-page attribute
        'clicked_cat': clicked_cat,
        'security': '<?php echo wp_create_nonce("load_more_posts"); ?>'
    };

    // 2. Send to query function and get results
    $.post(ajaxurl, data, function(response) {

        // Append the returned output to this selector
        $(response).insertBefore('#'+clicked_cat+'.posts-wrap .load-more-wrap').fadeIn(500);

        // If we have exhausted all post pages, hide the whole "Load More" link
        if (clicked_page >= total_pages_for_section) {
            $('#'+clicked_cat+'.posts-wrap .load-more-wrap').hide();
        // Otherwise, restore it and increment counter
        } else {
            // Change link text back to original
            $('#'+clicked_cat+'.posts-wrap .load-more-wrap .loadmore .moretext').text('Load More');
            $('#'+clicked_cat+'.posts-wrap .load-more-wrap .loadmore').find('svg').remove();
            // Increment "data-page" attribute of clicked link for next click
            $('#'+clicked_cat+'.posts-wrap .load-more-wrap .loadmore').find('span').data().page++;
        }
    });

});

});
</script>

</div><!-- .events -->

And functions.php:

// Events Load More button
function bos_loadmore_ajax_handler(){

    check_ajax_referer('load_more_posts', 'security');

    // 1. Query values are passed from referring page, to Javascript and to this query...
    $paged = $_POST['page'];                  // Passed from page: Which page we are on
    $clicked_cat = $_POST['clicked_cat'];     // ID of the clicked "More" link

    switch ($clicked_cat) {
        case "los-angeles":
            $event_tax_query_term = "los-angeles";
            break;
        case "san-diego":
            $event_tax_query_term = "san-diego";
            break;
        case "las-vegas":
            $event_tax_query_term = "las-vegas";
            break;
        case "bay-area":
            $event_tax_query_term = "bay-area";
            break;
        case "sacramento":
            $event_tax_query_term = "sacramento";
            break;
        case "reno":
            $event_tax_query_term = "reno";
            break;
    }

    // 3. Set query arguments
    $args = array(
        'post_type' => array( 'tribe_events' ),
        'post_status' => 'publish',
        'posts_per_page' => 10,
        'paged' => $paged,
        'tax_query' => array(
            array(
                'taxonomy'         => 'tribe_events_cat',
                'field'            => 'slug',
                'terms'            => $event_tax_query_term,
            ),
        ),
    );

    // 4. Query for posts
    $query = new WP_Query( $args );

    // 5. Send results to Javascript
    if ( $query->have_posts() ) :
        ?>
        <?php while ( $query->have_posts() ) : $query->the_post(); ?>
            <?php get_template_part( 'template-parts/event', 'small' ); ?>
        <?php endwhile; ?>
        <?php
    endif;

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

Hope this helps someone who needs ajax load more on multiple custom WP_Querys on the same page.

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.