WordPress Filter Posts with Ajax without page reload or plugin | Weichie
Menu

Updated on oktober 6, 2020

·

WordPress Filter Posts with Ajax, without page-reload or plugins

WordPress Filter without page reload using ajax is not a hard thing to do. Many developers will quickly install another plugin like facetwp or yith to do things like this. But it’s actually pretty simple, as WordPress has default ajax functionalities. For me, as a front-end developer, the hardest part is making the actual WP_Query if we’re extending this method for combined filters WooCommerce.

A more advanced ajax filter for Woocommerce will follow soon! Let’s first start with the basics. A live example of ajax filters in WordPress can be found on one of my live projects: https://welmanprojects.be/projects/ (currently still in development)

WordPress post filters with ajax

WordPress setup for posts and custom post types

Filtering posts, pages, or custom post types with ajax all works in the same way. I assume you have a basic knowledge of javascript and PHP for the syntax and basic WordPress to know how to make a query. You can filter posts and post types by category, tags, and custom taxonomies.

In this example, I will show you how we can filter our custom post type ‘projects’ by category, based on the category slug. There are many ways to query the posts. I’ll give a quick overview of all possible arguments so you can see which ones work best in your project.

Available WP_Query Arguments

Category Parameters

  • cat: use category ID – int
  • category_name: use category slug – string
  • category__and: use category IDs – array
  • category__in: use category IDs – array
  • category__not_in: use category IDs – array

Tag Parameters

  • tag: use tag slug – string
  • tag_id: use tag ID – int
  • tag__and: use tag IDs – array
  • tag__in: use tag IDs – array
  • tag__not_in: use tag IDs – array
  • tag_slug__and: use tag slugs – array
  • tag_slug__in: use tag slugs – array

WordPress Page Setup

To get started, we first need a page that queries all our projects and displays them on that page. We also need to query over our categories, to be able to display all available categories. A live example of the filters can be found on the website of a client of mine: https://welmanprojects.be/projects/ (still in development)

Let’s display our available categories first:

<?php $categories = get_categories(); ?>
<ul class="cat-list">
  <li><a class="cat-list_item active" href="#!" data-slug="">All projects</a></li>

  <?php foreach($categories as $category) : ?>
    <li>
      <a class="cat-list_item" href="#!" data-slug="<?= $category->slug; ?>">
        <?= $category->name; ?>
      </a>
    </li>
  <?php endforeach; ?>
</ul>

Notice that our first list-item is not within the loop. We’ll use this to remove the filters and display all the posts again. In our anchor-tags you see href=”#!”. We need this because I want to use a-tags for the filters, but anchor tags require a href-tag to be a valid HTML element.

Also note that I am using data-slug in my list-item links. We’ll need this later in our functions to filter on the category slug.

Querying over WordPress Posts and Custom Post Types

<?php 
  $projects = new WP_Query([
    'post_type' => 'projecten',
    'posts_per_page' => -1,
    'order_by' => 'date',
    'order' => 'desc',
  ]);
?>

<?php if($projects->have_posts()): ?>
  <ul class="project-tiles">
    <?php
      while($projects->have_posts()) : $projects->the_post();
        include('_components/project-list-item.php');
      endwhile;
    ?>
  </ul>
  <?php wp_reset_postdata(); ?>
<?php endif; ?>

This is a basic WP_Query to pull in all our posts from post-type ‘projecten’. Make sure our single list items are coming from a separate PHP file. You can use include like I did, or use get_template_part(). Whatever you prefer.

The include file is just a single <li> item, so you can give it whatever markup you want. I won’t include it in this tutorial, because we’re going straight to the point: filtering

Feel free to reach out to me or leave a comment in case you want to see a full setup, that also explains the project-list-item.php file. For testing your code, you can add the_title(); in this file for now.

The WordPress filter with Javascript!

For me as a front-ender, obviously the most fun part! We’ll be running a filter function when we click on a category link we’ve made before. We’ll be doing 2 things:

  1. Make the clicked category the new active filter
  2. Display only the projects that belong to that category

