A basic AJAX implementation that can easily be expanded upon. This article shows how to add AJAX sorting.
I created a component called filter.php that I could include wherever I needed it to keep things tidy.
<div class="filter">
<div class="filter__sort">
<select id="filter-sort" class="filter__dropdown">
<option value="newest">Sort by newest</option>
<option value="recent">Sort by recently updated</option>
<option value="oldest">Sort by oldest</option>
<option value="popular">Sort by most popular</option>
</select>
</div>
</div>
I recommend creating a separate JavaScript file, e.g. ajaxFilterPosts.js, and importing it in the main.js file to keep thing clean and easy to maintain.
export default () => {
const container = jQuery('#ajax-post-results');
const sortSelect = jQuery('#filter-sort');
// Helper function to get a URL parameter
const getURLParam = (param, defaultValue = '') => {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(param) || defaultValue;
};
// Helper function to update the URL without reloading
const updateURL = (param, value) => {
const url = new URL(window.location);
url.searchParams.set(param, value);
window.history.pushState({}, '', url);
};
// Function to filter posts via AJAX
const filterPosts = () => {
jQuery.ajax({
url: ajaxurl.url,
type: 'POST',
data: {
action: 'filter_posts',
sort: sortSelect.val(),
},
beforeSend: () => {
container.html('<div class="spinner"><div></div><div></div><div></div><div></div></div>');
},
success: (response) => {
container.html(response.html);
},
});
};
// Function to initialize values from the URL
const initializeFiltersFromURL = () => {
sortSelect.val(getURLParam('sort', 'newest'));
};
// Event: Sort Select Change
sortSelect.on('change', () => {
updateURL('sort', sortSelect.val());
filterPosts();
});
// Initialize filters and trigger initial post load
filterPosts();
};
Optional CSS for the spinner
// Copied from https://loading.io/css/
.spinner,
.spinner div {
box-sizing: border-box;
}
.spinner {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.spinner div {
box-sizing: border-box;
display: block;
position: absolute;
width: 64px;
height: 64px;
margin: 8px;
border: 8px solid currentColor;
border-radius: 50%;
animation: spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: currentColor transparent transparent transparent;
}
.spinner div:nth-child(1) {
animation-delay: -0.45s;
}
.spinner div:nth-child(2) {
animation-delay: -0.3s;
}
.spinner div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
At the top of the main.js file, I import ajaxFilterPosts like so:
import filterPosts from './ajaxFilterPosts';
And make sure to call the filterPosts() function I created above after the page is done loading:
window.addEventListener("DOMContentLoaded", () => {
filterPosts();
// All my other functions will be called here
}
I recommend creating a separate file and including it in your functions.php file via get_template_part() or however you prefer to handle includes. Here’s how I do it: get_template_part('/functions/ajax-filter-posts');
<?php
// Handle filtering
add_action('wp_ajax_filter_posts', 'filter_posts_function');
add_action('wp_ajax_nopriv_filter_posts', 'filter_posts_function');
function filter_posts_function() {
$sort_option = isset($_POST['sort']) ? sanitize_text_field($_POST['sort']) : 'newest'; // <-- defaults to "newest"
$args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => get_option('posts_per_page'),
'paged' => $page,
];
// Add sorting options
if ($sort_option === 'newest') {
$args['orderby'] = [
'date' => 'DESC'
];
} elseif ($sort_option === 'recent') {
$args['orderby'] = [
'modified' => 'DESC'
];
} elseif ($sort_option === 'oldest') {
$args['orderby'] = [
'date' => 'ASC'
];
} elseif ($sort_option === 'popular') {
$args['orderby'] = [
'post_views' => 'ASC'
];
}
$query = new WP_Query($args);
ob_start();
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
echo '<article class="card">';
get_template_part('inc/card-content');
echo '</article>';
}
} else {
if ($page === 1) {
echo '<p class="loop-message">Sorry, no results were found.</p>';
}
}
$html = ob_get_clean();
wp_reset_postdata();
// Output JSON response
wp_send_json([
'html' => $html,
]);
wp_die(); // Required to terminate immediately and return a proper response
}
In your functions.php file, add this code. I recommend placing it in the scripts() function, under the other enqueued scripts.
function scripts() {
// AJAX URL
wp_localize_script('js', 'ajaxurl', array(
'url' => admin_url('admin-ajax.php')
));
}
All that’s left to do is include your filter.php file wherever you need it and create the container div where your posts will be injected. In my case, it was in my custom template: projects.php. But you could include yours in index.php, archive.php or wherever you need it.
<?php get_template_part('inc/filter'); ?>
<div class="articles__loop" id="ajax-post-results">
<?php /** Filtered posts will be injected here */ ?>
</div><!-- END articles__loop -->
That’s it! Just tweak the code to suit your needs. Please see this article to learn how to add more features to your AJAX.