1

On the taxonomy-{name}.php template for my "Organisation" taxonomy, I am currently showing a limited number of posts, grouped for each term of taxonomy "Format".

Where the number of available posts exceeds that which is displayable, I now want to enable AJAX-based loading of more posts.

I have followed countless tutorials and StackExchange questions on this topic, but something is still not clicking. For example, I implemented Misha Rudrastyh's "Load More" button, but the code just would not load any posts. I am lost and on the verge of giving up.

I have a post block with a "More" link like this, ready and waiting...

enter image description here

That corresponds to posts of "Format" taxonomy term "executive" for one taxonomy "Organisation" term. Here is the relevant portion of taxonomy-organisation.php, containing my query...

 <?php


 // This will output posts in blocks for each "Format" taxonomy term.
 // But we also want to use this loop to output posts *without* a "Format" taxonomy term.


 $formats = array("interview","executive","analyst","oped","","ma","earnings","financialanalysis","ipo","marketindicators","industrymoves");

 foreach ($formats as $format) {

      // Term of the slug above
      $format_term = get_term_by('slug', $format, "format");

      // Formulate the secondary tax_query for "format" with some conditionality
      /* *********************************** */
      /*           Posts by Format           */
      /* *********************************** */
      if (!empty($format)) {
           $posts_per_page = 8;
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'field'    => 'slug',
                'terms'    => $format_term->slug,
                'include_children' => false
           );
           // Oh, and set a title for output below
           $section_title = $format_term->name;
      /* *********************************** */
      /*         Format without Format       */
      /* *********************************** */
      } elseif (empty($format)) {
           $posts_per_page = 12;
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'operator' => 'NOT EXISTS', // or 'EXISTS'
           );
           // Oh, and set a title for output below
           $section_title = 'Reporting';
      }

      // Query posts
      $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
      $args = array(
        // pagination
        // 'nopaging' => false,
        'posts_per_page' => $posts_per_page,
        // 'offset' => '4',
        // 'paged' => $paged,
        // posts
        'post_type' => array( 'article', 'viewpoint' ),
        // order
        'orderby' => 'date',
        'order'   => 'DESC',
        // taxonomy
        'tax_query' => array(
          array(
            'taxonomy' => 'company', // from above, whatever this taxonomy is, eg. 'source'
            'field'    => 'slug',
            'terms'    => $organisation->slug,
            'include_children' => false
          ),
          $tax_q_format_array
       ),
      );

      // the query
      $posts_org = new WP_Query($args);



      if ( $posts_org->have_posts() ) { ?>

           <h5 class="mt-0 pt-2 pb-3 text-secondary"><?php echo $section_title; ?> <span class="badge badge-secondary badge-pill"><?php echo $posts_org->found_posts; ?></span></h5>

           <div class="row pb-3">
           <?php
           while( $posts_org->have_posts() ) {
                $posts_org->the_post();
                get_template_part('partials/loops/col', 'vertical');
           }
           if ($posts_org->found_posts > $posts_per_page) {
                echo '<p class="text-secondary pl-3 pb-0 load_more" style="opacity: 0.6"><i class="fas fa-plus-circle"></i> More</p>';
           }
           echo '</div>';
           wp_reset_postdata(); // reset the query

      } else {
           // echo '<div class="col"><p>'.__('Sorry, no posts matched your criteria.').'</p></div>';
      } // end if have_posts


 }










 ?>

And here is the include file used by get_template_part to output post item thumbnails and links...

    <?php
    // Get fallback image if needed
    @require(dirname(__FILE__).'/../source_thumbnail_fallback.php');
    // @ Suppress "Notices" generated when post has no source set (ie no source at array position [0])
    ?>


    <div class="col-sm-6 col-md-4 col-lg-3 col-xl-2 post-item overlay-bg <?php echo $display_class; ?>">
            <a href="<?php the_field( 'source_url' ); ?>" class="text-dark" target="_blank">
                    <img src="http://www.myserver.com/to/image/location/<?php echo $source_image_url; ?>" class="img-fluid rounded mb-2">
                    <p><?php the_title(); ?></p>
                    <?php
                    //Do something if a specific array value exists within a post
                    $format_terms = wp_get_post_terms($post->ID, 'format', array("fields" => "all"));
                                    // foreach($format_terms as $format_single) { ?>
                        <span class="badge <?php get_format_badge_colour_class($format_terms[0]->term_id); ?> font-weight-normal mr-2 overlay-content"><?php echo $format_terms[0]->name; ?></span>
                                    <?php // } ?>
            </a>
    </div>