EASY. But let’s not celebrate too early. This is only the JS part, we do need some custom PHP code later on in order to make this stuff work.

$('.cat-list_item').on('click', function() {
  $('.cat-list_item').removeClass('active');
  $(this).addClass('active');

  $.ajax({
    type: 'POST',
    url: '/wp-admin/admin-ajax.php',
    dataType: 'html',
    data: {
      action: 'filter_projects',
      category: $(this).data('slug'),
    },
    success: function(res) {
      $('.project-tiles').html(res);
    }
  })
});

This doesn’t look too complicated, does it? We click our category links and create a new $.ajax call. We will be posting our data to a custom function called ‘filter_projects’. We also need to post the slug we want to filter and we do that by passing our filter to our data ‘category’.

The AJAX URL is default in wordpress: /wp-admin/admin-ajax.php

The PHP WordPress Filter, the horror!

Just kidding. Ajax queries in WordPress with PHP all sounds like a nightmare, but it’s all fun and games! Because we are using the default /wp-admin/admin-ajax.php URL, we can write this function somewhere in the functions.php file:

function filter_projects() {
  $catSlug = $_POST['category'];

  $ajaxposts = new WP_Query([
    'post_type' => 'projecten',
    'posts_per_page' => -1,
    'category_name' => $catSlug,
    'orderby' => 'menu_order', 
    'order' => 'desc',
  ]);
  $response = '';

  if($ajaxposts->have_posts()) {
    while($ajaxposts->have_posts()) : $ajaxposts->the_post();
      $response .= get_template_part('templates/_components/project-list-item');
    endwhile;
  } else {
    $response = 'empty';
  }

  echo $response;
  exit;
}
add_action('wp_ajax_filter_projects', 'filter_projects');
add_action('wp_ajax_nopriv_filter_projects', 'filter_projects');

When hitting the ‘filter_projects’ function, we’ll be checking if we did POST our category-slug. If it’s an empty string, the query won’t filter and it will return all our posts. (we use it when we click the filter “show all projects”)

Next, we run our WP_Query again, but this time with an extra parameter for the category name. Ironically enough, it’s using the SLUG and not the name of the category. Afterward, we just loop over query results and add the template part as a string to our $results.

Don’t forget to register our function, so WordPress knows our ‘filter_projects’ exists. Otherwise, we’ll receive an error when we try to make our ajax call.

Filter Dynamic custom post types with ajax

If you are using multiple post-types that are using the same taxonomies, we can dynamically pass our post_type to our ajax filter function as well. This way we can reuse our previously setup for all posts types on our page.

To be able to do this, we take our PHP list setup from the example above and add a new data attribute ‘data-type’ to also add the post type:

The following code snippets display only the parts that changed from the code examples from before. If something is not working correctly, make sure to use the code from above to setup all the triggers and functions.

<li>
  <a class="cat-list_item" href="#!" data-slug="<?= $category->slug; ?>" data-type="projecten">
    <?= $category->name; ?>
  </a>
</li>

Our data-type can be dynamic as well of course if you’re using the same filter component on all pages. I’ve kept it static in my example for simplicity. Now you can create multiple lists for each post-type and change the data-type attribute to the post type you want to filter.

In our javascript function, we also add an additional parameter in our Ajax filter function:

$.ajax({
  type: 'POST',
  url: '/wp-admin/admin-ajax.php',
  dataType: 'html',
  data: {
    action: 'filter_projects',
    category: $(this).data('slug'),
    type: $(this).data('type'),
  },
  success: function(res) {
    $('.project-tiles').html(res);
  }
});

And at last, we need to make sure our PHP code uses our dynamic post type argument to filter our post types. Update the PHP function code in functions.php to look like this:

function filter_projects() {
  $postType = $_POST['type'];
  $catSlug = $_POST['category'];

  $ajaxposts = new WP_Query([
    'post_type' => $postType,
    'posts_per_page' => -1,
    'category_name' => $catSlug,
    'orderby' => 'menu_order', 
    'order' => 'desc',
  ]);
  $response = '';

  if($ajaxposts->have_posts()) {
    while($ajaxposts->have_posts()) : $ajaxposts->the_post();
      $response .= get_template_part('templates/_components/project-list-item');
    endwhile;
  } else {
    $response = 'empty';
  }

  echo $response;
  exit;
}
add_action('wp_ajax_filter_projects', 'filter_projects');
add_action('wp_ajax_nopriv_filter_projects', 'filter_projects');

Filter custom taxonomies in WordPress with ajax

When filtering your posts using a custom taxonomy instead of the regular tags or categories, we need to edit our query a little bit. The way we can filter on custom taxonomies is slightly different. We need to add a tax_query to our normal query.

a tax_query is an array, existing of arrays. This actually brings us fluently to creating an ajax filter that supports multiple filters at once. But let’s just start with displaying all posts for the selected custom taxonomy.

Our query will look like this:

$projects = new WP_Query([
   'post_type' => 'projecten',
   'posts_per_page' => -1,
   'tax_query' => [
      [
         'taxonomy' => 'product_cat',
         'field'    => 'term_id',
         'terms'    => $termIds, // example of $termIds = [4,5]
         'operator' => 'IN'
      ],
   ]
]);

Note that our taxonomy query is an array of arrays. $termIds is also an array, with all termIds I want to filter on. Thanks to the taxonomy query being its own argument, we can combine taxonomy filters together with other category or tag filters.

I wil upload a full filter-example to github soon!

Combining multiple WordPress filters with Ajax

combining wordpress ajax filters

Let’s combine multiple filters at the same time! The picture shows example of how it works and will look. It’s somewhat similar to creating the regular filters we did above, but can quickly become a mess. So I’m going to seperate our ajax filters in a seperate filters.js file. The PHP part will still be in functions.php but you can place it in your own WordPress plugin folder if you’d like. I’m not going to cover creating WordPress Plugins, so my solution will be straight in functions.php 🙂

To get started on a filter like above, I created a custom sidebar with all my different filters in it:

<div class="filter--sidebar">
  <input type="hidden" id="filters-category" />
  <input type="hidden" id="filters-creators" />

  <?php 
    // or use include_once('parts/filter-pricerange.php');
    get_template_part('parts/filter','pricerange');

    get_template_part('parts/filter','categories');
  
    get_template_part('parts/filter','creators');
  ?>
</div>

Note the two hidden fields for our categories and creators. We will fill those up with javascript and then post those values to our ajax query. More on this later!

You can add every filter-part you want like this. Because I covered the categories above, I will cover creators first. This is a relationship field with the ACF Plugin.

Our parts/filter-creators.php file shows a list of all our creators/brands. This makes it possible for users to filter on the brand they like. This file simply loops over all our brands and shows them in a list:

<?php 
$get_creators = new WP_Query([
  'post_type' => 'creators',
  'posts_per_page' => -1,
]);

if ($get_creators->have_posts()): ?>
  <h4 class="filter-title"><?= _e('Creators','weichie'); ?></h4>
  <ul class="list-filters">
    <?php while ($get_creators->have_posts()):$get_creators->the_post(); ?>
      
      <li>
        <a href="javascript:;" class="filter-link" data-type="creators" data-id="<?= get_the_ID(); ?>">
          <?= esc_html(get_the_title()); ?>
          <span class="remove"><i class="fas fa-times"></i></span>
        </a>
      </li>

    <?php endwhile; ?>
  </ul>
  <?php wp_reset_postdata(); ?>
<?php endif; ?>

So far so goodd? I dont’ think anything special is going on in here. We create a new WP_Query for our ‘authors’ post type and loop over them. You do need to hide the span.remove element with css. This one we will show only when someone has selected that filter.

Creators ACF Field filter

Maybe it’s also interesting to show you how I’ve linked our creators to the products. I’m using the ACF Pro Plugin to create the fields and place them as a relationship field onto our products. ‘Creators’ is a custom post type on its own.

