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