From the tutorials and questions, I have gleaned that the process seems to be...

  • Register/enqueue/localise a Javascript file containing the relevant code, and pass parameters to it from PHP functions.php
  • Some sort of WordPress AJAX handler in functions.php which does a WP_Query for more posts?

But I am just not understanding how to implement in my situation.

FYI, my self-built theme is based on Bootstrap and, until now, I have de-registered WordPress' built-in jquery, replaced by enqueuing Bootstrap's recommended https://code.jquery.com/jquery-3.3.1.slim.min.js as jQuery. I'm not sure which is the right option, it seemed like this has a bearing on WP_Ajax_*.

I'm aware a similar question has been asked before, but my query seems unique and many questions/answers seem also to refer to unique situations, ie. offering answers specifically designed to work with twentyfifteen theme.

Update (Feb 15, 2019):

I have come a long way toward solving this, using a combination of:

  • Artisans Web tutorial for the basic concept
  • Anti's suggestion for establishing which of multiple "More" links was clicked (necessary to feed unique arguments back to looped WP queries).
  • Further research/exploration based on available code, to learn how to take and pass those variables.
  • Further jQuery research to learn how to change UI elements.

I can now share the following two pieces of code, which works 90% well and I could consider offering this as an answer, or Anti's answer.

Theme template fragment:

 <?php
 $organisation = get_query_var('organisation');
 $org_id_prefixed = get_query_var('org_id_prefixed');
 ?>


 <?php


 /* ************************************************************************************************************** */
 /*                                                                                                                */
 /*          LIST POSTS BY FORMAT TERM, WITH DYNAMIC AJAX MORE-POST LOADING                                        */
 /*                                                                                                                */
 /*          Outline:                                                                                             */
 /*          This will output posts in blocks for each "Format" taxonomy term.                                     */
 /*          But we also want to use this loop to output posts *without* a "Format" taxonomy term.                 */
 /*          >> Method via Sajid @ Artisans Web, https://artisansweb.net/load-wordpress-post-ajax/                 */
 /*                                                                                                                */
 /*          Dynamic:                                                                                              */
 /*          1. Javascript: When "More" link is clicked                                                            */
 /*          2. Send current "organisation", clicked "format" and "page" as variables to function                  */
 /*          3. PHP: Do WP query for next page of posts, return to javascript                                      */
 /*          4. Javascript: Append results to the clicked container                                                */
 /*                                                                                                                */
 /*          Dynamic method:                                                                                       */
 /*          $format used as ID for i) .row .my-posts container and ii) .loadmore link, so that we know:           */
 /*          a) Which "Load more" link was clicked                                                                 */
 /*          b) Which box to return new output to                                                                  */
 /*          >> Help via Antti Koskinen @ StackOverflow, https://wordpress.stackexchange.com/a/328760/39300        */
 /*                                                                                                                */
 /* ************************************************************************************************************** */


 // Specify which "Format" terms we want to display post blocks for
 // ("reporting" here is a wildcard, used to accommodate posts without a "Format" term set)
 $formats = array("interview","executive","analyst","oped","reporting","ma","earnings","financialanalysis","ipo","marketindicators","industrymoves");

 // For each of them,
 foreach ($formats as $format) {

      // 1. Get actual term of the slug above
      $format_term = get_term_by('slug', $format, "format");

      // 2. Formulate the secondary tax_query for "format" with some conditionality
      /* *********************************** */
      /*           Posts by Format?          */
      /* *********************************** */
      if ($format!="reporting") {
           // $posts_per_page = 8;
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'field'    => 'slug',
                'terms'    => $format_term->slug,
                'include_children' => false
           );
           // Oh, and set a title for output below
           $section_title = $format_term->name;
      /* *********************************** */
      /*         Format without Format?      */
      /* *********************************** */
      } elseif ($format=="reporting") {
           // $posts_per_page = 12;
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'operator' => 'NOT EXISTS', // or 'EXISTS'
           );
           // Oh, and set a title for output below
           $section_title = 'Reporting';
      }

      // 3. Set query arguments
      $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
      $args = array(
        // pagination
        // 'nopaging' => false,
        'posts_per_page' => '8', // $posts_per_page,
        // 'offset' => '4',
        'paged' => $paged,
        // posts
        'post_type' => array( 'article', 'viewpoint' ),
        // order
        'orderby' => 'date',
        'order'   => 'DESC',
        // taxonomy
        'tax_query' => array(
          // #1 Organisation: the current one
          array(
            'taxonomy' => 'company', // from above, whatever this taxonomy is, eg. 'source'
            'field'    => 'slug',
            'terms'    => $organisation->slug,
            'include_children' => false
          ),
          // #2 Format: as above
          $tax_q_format_array
        ),
      );

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


      // 5. Output
      if ( $posts_org->have_posts() ) { ?>

           <h5 class="mt-0 pt-4 pb-3 text-secondary"><?php echo $section_title; ?> <span class="badge badge-secondary badge-pill"><?php echo $posts_org->found_posts; ?></span></h5>
           <div class="row pb-0 my-posts" id="<?php echo $format; ?>">
           <?php
           while( $posts_org->have_posts() ) {
                $posts_org->the_post();
                get_template_part('partials/loops/col', 'vertical');
           }
           ?>
           </div>
           <?php
           // wp_reset_postdata(); // reset the query

           // "More" posts link
           if ($posts_org->found_posts > $posts_per_page) {
                echo '<p class="text-secondary pb-0" style="opacity: 0.6"><a href="javascript:;" class="loadmore" id="'.$format.'"><i class="fas fa-plus-circle"></i> <span class="moretext">More</span></a></p>';
           }

      } else {
           // echo '<div class="col"><p>'.__('Sorry, no posts matched your criteria.').'</p></div>';
      } // end if have_posts


 }


 ?>



 <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' ); ?>";
 var page = 2;                                                                // Infer page #2 to start, then increment at end
 var org_slug = "<?php echo $organisation->slug; ?>";                         // Slug of this "organisation" term

 jQuery(function($) {

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

           // Get ID of clicked link (corresponds to original $format value, eg. "executive"/"reporting")
           var clicked_format = $(this).attr('id');

           // Change link text to provide feedback
           $('#'+clicked_format+' .moretext').text('Loading...');
           $('#'+clicked_format+' i').attr('class', 'fas fa-cog fa-spin');

           // 1. Send this package of variables to WP query function
        var data = {
            'action': 'load_posts_by_ajax',
            'page': page,
                'org_slug': org_slug,
                'clicked_format': clicked_format,
            '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).appendTo('div#'+clicked_format).hide().fadeIn(2000); // was: $('div#'+clicked_format).append(response).fadeIn(4000); Reverse method, cf. https://stackoverflow.com/a/6534160/1375163
                // Change link text back to original
                $('#'+clicked_format+' .moretext').text('More');
                $('#'+clicked_format+' i').attr('class', 'fas fa-plus-circle');
                // Increment page for next click
            page++;
        });





    });



 });
 </script>