acf relationship filter

For each product in our store, I can select one or more brands that are ‘related to’ this product. So it would be a nice to have if users could now filter our products based on this brand.

Next up: our filter.js file! You don’t have to, but it I like to keep things clear. The filter-file will contain all our js-related filter stuff. So if something breaks or we need to change or add something, it’s always easy to find.

You remember our hidden fields in our php filters? Now is the time we’re going to use them. When clicking on a creator, we’ll be putting the IDs of our selected brands to the input field.

filters.js :

$('.filter-link').on('click', function(e) {
    e.preventDefault();
    $(this).toggleClass('activeFilter');

    editFilterInputs($('#filters-' + $(this).data('type')), $(this).data('id'));
    filterProducts();
  });

So for each filter-link we’re clicking on, we’ll toggle our ‘activeFilter’ class (to show/hide the remove icon on the filter), we’ll edit the values in the hidden input fields and we’ll be calling our filterProducts() function to actually filter our products.

To place our values in our hidden fields, we need to make sure we’re not adding a filter twice. Clicking on a filter will add the ID to our hidden field. Clicking on it again will remove the ID from the hidden input field:

function editFilterInputs(inputField, value) {
  const currentFilters = inputField.val().split(',');
  const newFilter = value.toString();

  if (currentFilters.includes(newFilter)) {
    const i = currentFilters.indexOf(newFilter);
    currentFilters.splice(i, 1);
    inputField.val(currentFilters);
  } else {
    inputField.val(inputField.val() + ',' + newFilter);
  }
}

The editFilterInputs function takes an inputfield – the one where we want to place our filters. Our creator IDs need to go into the creator hidden input field of course. Then we check if the ID already exists in the input field or not. If so, we’ll remove the ID, otherwise we’ll add it.

Our input-field has a value of string. I’m converting the current value in our input field to an array to make it easier to check if the value already exists. If so, remove it, otherwise add it.

Now our main filterProducts() function:

function filterProducts() {
  const catIds = $('#filters-category').val().split(',');
  const creatorIds = $('#filters-creators').val().split(',');
  
  $.ajax({
    type: 'POST',
    url: '/wp-admin/admin-ajax.php',
    dataType: 'json',
    data: {
      action: 'filter_products',
      catIds,
      creatorIds,
    },
    success: function(res) {
      $('#result-count').html(res.total);
      $('#main-product-list').html(res.html);
    },
    error: function(err) {
      console.error(err);
    }
  })
}

The JS part actually looks a lot like a single category/tag/taxonomy filter. But this one will be used for all our filters in the future. In this example it will work for both creators and categories. You can add all other filters in exactly the same way.

Please note that I am again converting the hidden input fields to an array before posting it to our php script to actually make the query and return our response.

Multiple Ajax Filters – PHP part

The PHP part is a bit different than usual. I don’t know if I am explaining any of this clearly to you, but please bear with me. We’re almost there and I hope you can make it to work in your project as well!

Getting started in the PHP file, we’ll create our filter_products() filter, receive our $_POST value from the creators and categories and just start with our basic query (without filters) for now.

function filter_products() {
	$catIds = $_POST['catIds'];
	$creatorIds = $_POST['creatorIds'];

	$args = [
		'post_type' => 'product',
		'posts_per_page' => -1,
		'post_status'  => 'publish',
		'orderby'        => 'date',
                'order'          => 'desc',
	];
}
add_action('wp_ajax_filter_products', 'filter_products');
add_action('wp_ajax_nopriv_filter_products', 'filter_products');

This is still going fine? We get the POST values and create an $args array with our basic query values. We are using a separate $args variable to make it easier to add our multiple filters to the query-array.

Next, we’ll add our product categories to the $args array. This one uses the WordPress default ‘product_cat’ taxonomy to filter on a product category: add this part right below our previous $args. In the same filter_projects() function.

