I have been delving into AJAX within WordPress and have followed many tutorials on how to do so, but every single request returns a 400 error.
In my functions.php I have added the following:
Step 1 - Register and add scripts to WordPress
/**
* Initialize the JavaScript we need for loading more
*
* @return void
*/
function ajax_load_more_init()
{
// Register and enqueue the script we need for load more
wp_register_script('ajax-load-more-script', get_template_directory_uri() . '/assets/scripts/ajax-load-more.js', array('jquery'));
wp_enqueue_script('ajax-load-more-script');
// Localize the script so we can access tthe variables in PHP
wp_localize_script('ajax-load-more-script', 'ajax_load_more_object', array(
'ajax_url' => admin_url('admin-ajax.php'),
));
}
Step 2 - Enqueue the actual script
/**
* Add AJAX loader to scripts
*/
add_action('wp_enqueue_scripts', 'ajax_load_more_init');
Step 3 - Allow AJAX to be used on the frontend
// AJAX Hook for users
add_action('wp_ajax_ajax_load_more', 'ajax_load_more');
add_action('wp_ajax_nopriv_ajax_load_more', 'ajax_load_more');
Step 4 - A dead simple function for testing
/**
* The backend PHP to actually load more posts
*
* @return void
*/
function ajax_load_more()
{
echo "TESTING";
wp_die();
}
In my actual AJAX script I have the following:
jQuery(document).ready(function ($) {
// Initialize Isotope as $grid
const $grid = $('#grid').isotope({
itemSelector: '.grid-item',
percentagePosition: true,
animationEngine: 'best-available', //CSS3 if browser supports it, jQuery otherwise
animationOptions: {
duration: 800,
},
masonry: {
columnWidth: '.grid-item',
gutter: 30,
},
})
var has_run = false;
var init_offset = 0;
// Hook into click event
$('button.load-more-posts').click(function (e) {
e.preventDefault();
var button = $(this);
var nonce = $(this).data("nonce");
console.log('Nonce is: ' + nonce);
// Disable the button
button.prop("disabled", true);
// Check the offset
if (has_run == false) {
button.data('offset', $(this).data("offset"));
init_offset = $(this).data("offset");
}
console.log('Initial offset is: ' + init_offset);
console.log('Initial offset is: ' + button.data('offset'));
// Perform AJAX request
$.ajax({
type: 'POST',
dataType: 'json',
url: ajax_load_more_object.ajax_url,
contentType: 'application/json; charset=utf-8',
data: {
action: 'ajax_load_more',
security: nonce,
init_offset: init_offset,
offset: button.data('offset'),
},
beforeSend: function (xhr) {
console.log('Loading more posts...')
button.text('Loading');
},
success: function (response) {
console.log(response);
// Undo Button Disable
button.prop("disabled", false);
// Set Offset
button.data("offset", offset + 10);
// Script has run
has_run = true;
return false;
},
error: function (xhr, status, error) {
console.log(xhr.responseText);
}
});
});
});
As you can see in the $ajax
call, the url is to admin-ajax
and the action is the trailing part of wp_ajax_nopriv_ajax_load_more
.
The nonce comes from the button on the front end like so:
<button class="load-more-posts" data-nonce="<?php echo wp_create_nonce('load_more_ajax'); ?>" data-offset="10">Load More Posts</button>
My working code
So, thanks to everyone's shared input I was able to come up with a solution that I'll detail below:
In functions.php (should really be in a plugin)
/**
* Additional thumbnail sizes
*/
add_theme_support('post-thumbnails');
add_image_size('post-thumbnail-square', 300, 300, true);
/**
* Initialize the JavaScript we need for loading more
*
* @return void
*/
function ajax_load_more_init()
{
// Register and enqueue the script we need for load more
wp_register_script('ajax-load-more-script', get_template_directory_uri() . '/assets/scripts/ajax-load-more.js', array('jquery'));
wp_enqueue_script('ajax-load-more-script');
// Localize the script so we can access tthe variables in PHP
wp_localize_script('ajax-load-more-script', 'ajax_load_more_object', array(
'ajax_url' => admin_url('admin-ajax.php'),
));
}
/**
* Add AJAX loader to scripts
*/
add_action('wp_enqueue_scripts', 'ajax_load_more_init');
/**
* Allow AJAX to be used on the front end
*/
add_action('wp_ajax_ajax_load_more_posts', 'ajax_load_more_posts_callback');
add_action('wp_ajax_nopriv_ajax_load_more_posts', 'ajax_load_more_posts_callback');
/**
* The backend PHP to actually load more posts
*
* @return void
*/
function ajax_load_more_posts_callback()
{
// First check the nonce, if it fails the function will break
check_ajax_referer('ajax_load_more_posts', 'security');
// Get the data we have from the load more button
$offset = $_POST['offset'];
$init_offset = $_POST['init_offset'];
// Get posts with given offset
if ($offset != null && absint($offset) && $init_offset != null && absint($init_offset)) {
// Finally, we'll set the query arguments and instantiate WP_Query
$args = array(
'post_type' => 'post',
'posts_per_page' => $init_offset,
'offset' => $offset
);
$post_list = array();
$query = new WP_Query($args);
if ($query->have_posts()) :
while ($query->have_posts()) : $query->the_post();
//$categories = get_the_categories();
$post_list[] = array(
'category' => get_the_category(),
'title' => get_the_title(),
'introduction' => get_field('introduction'),
'date' => get_the_date(),
'permalink' => get_permalink(),
'thumbnail' => get_the_post_thumbnail(),
);
endwhile;
endif;
echo json_encode($post_list);
wp_die();
}
}
In my ajax-loader script
jQuery(document).ready(function ($) {
var has_run = false;
var init_offset = 0;
// Hook into click event
$('button.load-more-posts').click(function (e) {
var $grid = $('#grid').isotope({
itemSelector: '.grid-item',
});
e.preventDefault();
var button = $(this);
var nonce = $(this).data("nonce");
// Disable the button
button.prop("disabled", true);
// Check the offset
if (has_run == false) {
button.data('offset', $(this).data("offset"));
init_offset = $(this).data("offset");
}
// Perform AJAX request
$.ajax({
type: 'POST',
dataType: 'json',
url: ajax_load_more_object.ajax_url,
data: {
action: 'ajax_load_more_posts',
security: nonce,
init_offset: init_offset,
offset: button.data('offset'),
},
beforeSend: function (xhr) {
console.log('Loading more posts...')
button.text('Loading');
},
success: function (response) {
console.log(response);
button.text('Load more');
// An array to store new items added via AJAX
var new_items = [];
// Run through JSON
$.each(response, function (key, value) {
var $new_item = $(`<div class="grid-item article-post-card ${value.category[0]['slug']}">
<a class="article-post-card__anchor" href=" ${value.permalink}" alt="${value.title}">
<div class="article-post-card__featured-image-container">
<div class="article-post-card__overlay"></div>
<div class="article-post-card__featured-image">
${value.thumbnail}
</div>
<div class="article-post-card__category-label">
${value.category[0]['name']}
</div>
</div>
<div class="article-post-card__content-wrapper">
<div class="article-post-card__publish-date">
<time class="updated" datetime="">${value.date}</time>
</div>
<div class="article-post-card__title">
${value.title}
</div>
<div class="article-post-card__excerpt">
${value.introduction}
</div>
</div>
</a>
</div>`);
new_items.push($new_item[0]);
});
// Add the new items to the grid
$grid
.isotope('insert', new_items)
.imagesLoaded().progress(function () {
$grid.isotope('layout');
});
// Undo Button Disable
button.prop("disabled", false);
// Set Offset
var offset = button.data("offset");
button.data("offset", offset + 10);
// Script has run
has_run = true;
return false;
},
error: function (xhr, status, error) {
console.log("There was an error", error);
}
});
});
});
If you guys could critique I think this would be useful for others too.
action=ajax_load_more_posts&security=25a70fd9b9&init_offset=10&offset=10