In functions.php:

  // Called from org_deck2_many.php

  add_action('wp_ajax_load_posts_by_ajax', 'load_posts_by_ajax_callback');
  add_action('wp_ajax_nopriv_load_posts_by_ajax', 'load_posts_by_ajax_callback');

  function load_posts_by_ajax_callback() {
      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
      $org_slug = $_POST['org_slug'];                        // Passed from page: Organisation taxonomy term slug
      $clicked_format = $_POST['clicked_format'];            // ID of the clicked "More" link (corresponds to original $format value, eg. "executive"/"reporting")
      // $tax_q_format_array = $_POST['tax_q_format_array']; // Passed from page: 'Format' term-specifying part for 'tax_query'

      // 2. Formulate the secondary tax_query for "format" with some conditionality
      /* *********************************** */
      /*           Posts by Format?          */
      /* *********************************** */
      if ($clicked_format!="reporting") {
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'field'    => 'slug',
                'terms'    => $clicked_format,
                'include_children' => false
           );
           // $offset = NULL;
      /* *********************************** */
      /*         Format without Format?      */
      /* *********************************** */
      } elseif ($clicked_format=="reporting") {
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'operator' => 'NOT EXISTS', // or 'EXISTS'
           );
           // $offset = '12';      // More articles shown in "Reporting"
      }

      // 3. Set query arguments
      $args = array(
           // posts
          'post_type' => array( 'article', 'viewpoint' ),
          'post_status' => 'publish',
          // 'offset' => $offset,
          // pages
          'posts_per_page' => '8',
          'paged' => $paged,
          // taxonomy
          'tax_query' => array(
            // #1 Organisation: the current one
            array(
             'taxonomy' => 'company', // from above, whatever this taxonomy is, eg. 'source'
             'field'    => 'slug',
             'terms'    => $org_slug,
             'include_children' => false
            ),
            // #2 Format: as above
            $tax_q_format_array
          ),
      );

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

      // 5. Send results to Javascript
      if ( $posts_org->have_posts() ) :
          ?>
          <?php while ( $posts_org->have_posts() ) : $posts_org->the_post(); ?>
              <?php get_template_part('partials/loops/col', 'vertical'); ?>
          <?php endwhile; ?>
          <?php
      endif;

      wp_die();
  }

However, there is a remaining issue that I know about...