// Product Category
if (count($catIds) > 1) {
	$args['tax_query'][] = [
		'taxonomy'      => 'product_cat',
		'field'		=> 'term_id',
		'terms'         => $catIds,
		'operator'      => 'IN'
	];
}

Also nothing too crazy I think? It takes our $catIds from our $_POST from before. By using $args[‘tax_query’][] we add this array to the already existing $args array from before. We’re not overwriting anything, we’re adding a ‘tax_query’ array to our previous array.

We’ll be adding our creators filter to the array the same way. Only is Creators a Custom Post Type and not a Taxonomy. So instead of using tax_query we need to use meta_query for this field:

// Product creator - Every creator needs a seperate meta_query
if(count($creatorIds) > 1) {
	$args['meta_query']['relation'] = 'OR';

	foreach($creatorIds as $creator) :
		if ($creator != '') :
			$args['meta_query'][] = [
				'key' => 'product_creator',
				'value' => '"' . $creator . '"',
				'compare' => 'LIKE',
			];
		endif;
	endforeach;
}

Each creator needs it’s own [‘meta_query’] array. Only if there is a creator selected, we’ll be adding this filter to our $args.

Now our $args filter has the default query for our products, a tax_query for our categories and a meta_query for our ACF Relation ship queries.

Now it’s time for the main query. This one is exactly the same as the singular filters from above, but I changed the response a little bit so we receive our query total (to see how many products are in the filter) and then our $html result.

Place this code in the same filter_products() function, right below our previous $args functions for categories and custom post types:

if ( $ajaxproducts->have_posts() ) {
	ob_start();
	while ( $ajaxproducts->have_posts() ) : $ajaxproducts->the_post();
		$response .= wc_get_template_part( 'content', 'product-dibbz' );
	endwhile;
	$output = ob_get_contents();
	ob_end_clean();
} else {
	echo __( 'No products found' );
}
	
$result = [
	'total' => $counter,
	'html' => $output,
];

echo json_encode($result);
wp_reset_postdata();
exit;

This post took longer than I thought and I couldn’t finish the post in 1 go… Please come again later this week when I’m able to wrap up. Sorry to break it like this, but I hope it can already help you out! (written on October 6th, 2020)

I will add a full-filter example on GIT soon to make my examples a bit more clear.

WordPress Ajax Filter Summary

This is all we need to make ajax filters work in WordPress. Let me know if the ajax filters were easier than you thought or if you’re still having difficulties.

A more advanced, dynamic ajax filter for Woocommerce will follow somewhere next month. I still have some difficulties with filtering e-commerce products with ajax to make it super fast. The post will be ready when I’m done struggling. 😉

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

