Thank you Sally CJ for helping me out! The problem was that the data needs to be sent as a serialized string (query string) and my request header was wrong.
Here is the final working function I have now. Sorry about the extraneous stuff about the loading animation and the scrolling to the top of the results list. Hopefully this helps someone in the future.
function kp_submit_filters(){
// console.log( 'START kp_submit_filters()' );
// console.log( window.mirrored_params );
// Start the loading animation.
var loading_indicator = document.querySelectorAll('.sk-circle');
if( loading_indicator.length ){
kpAddClass(loading_indicator, 'is-loading');
}
// Scroll to the top of the results.
var target = document.querySelectorAll('.results');
if (target.length) {
var target_shape = target[0].getBoundingClientRect();
var target_position = target_shape.top + window.pageYOffset;
window.scroll({
top: target_position - 60,
left: 0,
behavior: 'smooth'
});
}
// Set up some data to send in the AJAX request.
var base_url = kp_get_base_url();
var data = window.mirrored_params;
data.action = 'kp_filters';
data.nonce = Knuckle.nonce;
data.base_url = base_url;
// Serialize and encode the data object.
var serialized_data = encodeURI( kp_serialize(data) );
// console.log('About to post this data: ');
// console.log( data );
// console.log( serialized_data );
// Make the request object.
var xhr = new XMLHttpRequest();
// Set up what happens with the response - remove existing result items and pagination, add the new search result items and pagination to the DOM, and also stop the loading animation.
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if( xhr.readyState === DONE ){
// console.log('ajax is done: ' + xhr.status);
if( xhr.status === OK ){
// console.log('status is OK');
// console.log(xhr);
// console.log(xhr.responseText); // 'This is the returned text.'
var response = JSON.parse( xhr.responseText );
var result_items = document.querySelectorAll('.results-item');
var result_pagination = document.querySelectorAll('.results-pagination');
var results_list = document.querySelectorAll('.results-list');
if( response.results && response.results.length ){
// Remove the existing items.
for( i=0; i<result_items.length; i++){
result_items[i].parentNode.removeChild(result_items[i]);
}
// Remove the existing pagination.
for( i=0; i<result_pagination.length; i++){
result_pagination[i].parentNode.removeChild(result_pagination[i]);
}
// Add the new items and pagination to the DOM.
if( results_list.length ){
for(i=0; i<response.results.length; i++){
results_list[0].innerHTML += response.results[i];
}
if( response.pagination && response.pagination.length ){
results_list[0].innerHTML += response.pagination;
}
}
} else {
// No results, so just remove the existing items and pagination.
for( i=0; i<result_items.length; i++){
result_items[i].parentNode.removeChild(result_items[i]);
}
for( i=0; i<result_pagination.length; i++){
result_pagination[i].parentNode.removeChild(result_pagination[i]);
}
}
}
// Hide the loading animation.
if( loading_indicator.length ){
kpRemoveClass(loading_indicator, 'is-loading');
}
} else {
// console.log('Error: ' + xhr.status); // An error occurred during the request.
}
};
// Send the POST request via AJAX.
xhr.open('POST', Knuckle.ajaxurl, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
xhr.send( serialized_data );
return false;
}