There seems to be a Javascript issue with picking up the clicked "More" link's ID as clicked_format and clicking some of the multiple "More" links on the page. I can see this because the first few "More" clicks succeed, but then clicking a different "More" link can leave the process in the "Loading" state.

I suspect it is something to do with when clicked_format gets set and destroyed (or not). I have tried unsetting it, but to no effect.

Should i consider filing a separate, specific question - in either WordPress Development or StackOverflow (Javascript) - for this?

http://recordit.co/AI7OjJUVmH

  • $paged being commented-out wasn't helping. I now have this working artisansweb.net/load-wordpress-post-ajax in basic form, but I have complications in using multiple distinct queries on the page, each of which may need to have "Load more" used. – Robert Andrews Feb 15 at 0:01
  • Hi, I updated my answer with a code example regarding identifying which category should be queried. Hope it helps! – Antti Koskinen Feb 15 at 7:58
  • You can try to debug your script with console.log($('#'+clicked_format+' .moretext')); to see, if jQuery finds the correct button. The debug info will be display in your browser console. Or perhaps jQuery freaks out if the response is empty and causes an js error. – Antti Koskinen Feb 15 at 13:13
  • Great tip. This confirms the correct button is getting pressed. So something else is going on. Hard to know what. Nothing getting written to debug.log. – Robert Andrews Feb 15 at 14:24
  • It's not happening on every click. I can successfully load posts on several occasions. But may clicks don't work - recordit.co/AI7OjJUVmH I can't spot the pattern. – Robert Andrews Feb 15 at 14:39
1

You can use ajax to load more posts on your archive page.

  1. attach a js/jquery click event on the More link
  2. send ajax request to admin-ajax.php (use wp_localize_script to get the ajax url to front end) with page number (track this with js variable).
  3. handle ajax request in php. Add custom ajax actions with add_action( 'wp_ajax_my_action', 'my_action' ); and add_action( 'wp_ajax_nopriv_my_action', 'my_action' ); (for non-logged in users). Send my_action part of the action hook as ajax request action parameter.
  4. query posts. Get the correct page (WP_Query paged arg, commented out in your code) for the query from the ajax request, e.g. $_POST['page']
  5. send queried posts back to front end
  6. append posts from ajax response to the dom with js/jquery.

I don't think it matters that much if you use the default jQuery version or a newer one to load more posts.

Please have a look at the code examples here, https://www.billerickson.net/infinite-scroll-in-wordpress/ (not my blog), for more detailed example and explanation. I know code examples are preferred to links, but the code examples written by Bill Erickson are rather long so I think it is more to convenient post a link insted of copy-pasting the examples here.

You should also have a look at the codex entry about ajax, it is really helpfull. https://codex.wordpress.org/AJAX_in_Plugins

I only took a cursory glance at the query function you posted, but I think it should be fine for the post most part for loading more posts with ajax.


UPDATE 15.2.2019

If you need to identify which "Load more" link/button is clicked you can do this in js/jquery.

// Html
<div id="posts-cat-1" class="posts-section">
// posts here
<button id="cat-name-1" class="load-more">Load more</button>
</div>

<div id="posts-cat-2" class="posts-section">
// posts here
<button class="load-more" data-category="cat-name-2">Load more</button>
</div>

<div id="cat-name-3" class="posts-section">
// posts here
<button class="load-more">Load more</button>
</div>

// jQuery
jQuery('.load-more').on('click',function(event){
// get cat name from id
var catName1 = jQuery(this).attr('id');
// or from data attribute
var catName2 = jQuery(this).attr('data-category'); // or .data('category');
// or from parent section, perhaps not ideal
var catName3 = jQuery(this).closest('.posts-section').attr('id');

// Then pass the catName to your ajax request data so you can identify which category to query in php.
});

UPDATE 16.2.2019