14 reacties

  • Nino schreef:

    Hi man!

    What do I have to change to filter on a taxonomy instead of a category? What do I have to change?

    Thanks!

    • Bob Weichler schreef:

      Hi Nino,
      For custom taxonomies you need to change your query to something like this:

      $projects = new WP_Query([
         'post_type' => 'projecten',
         'posts_per_page' => -1,
         'tax_query' => [
            [
               'taxonomy' => 'custom-taxonomy',
               'field'    => 'slug',
               'terms'    => array('your-term-slug'),
               'operator' => 'IN'
            ],
         ]
      ]);

      It's untested so let me know if it works correctly for taxonomies. I'll add it to the article if it does.

      • Nino schreef:

        Hey Bob,

        I have created a custom taxonomy themes with multiple values in the back-end. I am able to return them when looping through them. But filtering is not working. I am not using categories but would like to filter by taxonomy. I have tried to apply your code but without luck.

        
        function filter_updates() {
            $postType = $_POST['type'];
            $catSlug = $_POST['category'];
        
          $ajaxposts = new WP_Query([
            'post_type' => $postType,
            'posts_per_page' => -1,
            'category_name' => $catSlug,
            'orderby' => 'menu_order',
            'order' => 'desc',
            'tax_query' => [
                [
                   'taxonomy' => 'custom-taxonomy',
                   'field'    => 'slug',
                   'terms'    => array('your-term-slug'),
                ],
             ]
          ]);
        }
        

        Could you see what is causing this issue? When trying to filter it returns only one post.

        • Bob Weichler schreef:

          Hi Nino, I removed a bit from your code as it became too long. I left the query part in your answer. If you’re not using categories, you need to remove the ‘category_name’ from the arguments. The Tax Query in my previous example was filled with dummy content. You need to change the custom-taxonomy and your-term-slug with your actual taxonomy.

          • Nino schreef:

            Thank you very much Bob for the fast reply. I will try to explain myself. I have figured it out I able to sort based on taxonomy. I would like to use 2 SELECT for 2 different taxonomies one for themes and one for subjects which are 2 taxonomies. How can I use a SELECT for multiple tax filtering? When is select one dropdown option it will filter all over again, is there a way I can simply do this? So basically giving users the option to filter on 2 select fields instead of one.

          • Bob Weichler schreef:

            Hi Nino, I removed your code examples from your reply as it became a mess in the backoffice. Please use pastebin or github gists to show your code. For combining multiple filters at once, I will add an additional section in my blog post. You need to build up your $args before you make the new WP_Query. Here’s a small example of how you can build your args query. I will try to expand my post over the weekend but hopefully this snippet can get you started.

            // Set your default query params
            $args = [
               'post_type'      => 'product',
               'posts_per_page' => -1,
               'post_status'    => 'publish',
               'orderby'        => 'date',
               'order'          => 'desc',
            ];
            
            // Add each extra taxonomy query like this:
            $args['tax_query'][] = [
               'taxonomy'      => 'product_cat',
               'field'	   => 'term_id',
               'terms'         => $catIds,
               'operator'      => 'IN'
            ];
            
            // Make a new WP_Query with the $args array
            $ajaxproducts = new WP_Query($args);
      • Nino schreef:

        Hi Bob,

        How can I add code here? For some reason, It is just not working for me.

  • Kevin schreef:

    Your code is clean and easy to follow. Thank you very much

  • Marty schreef:

    I want to see full code. Contact me please on my email. Where did you put the files you mentioned? I also plan to put snippets instead of intrusive code injection inside functions.php. I would try to avoid putting code inside admin-ajax.php also, just beacuse of possible updates and exploits. And one comment also, I really like php more, so when possible, I would bypass the ajax. It runs faster, eats less memory. If you do a lot of JavaScript and jQueries, it’s gonna eat your resources.

    • Bob Weichler schreef:

      Hi Marty, I will try adding some files to a git repo to offer a full code example. Also, I am a front-end developer so I will always choose for a JS solution when possible 🙂 While with javascript I can rerun solely the query I need, running it in plain PHP will require a full page reload – reloading headers and footers ass well. I did not add any code to admin-ajax.php, but it’s the WordPress default route for ajax calls.

  • Dee schreef:

    Hi.
    Thank you for your work on this.
    Could you explain how this could work for post_tags?
    in the js, changed the data: category to data : taxonomy
    in functions filter_projects, changed the slug variable to –
    $catSlug = $_POST[‘taxonomy’];
    But it shows ALL the posts on the site

    • Bob Weichler schreef:

      Hi Dee,
      Did you also change the WP_Query from ‘category’ to ‘tag’ => $catSlug? It should work just fine for post_tags as well. Just make sure you’re using the correct WP_Query arguments. They are slightly different from categories to tags. You can find all available query arguments for tags in the beginning of my post.
      Hope it helps!

  • Bart schreef:

    Hello, does this also work for multiple filters? So can I copy the code that multiple filters work on the same custom post?

    Thanks in advance,

    Bart

    • Bob Weichler schreef:

      Hey Bart,
      Thank you for your message. Do you mean combining multiple filters or using this filter for different post-types? You can pass the parameter of ‘post_type’ to your PHP function the same way as we pass our ‘category_name’ in our example. I will edit this post with add an additional example, but hopefully, this makes sense?
      Best,
      Bob