Custom Ajax Search filter for WordPress Search
I want to give a complete breakdown so you can easily analyse the problem and see that the issue is nowhere else, but the JavaScript. If you are not new to this please just scroll to the javaScript CUSTOM-SEARCH.JS section.
I found an article on how to add ajax search filters.
searchform.php
(Standard Search Form)
THE STANDARD SEARCH FORM
Before the filters, I edited the search form so a user can search based on post type (opone,optwo,opthree).
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
<select id="drpdwn_search">
<option value="any" selected>Choose Type</option>
<option value="opone">Option 1</option>
<option value="optwo">Option 2</option>
<option value="opthree">Option 3</option>
</select>
<input type="search" class="search-field form-control" name="s" placeholder="Search" value="<?php echo esc_attr( get_search_query() ); ?>" title="<?php _ex( 'Search for:', 'label', 'wp-bootstrap-starter' ); ?>">
<input type="hidden" name="post_type" value="any" />
<input type="submit" class="search-submit btn btn-default" value="<?php echo esc_attr_x( 'Search', 'submit button', 'wp-bootstrap-starter' ); ?>">
</form>
The site was working normally where the search results were shown and InfiniteScroll was used for pagination, but I wanted to be able to filter the search results so I tried creating an ajax search filter form.
search.php
(AJAX Filter Form/Search Results template)
Ignore $actual_link
- Since I am using a staging server without SSL $actual_link
variable supports HTTP and HTTPS, but will change in production if the variable gets used.
Query Moved
I moved the query into a function according to the instructions to implement the ajax filter so it is no longer in search.php.
Ajax Filter Form
For the AJAX filter form, I got the search term using the get_search_query() function in the search field input and post type (opone,optwo,opthree) from the query string in the URL (http://somedomain.com/?s={SEARCH TERM}&post_type={POST TYPE})and stored it as a variable ($param). Based on the post type selected, only its custom taxonomy "categories" ('opone_cat','optwo_cat','opthree_cat') will be an option in the AJAX filter form. Then created radio buttons in the AJAX filter form so users can display results ascending or descending.
Ajax search filter form and response div (.scroll-content
) in search results template (search.php):
<?php
$actual_link = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$param = filter_input(INPUT_GET, 'post_type', FILTER_SANITIZE_URL);
?>
<form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter">
<input type="search" class="search-field form-control" name="s" placeholder="Search" value="<?php echo esc_attr( get_search_query() ); ?>" title="<?php _ex( 'Search for:', 'label', 'wp-bootstrap-starter' ); ?>">
<input type="hidden" class="form-control" name="post_type" value="<?php echo $param; ?>" />
<?php
if('opone' == $param) {
if( $terms = get_terms( array(
'taxonomy' => 'opone_cat',
'orderby' => 'name'
) ) ) :
// if categories exist, display the dropdown
echo '<select name="categoryfilter" class="form-select" aria-label="Default select example"><option value="">Select category...</option>';
foreach ( $terms as $term ) :
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID of the category as an option value
endforeach;
echo '</select>';
endif;
} else if('optwo' == $param) {
if( $terms = get_terms( array(
'taxonomy' => 'optwo_cat',
'orderby' => 'name'
) ) ) :
// if categories exist, display the dropdown
echo '<select name="categoryfilter" class="form-select" aria-label="Default select example"><option value="">Select category...</option>';
foreach ( $terms as $term ) :
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID of the category as an option value
endforeach;
echo '</select>';
endif;
} else if('opthree' == $param) {
if( $terms = get_terms( array(
'taxonomy' => 'opthree_cat',
'orderby' => 'name'
) ) ) :
// if categories exist, display the dropdown
echo '<select name="categoryfilter" class="form-select" aria-label="Default select example"><option value="">Select category...</option>';
foreach ( $terms as $term ) :
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID of the category as an option value
endforeach;
echo '</select>';
endif;
} else {
if( $terms = get_terms( array(
'taxonomy' => array('opone_cat','optwo_cat','opthree_cat'),
'orderby' => 'date'
) ) ) :
// if categories exist, display the dropdown
echo '<select name="categoryfilter" class="form-select" aria-label="Default select example"><option value="">Select category...</option>';
foreach ( $terms as $term ) :
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID of the category as an option value
endforeach;
echo '</select>';
endif;
}
?>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="asc" name="date" value="ASC" />
<label class="form-check-label" for="asc">Date: Ascending</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="dsc" name="date" value="DESC" selected="selected" />
<label class="form-check-label" for="dsc">Date: Descending</label>
</div>
<button class="btn btn-primary btn-filter btn-lg">Apply filter</button>
<input type="hidden" name="action" value="myfilter">
</form>
</div>
<div class="scroll-content col-sm-12 col-md-9"></div>
If you look at the ajax search filter form above in search.php (search results template), we have a hidden action input with the value myfilter.
<input type="hidden" name="action" value="myfilter">
functions.php
myfilter
- the name of the AJAX action callback being fired.
In the instructions to implement the Ajax search filter, we are to put the query in a function and add these ajax action hooks to handle the request. One hook is for logged-in (wp_ajax_myfilter
) users and the other for non-logged-in (wp_ajax_nopriv_myfilter
) users.
add_action('wp_ajax_myfilter', 'search_filter_function');
add_action('wp_ajax_nopriv_myfilter', 'search_filter_function');
The function below has the search query loop. From the ajax filter form, we were able to get the search term $_POST['s']
, post type $_POST['post_type']
, date $_POST['date']
, and post type taxonomies $_POST['categoryfilter']
as arguments for the search query loop.
The other arguments ($args) include the standard paging variable to use for the paged argument (pagination), set post_status
to make sure only published posts are in the results, and posts_per_page to show only six posts per page.
As you can see I am using get_template_part()
to add the content (posts) and pagination.
add_action('wp_ajax_myfilter', 'search_filter_function');
add_action('wp_ajax_nopriv_myfilter', 'search_filter_function');
function search_filter_function(){
global $wp_post_types, $wp_query;
$wp_post_types['page']->exclude_from_search = true;
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$args = array(
's' => $_POST['s'],
'post_type' => $_POST['post_type'],
'post_status' => 'publish',
'posts_per_page' => 6,
'orderby' => 'date', // we will sort posts by date
'order' => $_POST['date'], // ASC or DESC
'paged' => $paged
);
// for taxonomies / categories
if( isset( $_POST['categoryfilter'] ) )
$args['tax_query'] = array(
'relation' => 'OR',
array(
'taxonomy' => 'opone',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
array(
'taxonomy' => 'optwo',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
array(
'taxonomy' => 'opthree',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
);
$search = new WP_Query( $args );
if ( $search->have_posts() ) : while ( $search->have_posts() ) : $search->the_post();
get_template_part( 'template-parts/content', 'search' );
endwhile;
get_template_part( 'template-parts/pagination', 'notabs' );
else :
get_template_part( 'template-parts/content', 'none' );
endif;
die();
}
/TEMPLATE-PARTS/CONTENT-SEARCH.PHP
The template parts were working before adding the ajax filter and as you can see I know better not to add any custom scripts to these particular files.
I also excluded the page
post type as well.
Here is the content template:
<article id="post-<?php the_ID(); ?>" <?php post_class('scroll-post'); ?> data-category="<?php echo get_post_type(); ?>">
<div class="post-thumbnail">
<?php the_post_thumbnail(); ?>
</div>
<header class="entry-header">
<?php
the_title( '<h2 class="entry-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></h2>' );
?>
<div class="entry-meta">
<?php wp_bootstrap_starter_posted_on(); ?>
</div><!-- .entry-meta -->
</header><!-- .entry-header -->
<!-- <div class="entry-content">excerpt</div> -->
<footer class="entry-footer">
<?php wp_bootstrap_starter_entry_footer(); ?>
</footer><!-- .entry-footer -->
</article><!-- #post-## -->
/TEMPLATE-PARTS/PAGINATION-NOTABS.PHP
Here is the pagination template:
<?php if(wp_script_is( 'infinite', 'enqueued' )) : ?>
<div class="page-load-status">
<div class="loader-ellips infinite-scroll-request">
<span class="loader-ellips__dot"></span>
<span class="loader-ellips__dot"></span>
<span class="loader-ellips__dot"></span>
<span class="loader-ellips__dot"></span>
</div>
<p class="infinite-scroll-last">End of content</p>
<p class="infinite-scroll-error">No more pages to load</p>
</div>
<p>
<button class="btn btn-primary btn-scroll btn-lg">View more</button>
</p>
<div id="nav-below infinite" class="pagination">
<div class="next-post"><?php next_posts_link() ?></div>
</div>
<?php endif; ?>
FUNCTIONS.PHP
(JavaScript)
I had already enqueued the Isotope script and its layout fitRows. Also added the InfiniteScroll script and registered/localized a custom javaScript file (custom-search.js
) so I can pass a PHP variable ($search_param
) with the search term since it will be needed in the custom-search.js
file.
Versions:
- Isotope PACKAGED v3.0.6
- Infinite Scroll PACKAGED v3.0.6
wp_enqueue_script('isotope', get_template_directory_uri() . '/inc/assets/js/isotope/isotope.pkgd.min.js', array('jquery'), '', false);
wp_enqueue_script('fitrows', get_template_directory_uri() . '/inc/assets/js/isotope/layout-modes/fit-rows.js', array('isotope'), '', false);
if( is_search() ) {
wp_register_script( 'custom-search', get_template_directory_uri() . '/inc/assets/js/isotope/archive-search.js', array('infinite'), '', true );
$search_query = get_search_query();
$search_param = array('search_term' => $search_query);
wp_enqueue_script('infinite', get_template_directory_uri() . '/inc/assets/js/isotope/infinitescroll.pkgd.min.js', array('jquery'), '', false);
wp_enqueue_script('custom-search', get_template_directory_uri() . '/inc/assets/js/isotope/custom-search.js', array('infinite'), '', true);
wp_localize_script( 'custom-search', 'searchParam', $search_param );
}
CUSTOM-SEARCH.JS
(JavaScript)
Great news! The filter works but...
Key problem - Isotope and InfiniteScroll does not work on the filtered posts and posts do not show without the filter being applied.
Isotope and InfiniteScroll stopped working after adding the AJAX filter form in the search results template (search.php
) and after moving the query to a function (functions.php
). I copied the Ajax call from the instructions and added above the previously working InfiniteScroll and Isotope below in custom-search.js
.
**From what I know, I need to apply the Isotope fitRows layout when appending posts. I think InfiniteScroll for pagination is a problem due to it only being initialised here based on the post type in the URL query string.
I also need to show the initial search results by default (before filters are applied) and hide (fade out) the initial search results before the filtered posts are displayed.**
This is where I need help.
jQuery(window).on('load', function () {
jQuery('#filter').submit(function () {
var filter = jQuery('#filter');
jQuery.ajax({
url: filter.attr('action'),
data: filter.serialize(),
type: filter.attr('method'), // POST
beforeSend: function (xhr) {
filter.find('.btn-filter').text('Processing...');
},
success: function (data) {
filter.find('.btn-filter').text('Apply filter');
jQuery('.scroll-content').html(data); // insert data
} //success
}); // jQuery ajax
return false;
}); //submit function
let currentLocation = window.location.href;
const ptParams = new Proxy(new URLSearchParams(window.location.search), {get: (searchParams, prop) => searchParams.get(prop),});
let post_type_value = ptParams.post_type;
let $scroll_container = jQuery('.scroll-content');
let fhsFit = $scroll_container.data('isotope');
$scroll_container.isotope({
layoutMode: 'fitRows',
itemSelector: '.scroll-post'
}); //isotope
if ( post_type_value === 'opone' || post_type_value === 'optwo' || post_type_value === 'opthree' ) {
$scroll_container.infiniteScroll({
path: 'page/{{#}}/?s=' + searchParam.search_term + '&post_type=' + post_type_value,
append: '.scroll-post',
button: '.btn-scroll',
outlayer: fhsFit,
loadOnScroll: false,
scrollThreshold: 300,
status: '.page-load-status',
hideNav: '.pagination'
}); //infinite scroll
} else {
$scroll_container.infiniteScroll({
path: 'page/{{#}}/?s=' + searchParam.search_term + '&post_type=any',
append: '.scroll-post',
button: '.btn-scroll',
outlayer: fhsFit,
loadOnScroll: false,
scrollThreshold: 300,
status: '.page-load-status',
hideNav: '.pagination'
}); //infinite scroll
} //if statement
jQuery('.btn-scroll').on('click', function () {
$scroll_container.on('load.infiniteScroll', function (event) {
$scroll_container.isotope('layout');
jQuery('.page-load-status').detach().appendTo(jQuery('.scroll-content'));
}); //on load function
}); // on click function
}); // window on load
example.com/wp-json/wp/v2/posts
, there's no need to use the old legacy AJAX API with a custom handler, just use the modern REST API that's already built in