To hide the "Load more" button when there are no more posts to show you can add a simple if-else conditional to your jquery ajax call.

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

  // Check if there's any content in the response.
  // I think your ajax php should return empty string when there's no posts to show
  if ( '' !== response ) {

    // Append the returned output to this selector
    $(response).appendTo('div#'+clicked_format).hide().fadeIn(2000); // was: $('div#'+clicked_format).append(response).fadeIn(4000); Reverse method, cf. https://stackoverflow.com/a/6534160/1375163

    // Change link text back to original
    $('a#'+clicked_format+' i').attr('class', 'fas fa-plus-circle'); // Icon
    $('a#'+clicked_format+' .moretext').text('More');                // Text

    // Increment "data-page" attribute of clicked link for next click
    // page++;
    $('a#'+clicked_format).find('span').data().page++

  } else {

    // This adds display:none to the button, use .remove() to remove the button completely
    $('a#'+clicked_format).hide();

  }

});
  • i) I had already read the Bill Erickson piece and got confused. ii) The one at artisansweb.net/load-wordpress-post-ajax seems easiest, and I have now come a long way... iii) Your observation about my paged being commented-out is helpful. iv) Your suggestions for ways to identify clicked links is very useful. v) I have built this out in to something that virtually works beautifully, except for an issue around the clicked link. I will share the code in an edit... – Robert Andrews Feb 15 at 12:16
  • 1
    That seemed to only work when the link had been clicked for an additional, unnecessary time. I chose to use a "data" attribute again (totally new concept for me) to store the next-page-to-be-pulled inside the corresponding link, then getting it with jQuery. As my edit explains, in PHP I get the total number of post pages expected in this query, based on total posts in the query divided by posts per page. We use jQuery to increment "data-page" at the end of each successful poll. But, if this value ever exceeds the total number of pages of posts in this query, it means we have exhaused all posts – Robert Andrews Feb 16 at 15:31
  • That is actually a neat idea. I need to keep that in mind the next time I'm doing similar ajax stuff myself. – Antti Koskinen Feb 17 at 9:39
1

This has been quite the learning curve.

This solution takes multiple sources:

The method employs:

Javascript: In-line activate on click of "More" link.

In my case, due to using multiple blocks of posts for revealing, the script must have awareness of which link was clicked. So I need to set a named region for the post block and name the link correspondingly.

Additionally, it is then important to increment only the next-page for the specific block whose "More" was clicked. So, we set a "data-page" attribute in the link, starting at the default 2 for page 2. Then we pick up this value and use it as "page". Then we increment this and only this one for next time.

WordPress function in functions.php

Fired through wp_ajax_load_posts_by_ajax and wp_ajax_nopriv_load_posts_by_ajax actions, it virtually replicates the same WP query setup as in my template file, but smartly taking the name of the clicked link in to correspond to a taxonomy term value required in the tax_query array.

Javascript: New results sent back to front-end

The new posts are appended to the posts block. And then the page increment takes place.

Enhancements welcome to the code or this explanation.

