Simple Ajax Filter & Search for WordPress
In this post, I am going to share a simple way of using Ajax filter search for WordPress. I love using Ajax in my projects. It is neat, light and fast. Most importantly the user experience is much better.
The Scenario
We are going to build a search form like the one shown below. This form will search for movies which is a custom post type. We will also filter the result with a few fields – Movie Year, IMDB Rating, Language and Genre.
How Our Ajax Filter Search Works?
Ajax is very simple but can seem very tricky at first. In our scenario, we are going to send data as a request (search keywords, year, rating, language, genre) to the server and it will do a query with the data then return a callback with the results.
Initial Setup
For the purpose our ajax filter search form, we may create a fresh WordPress install and populate dummy data for the trial. We will basically edit 2 files in the theme – functions.php and script.js. As always, please make sure you are using a child theme. I used the Twenty Sixteen Theme’s child theme in my demo. I added a custom post type called movie and created the custom fields with Advanced Custom Fields plugin. You can add a custom post type with the following code added to the functions.php file.
<?php
function register_custom_post_type_movie() {
$args = array(
"label" => __( "Movies", "" ),
"labels" => array(
"name" => __( "Movies", "" ),
"singular_name" => __( "Movie", "" ),
"featured_image" => __( "Movie Poster", "" ),
"set_featured_image" => __( "Set Movie Poster", "" ),
"remove_featured_image" => __( "Remove Movie Poster", "" ),
"use_featured_image" => __( "Use Movie Poster", "" ),
),
"public" => true,
"publicly_queryable" => true,
"show_ui" => true,
"show_in_rest" => false,
"has_archive" => false,
"show_in_menu" => true,
"exclude_from_search" => false,
"capability_type" => "post",
"map_meta_cap" => true,
"hierarchical" => false,
"rewrite" => array( "slug" => "movie", "with_front" => true ),
"query_var" => true,
"supports" => array( "title", "editor", "thumbnail" ),
"taxonomies" => array( "category" ),
);
register_post_type( "movie", $args );
}
add_action( 'init', 'register_custom_post_type_movie' );
Code language: HTML, XML (xml)
Alternatively, you can use a plugin like Custom Post Types UI for adding custom post types with a user interface.
Before we create some movie posts, we need to create the custom fields which are used as filters.
The fields are pretty self-explanatory but if you are confused, here is the exported JSON file of the field group which you can download, unzip and import on ACF.
Download acf-export-2018-04-02.zip
Once you have the custom fields ready, you can start posting some movie posts. The add new movie post page should look something like the following.
You can see, I added some categories. They will work as the genre of the movie. After creating some demo movie posts, we are ready to start coding.
Creating a Shortcode for the Ajax Filter Search
In this step, we are creating a shortcode that will return the form code. So, we can use the shortcode in any part of the website to use the form. Put the following code on functions.php file.
<?php
// Shortcode: [my_ajax_filter_search]
function my_ajax_filter_search_shortcode() {
ob_start(); ?>
Test Shortcode Output
<!-- FORM CODE WILL GOES HERE -->
<?php
return ob_get_clean();
}
add_shortcode ('my_ajax_filter_search', 'my_ajax_filter_search_shortcode');
Code language: HTML, XML (xml)
Now use the shortcode [my_ajax_filter_search] in any page content and you should see “Test Shortcode Output” as output. As our shortcode is working fine, we are going to put actual form code that we will use the filter/search form.
<?php
// Shortcode: [my_ajax_filter_search]
function my_ajax_filter_search_shortcode() {
ob_start(); ?>
<div id="my-ajax-filter-search">
<form action="" method="get">
<input type="text" name="search" id="search" value="" placeholder="Search Here..">
<div class="column-wrap">
<div class="column">
<label for="year">Year</label>
<input type="number" name="year" id="year">
</div>
<div class="column">
<label for="rating">IMDB Rating</label>
<select name="rating" id="rating">
<option value="">Any Rating</option>
<option value="9">At least 9</option>
<option value="8">At least 8</option>
<option value="7">At least 7</option>
<option value="6">At least 6</option>
<option value="5">At least 5</option>
<option value="4">At least 4</option>
<option value="3">At least 3</option>
<option value="2">At least 2</option>
<option value="1">At least 1</option>
</select>
</div>
</div>
<div class="column-wrap">
<div class="column">
<label for="language">Language</label>
<select name="language" id="language">
<option value="">Any Language</option>
<option value="english">English</option>
<option value="korean">Korean</option>
<option value="hindi">Hindi</option>
<option value="serbian">Serbian</option>
<option value="malayalam">Malayalam</option>
</select>
</div>
<div class="column">
<label for="genre">Genre</label>
<select name="genre" id="genre">
<option value="">Any Genre</option>
<option value="action">Action</option>
<option value="comedy">Comedy</option>
<option value="drama">Drama</option>
<option value="horror">Horror</option>
<option value="romance">Romance</option>
</select>
</div>
</div>
<input type="submit" id="submit" name="submit" value="Search">
</form>
<ul id="ajax_filter_search_results"></ul>
</div>
<?php
return ob_get_clean();
}
add_shortcode ('my_ajax_filter_search', 'my_ajax_filter_search_shortcode');
Code language: HTML, XML (xml)
Here, we added 5 fields. The search as an input field, Year as a number field, Rating, Language and Genre as a select dropdown. The options in the drop-down fields are set based on the option we had in the custom fields. The year field will have 4 digit number so it is ok with as a number field. Rating is set from 1 to 9 as IMDB ratings are like that. Language and Genre are ordinary select fields with matching options from the backend custom field and categories.
Adding Necessary Script
We are going to create a new script.js file in our theme folder for adding our javascript codes. Add the following code before the shortcode function we added earlier on the functions.php file.
<?php
// Scripts for Ajax Filter Search
function my_ajax_filter_search_scripts() {
wp_enqueue_script( 'my_ajax_filter_search', get_stylesheet_directory_uri(). '/script.js', array(), '1.0', true );
wp_add_inline_script( 'my_ajax_filter_search', 'const ajax_info = '. json_encode( array(
'ajax_url' => admin_url( 'admin-ajax.php' )
) ), 'before' );
}
Code language: HTML, XML (xml)
We also used wp_inline_script() for easy use of admin-ajax.php file.
Next thing to do is calling the my_ajax_filter_search_scripts() function inside the my_ajax_filter_search_shortcode() function.
<?php
// Shortcode: [my_ajax_filter_search]
function my_ajax_filter_search_shortcode() {
my_ajax_filter_search_scripts(); // Added here
ob_start(); ?>
<div id="my-ajax-filter-search">
<form action="" method="get">
....
....
Code language: HTML, XML (xml)
This way the scripts needed for the ajax filter/search will load whenever we use the shortcode.
Preparing Data for Sending the Request
Add the following code to the script.js file
$ = jQuery;
var mafs = $("#my-ajax-filter-search");
var mafsForm = mafs.find("form");
mafsForm.submit(function(e){
e.preventDefault();
console.log("form submitted");
// we will add codes above this line later
});
Code language: JavaScript (javascript)
Here we added two variables for ease of use and then a function that fires when the form submit button is pressed. The code e.preventDefault(); part prevents page reload. You will also see output “form submitted” on browser console (Press F12 on Chrome to go to developer tools and then Esc to go to console window).
Now that we see our code is working fine, we can go ahead and prepare the data. Replace console.log(“form submitted”); with the following code (before “});” at the bottom).
if(mafsForm.find("#search").val().length !== 0) {
var search = mafsForm.find("#search").val();
}
if(mafsForm.find("#year").val().length !== 0) {
var year = mafsForm.find("#year").val();
}
if(mafsForm.find("#rating").val().length !== 0) {
var rating = mafsForm.find("#rating").val();
}
if(mafsForm.find("#language").val().length !== 0) {
var language = mafsForm.find("#language").val();
}
if(mafsForm.find("#genre").val().length !== 0) {
var genre = mafsForm.find("#genre").val();
}
var data = {
action : "my_ajax_filter_search",
search : search,
year : year,
rating : rating,
language : language,
genre : genre
}
Code language: JavaScript (javascript)
Here, we are checking on submission, if the fields have value. If they do, we set their respective value on a variable and add all of them to the variable data. The part action : “my_ajax_filter_search” is needed for the callback in the next step as we will be sending data via GET method.
Prepare Callback Function
We have the data ready for the request. Now, we need to configure the callback. Going back to the functions.php file, we will add a new function for the callback.
<?php
// Ajax Callback
add_action('wp_ajax_my_ajax_filter_search', 'my_ajax_filter_search_callback');
add_action('wp_ajax_nopriv_my_ajax_filter_search', 'my_ajax_filter_search_callback');
function my_ajax_filter_search_callback() {
header("Content-Type: application/json");
$meta_query = array('relation' => 'AND');
if(isset($_GET['year'])) {
$year = sanitize_text_field( $_GET['year'] );
$meta_query[] = array(
'key' => 'year',
'value' => $year,
'compare' => '='
);
}
if(isset($_GET['rating'])) {
$rating = sanitize_text_field( $_GET['rating'] );
$meta_query[] = array(
'key' => 'rating',
'value' => $rating,
'compare' => '>='
);
}
if(isset($_GET['language'])) {
$language = sanitize_text_field( $_GET['language'] );
$meta_query[] = array(
'key' => 'language',
'value' => $language,
'compare' => '='
);
}
$tax_query = array();
if(isset($_GET['genre'])) {
$genre = sanitize_text_field( $_GET['genre'] );
$tax_query[] = array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => $genre
);
}
$args = array(
'post_type' => 'movie',
'posts_per_page' => -1,
'meta_query' => $meta_query,
'tax_query' => $tax_query
);
if(isset($_GET['search'])) {
$search = sanitize_text_field( $_GET['search'] );
$search_query = new WP_Query( array(
'post_type' => 'movie',
'posts_per_page' => -1,
'meta_query' => $meta_query,
'tax_query' => $tax_query,
's' => $search
) );
} else {
$search_query = new WP_Query( $args );
}
if ( $search_query->have_posts() ) {
$result = array();
while ( $search_query->have_posts() ) {
$search_query->the_post();
$cats = strip_tags( get_the_category_list(", ") );
$result[] = array(
"id" => get_the_ID(),
"title" => get_the_title(),
"content" => get_the_content(),
"permalink" => get_permalink(),
"year" => get_field('year'),
"rating" => get_field('rating'),
"director" => get_field('director'),
"language" => get_field('language'),
"genre" => $cats,
"poster" => wp_get_attachment_url(get_post_thumbnail_id($post->ID),'full')
);
}
wp_reset_query();
echo json_encode($result);
} else {
// no posts found
}
wp_die();
}
Code language: HTML, XML (xml)
The above code block has few important things that I would like to explain.
- At line 05 and 06, you see we used add_action for the callback function my_ajax_filter_search_callback(). If you have been using a diffrent function name, you can change wp_ajax_my_ajax_filter_search to wp_ajax_YOUR_FUNCTION_NAME. Same thing for wp_ajax_nopriv_my_ajax_filter_search
- At line 10 we added header(“Content-Type: application/json”); to declare the content type as json because, we are going to use JSON to send result back to the browser.
- At line 12 we started adding meta_query rules based on our fields. They are checked if they are set from the form submission then, they are sanitized for injection and compared different conditions.
- At line 41 we added tax_query rules as we used WordPress categories as the Genre of the movies.
- Finally, at line 52, we combined the arguments for the query but we also need to query for the search keywords passed from the search input. For this, we used ‘s’ => $search. Here the value of $search is checked for relevant results.
- After the query, at line 95 we echo the result encoded as JSON which we will use in the next step in jQuery Ajax function.
jQuery Ajax Function
Now we are going to use jQuery Ajax function to complete our last step. Add the following code after the previous code (before the bottom “});”) on script.js file.
$.ajax({
url : ajax_info.ajax_url,
data : data,
success : function(response) {
mafs.find("ul").empty();
if(response) {
for(var i = 0 ; i < response.length ; i++) {
var html = "<li id='movie-" + response[i].id + "'>";
html += " <a href='" + response[i].permalink + "' title='" + response[i].title + "'>";
html += " <img src='" + response[i].poster + "' alt='" + response[i].title + "' />";
html += " <div class='movie-info'>";
html += " <h4>" + response[i].title + "</h4>";
html += " <p>Year: " + response[i].year + "</p>";
html += " <p>Rating: " + response[i].rating + "</p>";
html += " <p>Language: " + response[i].language + "</p>";
html += " <p>Director: " + response[i].director + "</p>";
html += " <p>Genre: " + response[i].genre + "</p>";
html += " </div>";
html += " </a>";
html += "</li>";
mafs.find("ul").append(html);
}
} else {
var html = "<li class='no-result'>No matching movies found. Try a different filter or search keyword</li>";
mafs.find("ul").append(html);
}
}
});
Code language: JavaScript (javascript)
Here you can see we used ajax_info.ajax_url that we defined on the functions.php file. We then used the data variable for data param and for the success we have set a response.
As we are going to search multiple times from the same page, we will need to empty the ul that will contain all the results before appending the results. So we used mafs.find(“ul”).empty();
At last, we did a for loop and prepared the layout of the result to the html variable.
mafs.find(“ul”).append(html); appends the result in the ul.
Let us add some quick CSS so that it all looks good. You can add it to the sytle.css file or custom CSS in customizer.
.column-wrap {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin: 20px 0
}
.column-wrap .column {
width: 49%;
}
.column-wrap .column:last-child {
float: right;
}
#my-ajax-filter-search label {
display: block;
font-weight: bold;
font-style: italic;
}
#my-ajax-filter-search select {
width: 100%;
background-color: #f7f7f7;
padding: 0.625em 0.4375em;
border: 1px solid #d1d1d1;
}
#ajax_filter_search_results {
list-style: none;
display: flex;
justify-content: start;
flex-wrap: wrap;
margin-top: 30px;
}
#ajax_filter_search_results li {
width: 23.5%;
float: left;
margin-right: 2%;
overflow: hidden;
position: relative;
margin-bottom: 20px;
}
#ajax_filter_search_results li.no-result {
width: 100%;
text-align: center;
}
#ajax_filter_search_results li:nth-child(4n+4) {
margin-right: 0;
}
.movie-info h4 {
margin-bottom: 10px;
color: #fff;
}
.movie-info p {
margin-bottom: 0;
color: #fff;
font-size: 13px;
}
.movie-info {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 15px;
background: rgba(0, 0, 0, 0.85);
opacity: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
transition: all .3s;
}
.movie-info * {
width: 100%;
display: block;
}
#ajax_filter_search_results li:hover .movie-info {
opacity: 1;
}
Code language: CSS (css)
The Output!
Bonus
A lot of requests came in since this article was published. One of the major requests was to know how to use checkboxes for the filter. So, let me share that with you guys.
To use checkboxes you will need to modify the code in a few areas. First of all, you need to make sure the checkboxes are added properly. If you are going to take multiple values, they need to be prepared so the values can be used in the query.
I am adding an example to the existing setup so you can follow:
First, add a new field in the field group for the checkboxes. Check the screenshot
In the form right before the submit field, let’s add an extra checkbox group
<input class="available" type="checkbox" id="netflix" value="netflix" name="available"><label for="netflix">Netflix</label>
<input class="available" type="checkbox" id="amazon" value="amazon" name="available"><label for="amazon">Amazon</label>
<input class="available" type="checkbox" id="viki" value="viki" name="available"><label for="viki">Viki</label>
<input class="available" type="checkbox" id="blueray" value="blueray" name="available"><label for="blueray">BlueRay</label>
Code language: HTML, XML (xml)
Then, let’s add the javascript part. Here we are taking the values of the checkboxes to an array.
var available = [];
$.each($("input[name='available']:checked"), function(){
available.push($(this).val());
});
Code language: JavaScript (javascript)
Now inside the my_ajax_filter_search_callback()
function before the genre meta_query conditon, let’s add some code
if(isset($_GET['available'])) {
$available = $_GET['available'];
foreach( $available as $value ) {
$meta_query[] = array(
'key' => 'available_on', // our new added custom field for the checkboxes
'value' => $value,
'compare' => 'LIKE'
);
}
}
Code language: PHP (php)
Now your query should return according to the checkbox checked or unchecked properly.
Hopefully, this article will help you try Ajax on your next WordPress project and you will be easily adding an ajax filter search form on your website. Feel free to share your feedback below. Happy Coding!
Credits: Banner image from Freepik by macrovector. Movie posters are for demo purposes only. I don’t hold any copyright for the movie posters visible at the screenshots or anywhere else.
Last Updated: September 10, 2022