Theme template fragment:

 <?php
 $organisation = get_query_var('organisation');
 $org_id_prefixed = get_query_var('org_id_prefixed');
 ?>


 <?php


 /* ************************************************************************************************************** */
 /*                                                                                                                */
 /*          LIST POSTS BY FORMAT TERM, WITH DYNAMIC AJAX MORE-POST LOADING                                        */
 /*                                                                                                                */
 /*          Outline:                                                                                             */
 /*          This will output posts in blocks for each "Format" taxonomy term.                                     */
 /*          But we also want to use this loop to output posts *without* a "Format" taxonomy term.                 */
 /*          >> Method via Sajid @ Artisans Web, https://artisansweb.net/load-wordpress-post-ajax/                 */
 /*                                                                                                                */
 /*          Dynamic:                                                                                              */
 /*          1. Javascript: When "More" link is clicked                                                            */
 /*          2. Send current "organisation", clicked "format" and "page" as variables to function                  */
 /*          3. PHP: Do WP query for next page of posts, return to javascript                                      */
 /*          4. Javascript: Append results to the clicked container                                                */
 /*                                                                                                                */
 /*          Dynamic method:                                                                                       */
 /*          $format used as ID for i) .row .my-posts container and ii) .loadmore link, so that we know:           */
 /*          a) Which "Load more" link was clicked                                                                 */
 /*          b) Which box to return new output to                                                                  */
 /*          c) Employs data-page attribute in link - picks up this and increments page only for this              */
 /*          >> Help via Antti Koskinen @ StackOverflow, https://wordpress.stackexchange.com/a/328760/39300        */
 /*                                                                                                                */
 /* ************************************************************************************************************** */


 // Specify which "Format" terms we want to display post blocks for
 // ("reporting" here is a wildcard, used to accommodate posts without a "Format" term set)
 $formats = array("interview","executive","analyst","oped","reporting","ma","earnings","financialanalysis","ipo","marketindicators","industrymoves");

 // For each of them,
 foreach ($formats as $format) {

      // 1. Get actual term of the slug above
      $format_term = get_term_by('slug', $format, "format");

      // 2. Formulate the secondary tax_query for "format" with some conditionality
      /* *********************************** */
      /*           Posts by Format?          */
      /* *********************************** */
      if ($format!="reporting") {
           $posts_per_page = 8;
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'field'    => 'slug',
                'terms'    => $format_term->slug,
                'include_children' => false
           );
           // Oh, and set a title for output below
           $section_title = $format_term->name;
      /* *********************************** */
      /*         Posts without Format?       */
      /* *********************************** */
      } elseif ($format=="reporting") {
           $posts_per_page = 8;
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'operator' => 'NOT EXISTS', // or 'EXISTS'
           );
           // Oh, and set a title for output below
           $section_title = 'Reporting';
      }

      // 3. Set query arguments
      $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
      $args = array(
        // pagination
        // 'nopaging' => false,
        'posts_per_page' => $posts_per_page,
        // 'offset' => '4',
        'paged' => $paged,
        // posts
        'post_type' => array( 'article', 'viewpoint' ),
        // order
        'orderby' => 'date',
        'order'   => 'DESC',
        // taxonomy
        'tax_query' => array(
          // #1 Organisation: the current one
          array(
            'taxonomy' => 'company', // from above, whatever this taxonomy is, eg. 'source'
            'field'    => 'slug',
            'terms'    => $organisation->slug,
            'include_children' => false
          ),
          // #2 Format: as above
          $tax_q_format_array
        ),
      );

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


      // 5. Output
      if ( $posts_org->have_posts() ) { ?>

           <h5 class="mt-0 pt-4 pb-3 text-secondary"><?php echo $section_title; ?> <span class="badge badge-secondary badge-pill"><?php echo $posts_org->found_posts; ?></span></h5>
           <div class="row pb-0 my-posts" id="<?php echo $format; ?>">
           <?php
           while( $posts_org->have_posts() ) {
                $posts_org->the_post();
                get_template_part('partials/loops/col', 'vertical');
           }
           ?>
           </div>
           <?php
           // wp_reset_postdata(); // reset the query

           // "More" posts link
           if ($posts_org->found_posts > $posts_per_page) {
                echo '<p class="text-secondary pb-0" style="opacity: 0.6"><a href="javascript:;" class="loadmore" id="'.$format.'"><i class="fas fa-plus-circle"></i> <span class="moretext" data-page="2">More</span></a></p>';
           }

      } else {
           // echo '<div class="col"><p>'.__('Sorry, no posts matched your criteria.').'</p></div>';
      } // end if have_posts


 }


 ?>



 <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' ); ?>";
 var page = 2;                                                               // Infer page #2 to start, then increment at end
 var org_slug = "<?php echo $organisation->slug; ?>";                         // Slug of this "organisation" term

 jQuery(function($) {

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

           // Get ID of clicked link (corresponds to original $format value, eg. "executive"/"reporting")
           var clicked_format = $(this).attr('id');

           // Temporarily change clicked-link text to provide feedback
           $('a#'+clicked_format+' i').attr('class', 'fas fa-cog fa-spin');      // Icon
           $('a#'+clicked_format+' .moretext').text('Loading...');               // Text

           // Pick up data-page attribute from the clicked link (defaults to 2 at load)
           var clicked_page = $('a#'+clicked_format).find('span').data().page;
           // console.log(clicked_page);


           // 1. Send this package of variables to WP query function
        var data = {
            'action': 'load_posts_by_ajax',
            'page': clicked_page,
                'org_slug': org_slug,
                'clicked_format': clicked_format,
            '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).appendTo('div#'+clicked_format).hide().fadeIn(2000); // was: $('div#'+clicked_format).append(response).fadeIn(4000); Reverse method, cf. https://stackoverflow.com/a/6534160/1375163

                // Change link text back to original
                $('a#'+clicked_format+' i').attr('class', 'fas fa-plus-circle'); // Icon
                $('a#'+clicked_format+' .moretext').text('More');                // Text

                // Increment "data-page" attribute of clicked link for next click
            // page++;
                $('a#'+clicked_format).find('span').data().page++
        });





    });



 });
 </script>

Function in functions.php:

  // Called from org_deck2_many.php

  add_action('wp_ajax_load_posts_by_ajax', 'load_posts_by_ajax_callback');
  add_action('wp_ajax_nopriv_load_posts_by_ajax', 'load_posts_by_ajax_callback');

  function load_posts_by_ajax_callback() {
      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
      $org_slug = $_POST['org_slug'];                        // Passed from page: Organisation taxonomy term slug
      $clicked_format = $_POST['clicked_format'];            // ID of the clicked "More" link (corresponds to original $format value, eg. "executive"/"reporting")

      // 2. Formulate the secondary tax_query for "format" with some conditionality
      /* *********************************** */
      /*           Posts by Format?          */
      /* *********************************** */
      if ($clicked_format!="reporting") {
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'field'    => 'slug',
                'terms'    => $clicked_format,
                'include_children' => false
           );
           // $offset = NULL;
      /* *********************************** */
      /*         Posts without Format?       */
      /* *********************************** */
      } elseif ($clicked_format=="reporting") {
           $tax_q_format_array = array(
                'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                'operator' => 'NOT EXISTS', // or 'EXISTS'
           );
           // $offset = '12';      // More articles shown in "Reporting"
      }

      // 3. Set query arguments
      $args = array(
           // posts
          'post_type' => array( 'article', 'viewpoint' ),
          'post_status' => 'publish',
          // 'offset' => $offset,
          // pages
          'posts_per_page' => '8',
          'paged' => $paged,
          // taxonomy
          'tax_query' => array(
            // #1 Organisation: the current one
            array(
             'taxonomy' => 'company', // from above, whatever this taxonomy is, eg. 'source'
             'field'    => 'slug',
             'terms'    => $org_slug,
             'include_children' => false
            ),
            // #2 Format: as above
            $tax_q_format_array
          ),
      );

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

      // 5. Send results to Javascript
      if ( $posts_org->have_posts() ) :
          ?>
          <?php while ( $posts_org->have_posts() ) : $posts_org->the_post(); ?>
              <?php get_template_part('partials/loops/col', 'vertical'); ?>
          <?php endwhile; ?>
          <?php
      endif;

      wp_die();
  }

Edit: If all posts are pulled, disallow more polling

How should we stop allowing more link clicks if all posts/pages have been returned?

In PHP, this code update calculates the total number of pages of posts this query will have, based on total posts divided by posts per page.

It uses a data-page attribute in the clicked link to denote the next pagination page expected to be pulled.

That counter is incremented each time a page of results is pulled.

If the counter reaches past the total number of pages of posts for the query, that means we have exhausted the posts/pages for this query, there are no more, so we should STOP.

What does "STOP" mean? It means using jQuery selectors to find and HIDE the whole link, which could otherwise be clicked to call posts again.

         <?php
         $organisation = get_query_var('organisation');
         $org_id_prefixed = get_query_var('org_id_prefixed');
         ?>


         <?php


         /* ************************************************************************************************************** */
         /*                                                                                                                */
         /*          LIST POSTS BY FORMAT TERM, WITH DYNAMIC AJAX MORE-POST LOADING                                        */
         /*                                                                                                                */
         /*          Outline:                                                                                             */
         /*          This will output posts in blocks for each "Format" taxonomy term.                                     */
         /*          But we also want to use this loop to output posts *without* a "Format" taxonomy term.                 */
         /*          >> Method via Sajid @ Artisans Web, https://artisansweb.net/load-wordpress-post-ajax/                 */
         /*                                                                                                                */
         /*          Dynamic:                                                                                              */
         /*          1. Javascript: When "More" link is clicked                                                            */
         /*          2. Send current "organisation", clicked "format" and "page" as variables to function                  */
         /*          3. PHP: Do WP query for next page of posts, return to javascript                                      */
         /*          4. Javascript: Append results to the clicked container                                                */
         /*                                                                                                                */
         /*          Dynamic method:                                                                                       */
         /*          $format used as ID for i) .row .my-posts container and ii) .loadmore link, so that we know:           */
         /*          a) Which "Load more" link was clicked                                                                 */
         /*          b) Which box to return new output to                                                                  */
         /*          c) Employs data-page attribute in link - picks up this and increments page only for this              */
         /*          >> Help via Antti Koskinen @ StackOverflow, https://wordpress.stackexchange.com/a/328760/39300        */
         /*                                                                                                                */
         /* ************************************************************************************************************** */


         // Specify which "Format" terms we want to display post blocks for
         // ("reporting" here is a wildcard, used to accommodate posts without a "Format" term set)
         $formats = array("interview","executive","analyst","oped","reporting","ma","earnings","financialanalysis","ipo","marketindicators","industrymoves");

         // For each of them,
         foreach ($formats as $format) {

                    // 1. Get actual term of the slug above
                    $format_term = get_term_by('slug', $format, "format");

                    // 2. Formulate the secondary tax_query for "format" with some conditionality
                    /* *********************************** */
                    /*           Posts by Format?          */
                    /* *********************************** */
                    if ($format!="reporting") {
                             $posts_per_page = 8;
                             $tax_q_format_array = array(
                                        'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                                        'field'    => 'slug',
                                        'terms'    => $format_term->slug,
                                        'include_children' => false
                             );
                             // Oh, and set a title for output below
                             $section_title = $format_term->name;
                    /* *********************************** */
                    /*         Posts without Format?       */
                    /* *********************************** */
                    } elseif ($format=="reporting") {
                             $posts_per_page = 8;
                             $tax_q_format_array = array(
                                        'taxonomy' => 'format', // from above, whatever this taxonomy is, eg. 'source'
                                        'operator' => 'NOT EXISTS', // or 'EXISTS'
                             );
                             // Oh, and set a title for output below
                             $section_title = 'Reporting';
                    }

                    // 3. Set query arguments
                    $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
                    $args = array(
                        // pagination
                        // 'nopaging' => false,
                        'posts_per_page' => $posts_per_page,
                        // 'offset' => '4',
                        'paged' => $paged,
                        // posts
                        'post_type' => array( 'article', 'viewpoint' ),
                        // order
                        'orderby' => 'date',
                        'order'   => 'DESC',
                        // taxonomy
                        'tax_query' => array(
                            // #1 Organisation: the current one
                            array(
                                'taxonomy' => 'company', // from above, whatever this taxonomy is, eg. 'source'
                                'field'    => 'slug',
                                'terms'    => $organisation->slug,
                                'include_children' => false
                            ),
                            // #2 Format: as above
                            $tax_q_format_array
                        ),
                    );

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


                    // 5. Output
                    if ( $posts_org->have_posts() ) { ?>

                            <?php
                            $total_posts = $posts_org->found_posts;
                            $total_pages = $total_posts / $posts_per_page;
                            ?>

                             <h5 class="mt-0 pt-4 pb-3 text-secondary"><?php echo $section_title; ?> <span class="badge badge-secondary badge-pill"><?php echo $total_posts; ?></span></h5>
                             <div class="row pb-0 my-posts" id="<?php echo $format; ?>" data-totalpages="<?php echo $total_pages; ?>">
                             <?php
                             while( $posts_org->have_posts() ) {
                                        $posts_org->the_post();
                                        get_template_part('partials/loops/col', 'vertical');
                             }
                             ?>
                             </div>
                             <?php
                             // wp_reset_postdata(); // reset the query

                             // "More" posts link
                             if ($posts_org->found_posts > $posts_per_page) {
                                        echo '<p class="text-secondary pb-0" style="opacity: 0.6"><a href="javascript:;" class="loadmore" id="'.$format.'"><i class="fas fa-plus-circle"></i> <span class="moretext" data-page="2">More</span></a></p>';
                             }

                    } else {
                             // echo '<div class="col"><p>'.__('Sorry, no posts matched your criteria.').'</p></div>';
                    } // end if have_posts


         }


         ?>



         <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' ); ?>";
         var page = 2;                                                               // Infer page #2 to start, then increment at end
         var org_slug = "<?php echo $organisation->slug; ?>";                         // Slug of this "organisation" term

         jQuery(function($) {

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

                             // Get ID of clicked link (corresponds to original $format value, eg. "executive"/"reporting")
                             var clicked_format = $(this).attr('id');

                            // Get total pagination pages for this section
                            var total_pages_for_section = $('div .row #'+clicked_format).data().totalpages;
                            // alert(total_pages_for_section);

                             // Temporarily change clicked-link text to provide feedback
                             $('a#'+clicked_format+' i').attr('class', 'fas fa-cog fa-spin');      // Icon
                             $('a#'+clicked_format+' .moretext').text('Loading...');               // Text

                             // Pick up data-page attribute from the clicked link (defaults to 2 at load)
                             var clicked_page = $('a#'+clicked_format).find('span').data().page;
                             // console.log(clicked_page);


                             // 1. Send this package of variables to WP query function
                var data = {
                    'action': 'load_posts_by_ajax',
                    'page': clicked_page,   // page of posts to get is the number set in data-page attribute
                    'org_slug': org_slug,
                    'clicked_format': clicked_format,
                    '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).appendTo('div#'+clicked_format).hide().fadeIn(2000); // was: $('div#'+clicked_format).append(response).fadeIn(4000); Reverse method, cf. https://stackoverflow.com/a/6534160/1375163

                    // If we have exhausted all post pages, hide the whole "More" link <p>
                    if (clicked_page >= total_pages_for_section) {
                            $('p a#'+clicked_format).hide();
                    // Otherwise, restore it and increment counter
                    } else {
                        // Change link text back to original
                        $('a#'+clicked_format+' i').attr('class', 'fas fa-plus-circle'); // Icon
                        $('a#'+clicked_format+' .moretext').text('More');                // Text
                        // Increment "data-page" attribute of clicked link for next click
                        $('a#'+clicked_format).find('span').data().page++; // was page++;
                    }

                });





            });



         });
         </script>
  • Heck, I'm going to need to find a way to not show a "More" link if we have exhausted the posts. – Robert Andrews Feb 15 at 23:21
  • Hi, I added another example to my answer on how to hide the "More" link. – Antti Koskinen Feb 16 at 8:03

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.