Filter WordPress posts by multiple taxonomy terms with AJAX

UPDATE: 14. Nov. 2016
Demo is now extended to show standard numbered pagination or infinite scroll.

This is the second part of the post where I show how to display and filter WordPress posts based on taxonomy term.
In this post I will create a working script which shows how to filter posts by multiple terms.

I will include the entire code but I will not explain in depth what’s going on there because I already did that in previous post.
If you missed the first part please check here How to filter WordPress posts by custom taxonomy term with AJAX and pagination.

Explanation

The idea is very simple, in the previous post you can find out how to:

  1. Display list of terms
  2. Get taxonomy and term of clicked element
  3. Get and display posts by selected term

This post will just extend this feature so instead only one element, we will get all active terms and retrieve posts for all active terms.
To do that we would just need to slightly modify our javascript to:

What does this do?

  1. On click toggle parent li item class ‘active’
  2. Get all .active li items
  3. Get ‘taxonomy’ and ‘term’ slug from all elements: $terms[taxonomy_name] = ['term1', 'term2'];
  4. Pass params to our function that gets posts by selected terms

Function which gets posts by AJAX stays the same like in previous posts.

PHP function

After we get all params with jQuery we need to get posts by selected terms. To do that we need to modify our PHP function, and use ‘tax_query’ in our WP_Query.
Snippet from function:

What does this do?

Basically script stays the same like in previous post, the only difference is that we now pass data as an array of 'taxonomy' => array_of_terms[]' and we need to loop trough this array and construct ‘tax_query’.

Working demo example with pagination
Working demo example with infinite scroll

Downloads

  1. PHP file Gist
  2. Javascript file Gist

67 Comments

    1. Hi Rubic,

      Thanks for reporting, I have fixed the issue.
      Problem was that I forgot to include ‘wp_ajax_nopriv’ so it was not working for logged out users.

      Cheers.

  1. Hi, thank you for tutorial, when i first try your demo, its doesn’t work, but now its fixed for logged out users :)
    I wonder how to show all posts when:
    – User visit page with all posts (blog in my case) for the first time
    – User first select some terms, and after that un-select everything (return to default state)
    Also what do you think about using/adding hidden term list item, that will contain default wordpress category?
    I mean: my default wp category is Uncategorized(for testing this) and i have cat 1 to 12 as children of uncategorized
    When i make hidden list item li.uncategorized .active
    it should load all posts by default, whnever i click on other item it should remove active class from li.uncategorized .active, so my sorting should work??
    Currently i’m using barba.js pjax, so my testing website feels like its fully ajaxified, i read somwhere that pjax is better for seo than ajax, its doesn’t hide content for search engines
    But if we could load all posts by default, or load all posts when any terms aren’t selected it still should be fine for seo, yes?

  2. Hi Gabrielle,

    I’ve updated example to include ‘Show all’ which is selected by default.
    Hope this clears all your questions.

  3. Hi Vlado,

    Wonderful guide, works perfectly for me!

    Next step for me is to create infinite-scroll / load more pagination instead of page numbering to make it all ajaxified :).

    Anyone that has managed to do this?

  4. Would be amazing, I don’t think it should be a huge deal while you’re already using a sort of ajax-pagination: I just think the load more option is nicer than replacing a number of posts with new posts (page numbers).

    Meanwhile I will try to expand the filtering of posts by adding filters from meta data in select inputs. Cause im using your function with custom taxonomies to filter apartments by “Place”. Now i need to add “No of rooms” and “Min/Max-price” in select dropdowns. I guess by adding some kind of meta_query key->rooms type of thing.

    See: https://postimg.org/image/4q1llo7td/

    1. Hi,
      Article is now updated to work either with infinite scroll or numbered pagination.
      Please check the demo with infinite scroll here and update your code accordingly.
      I’m not sure are you using shortcode or not, but to use infinite scroll you would need to add param pager=”infscr”, that would display ‘Load more’ button instead of numbered pagination.

  5. Hi Vlado,

    I’m also intarasted in this infinite-scroll option.

    Could you help me with query for time.
    I would like to user can filter by X h ago for example …

    Thanks Nikola

    1. Article is now updated to work either with infinite scroll or numbered pagination.
      Please check the demo with infinite scroll here and update your code accordingly.
      I’m not sure are you using shortcode or not, but to use infinite scroll you would need to add param pager=”infscr”, that would display ‘Load more’ button instead of numbered pagination.

      Regarding the time query, please consult WP_Query documentation on codex, the best way is to trial and error until you get desired results on your localhost.

  6. Hi Vlado,

    could you please check your files on github.
    I really tried all but there are some differences between codes on post / github and your working code :) .

    First of all in any file i cannot find this piece of code for example:

    <a href="#" data-filter="taxonomy; ?>” data-term=”all-terms” data-page=”1″>
    Show All

    so i need to search inside post to see what changes you have made and that is not problem,
    problem is that doesn’t work after that for example on infinity scroll i get infinity scroll and i get paging, when i click load more it reload second page with posts, and if i use pager=”infscr” it just show me pagination :)

    If you can make resume with final files which you use on your site it would be very nice!

    I will really appreciate your help and thanks once again for this great tutorial!

    Cheers!

  7. Hey Vlado Bosnjak,

    I am using tag with options for the filter selection. How can i make your code work with my markup?

    $(‘.sc-ajax-filter-multi’).on(‘click’, ‘a[data-filter], .pagination a’, function(event) {}

    I have tried replacing a[data-filter] with option[data-filter]..it doesn’t work. Console log returns nothing

    Please help!

    1. Hi Dylan,

      Sorry but I’m not sure I understand whats wrong.
      If you are using select, then you probably should do:

      $('select').change( function() { ... })
      

      To get the selected option

  8. Great tutorial, I learned a bunch here that directly applies to the project I’m putting together. Thank you!

    I have a question about setting the specific terms to be used with // ‘terms’ => false, // Get specific taxonomy terms only

    Is it best practice to use the term_id? I suppose it would be because if you changed the name then the id would still be maintained? If desired, would it be easy enough to change the list of terms to names or slugs?

    thanks again.
    cheers

    1. Hi Stephen,

      Yes, I think by ID would be best.
      You can get entire term object and then display what you like.

  9. great ! I love this tutorial and codes.but could you please tell me how to show category name instead of category slug in the selection confirmation msg section? thank you very much

    1. Hi Eric,

      I’m not sure where exactly is this code, but if $trm = slug of the term, you can get term data with get_term_by function. Anyhow, you can do print_r and see what is $term.

  10. Hy Vlado,

    awesome posts – it pushes my concept in the right direction! But what if i have to filter in multiple taxonomies. In my case i have to filter a recipe database (http://www.genussfreude.at/rezepte/). One of the taxonomies is “season”, another is “ingredients”. I have to combine them in a way like in this example (https://www.loveandlemons.com/recipe-browser/#all). Do you have a idea how to achieve that?

    Thanks for your reply and kind regards,
    Bernhard

    1. Hi Bernhard,

      Basically it’s the same thing. Just instead of 1 tax add array.
      In the shortcode part here L8: https://www.bobz.co/filter-wordpress-posts-by-custom-taxonomy-term-with-ajax-and-pagination/

      $a = shortcode_atts( array(
              'tax'      => ['tax_1', 'tax_2], // Taxonomy
              'terms'    => false, // Get specific taxonomy terms only
              'active'   => false, // Set active term by ID
              'per_page' => 12 // How many posts per page,
              'pager'    => 'pager' // 'pager' to use numbered pagination || 'infscr' to use infinite scroll
      ), $atts );

      Then, on L16, you don’t do get_terms($a['tax']);, but instead you do:

      foreach ($a[tax] as $tax) {
         get_terms($tax);
         ... the rest of code ...
      }

      That’s roughly, but in short you just need to convert my code for one taxonomy into for/foreach loop.
      On click of each term, you mark him active and when making request get all with class ‘active’ and read tax/term_id from them, and construct WP_Query out of it.

      BR
      Vlado

  11. This is a life-saver!!! I have more complex taxonomy terms, but at least this gets me started! I was never able to figure this out on my own and now I can ajaxify ALL the things! Thank you so much!!

  12. amazing tutorial just one question, how can i use 2 post type and create 2 shortcut of them for echo in different area’s like :
    echo do_shortcode('[ajax_filter_posts_moves per_page="10"]');
    that show post’s from moves post type and :
    echo do_shortcode('[ajax_filter_posts_series per_page="10"]');
    and this one show posts from series post type
    p.s:sorry for bad english hope you understand my question .

    1. really thanks for tour answer sir , can you explain more? duplicating shortcut is easy the problem is how i can tell the program if post_type = movies echo ajax_filter_posts_movies and next time put post_type = series and echo ajax_filter_posts_series there is a way or no way to do this?

    2. Purpose of this tutorial is to show you how things work so you can use the logic and create what you need for your self.
      It would be difficult for me to include all possible variations that would cover all available cases.

      What you need to do is to extend shortcode function to accept one more param ‘post_type’, and pass that param along with all others to the function.
      It really shouldn’t be that hard.

  13. Hi, great tutorial. I think there’s a small bug for the infinite scroll demo though.

    When you’re on the “Show All” tab and click “Load More”, the article “Add Wistia videos …” shows up.

    Then if you click on another tag and then click back again on “Show All” and then “Load More” again, another article populates, “Pre-populate Woocommerce…”. So the “Show All” functionality is not resetting itself. Once you have loaded new posts once, they are gone forever (unless you refresh the page).

    I am trying to fix that but haven’t been able to yet. Any ideas?

  14. Hi Vlado,
    Excellent tutorial, great work!

    However there is an issue with the infinite scroll (I tested on your demo too, just to be sure it was not coming from me).
    Basically, it doesn’t reset to the “Load more” link to “#page-2” when you change of tag. For example on your demo, stay on “show all” and then navigate to #page-5; then pick another tags (less than 5) and try to click on “Load More”, it will be set with “#page-6” and will show “You reached the end”.

  15. Hi Again,

    I fixed the issue I just reported, I placed the (data.next) condition after the else {}.

    /**
    * Append content for infinite scroll
    */
    else {
    $content.append(data.content);
    }
    if (data.next !== 0) {
    $pager.attr(‘href’, ‘#page-‘ + data.next);
    }

    1. Thanks Nico,
      I will review the code as soon as I get some free time.
      I’m sure it worked when I was testing it :)

      Glad you found your own solution, it fulfills the purpose of this tutorial.

    2. Mhm. I have just checked this and it’s working for me.
      Would you mind sharing what browser and version are you using?

  16. Hi,

    Using Chrome Version 56.0.2924.87. + FF 52.0.2
    Do this little experiment:
    1. Go to https://www.bobz.co/blog/demo-filter-wordpress-posts-multiple-taxonomy-terms-ajax-infinite-scroll/
    2. “Show All” is selected, click on “load more” twice (mouse hover the “load more” button and you will see next link is “#page-4”)
    3. Select 2 another tags, you will see the first one, then click “Load more” to see the second one and you will get “You reaced the end” message because it’s trying to load “#page-4” instead of “#page-2”
    4. Also, if you go back to “Show All”, you get to see the first post again, but when you click on “Load more” it will bring the fourth one (“#page-4”), skipping post 2 and 3.

    Hope it make sense :)
    Thanks again for your tutorial!

    1. Hi John,
      Anything is possible :)
      But not as a copy/paste from this tutorial, it shows how thing work so basically you need to alter the code a little bit to target specific container of posts.
      It is still almost the same function that triggers query, in your case you would need to display results in correct container.

      Goal of tutorial is to explain how things work and not only to copy/paste things.

  17. Hi Vlado!, I am trying to exclude terms by id with ‘exclude’ => array(96), but it does not work. (Show all tags)
    function vb_filter_posts_sc($atts) {
    $a = shortcode_atts( array(
    ‘tax’ => ‘tagss’, // Taxonomy
    ‘terms’ => false, // Get specific taxonomy terms only
    ‘exclude’ => array(96),
    ‘active’ => true, // Set active term by ID
    ‘per_page’ => 12 // How many posts per page

    I have also tried including unique id by terms

    function vb_filter_posts_sc($atts) {
    $a = shortcode_atts( array(
    ‘tax’ => ‘tagss’, // Taxonomy
    ‘terms’ => array(’96’), // Get specific taxonomy terms only
    ‘active’ => true, // Set active term by ID
    ‘per_page’ => 12 // How many posts per page

  18. Hello!

    This tutorial is great. I was wondering how hard it would be to alter the code slightly to hook into a main query instead of creating a new query? This way you can filter an existing blog/custom post type query without having to recreate it.

    Cheers!

    1. Hi Jesse,

      I usually hook into main query using pre_get_posts filter, but I’m not sure that would be useful.
      Not sure why would you want to do that, this is a standard WordPress way of doing things.

    2. Someone may want to do this if they are using your tutorial in a more advanced setting there they aren’t creating and updating (filtering) a loop, but rather updating (filtering) an existing loop. Yes, using WP_Query is WordPress’ standard way of creating a new loop query, but there are instances where there are existing loops that are created by plugins or themes, and implementing your AJAX filter would be more flexible and customizable over a pre-existing AJAX filter plugin.

      I suppose you didn’t have enough context to understand. Seems like you are unable to provide direction on this, but thank you anyways.

  19. Hi Vlado, thank you for the fantastic tutorial; it is working well for my application, and I’m trying to make a few customizations. Mainly, I’m wondering if you could elaborate on how to add multiple taxonomies by passing the array of taxonomy names to `tax` in the `shortcode_atts` portion of your code, as you mentioned in your reply to the earlier question by Bernhard? In my case, I have two custom taxonomies I’d like to use, one hierarchical and the other non-hierarchical, and I’m assuming that using the hierarchical taxonomy requires passing as an array as well? Thank you for any further direction you’re able to provide.

  20. Hi Vlado,

    Thank you so much for your time and effort to create such a complete and comprehensive tutorial. I’ve been spending months trying to solve the problem with my ajax filtering and pagination without much success, and bam! Google lead me to your site! You are my life saver!

    One thing, I am using Select2 jquery for my dropdown list instead of your Tag Cloud setup, so I am unable to use Document on Click event in the get_posts function. The events available for Select2 are pretty limited, Select, Selecting, Change. Any suggestion on how to bind this event to the Click event in the Pagination div? I tried the below, but it didnt work:
    jQuery(‘#image_grid’).on(‘change’, ‘#tag_search, #date_search, #pagination’, function(e) {
    …..
    }

    1. Mhm, that’s strange that they don’t have ‘init’ or ‘load’ event.
      You can attach maybe to select2 event on change, and then manually trigger change event on your select item. Eg:

      $('#my-select').on('select2:change', function (evt) {
      // Trigger element
      }).trigger('change');

      Or you can simply create WP_Query in your template that will display posts on page.

      BR
      Vlado

    1. OK, it is working, after adding this.

      wp_create_nonce( ‘bobz’ ),
      ‘ajax_url’ => admin_url( ‘admin-ajax.php’ )
      ));
      }
      add_action(‘wp_enqueue_scripts’, ‘assets’, 100);

      I am a little confused what the above does though. I have added it exactly as above, and it works. I had assumed I would need to amend this to point to my own jquery file? If so, how come it is working?

  21. Hello.
    Thanks for the great job with code ;) … but I think I found some bugs.
    When You select few terms and use button “Load More” to show all of filtered terms, and after that deselect used terms and select them once again or something else, button “Load More” returning “You reached the end”, not new terms.
    Please check this out.

    1. Hi Michael, Thanks for feedback. I will check the evil bugs and remove them, thanks a lot for testing.

    2. Hello again.
      Did You findout how to fix infinite scroll? I found another bug. At first I wanted to hide content at moment when no one post is find (‘status’ => 201), cause when You’re method is ‘pager’ and You select combination of terms that is giving ‘No posts found’, the old content with pagination is still visible under filters and when You try to click for example second or third page, pagination don’t work for this old content. It was not logical for me so I thinked it’s better to hide this. Unexpectedly I found code for this in Your JS script, but here is the problem:
      else if(data.status === 201)
      {
      if(data.method === ‘pager’)
      {
      $content.html(data.message);
      … the rest of code …

      data.method here is ‘undefined’ so $content.html(data.message); is not working.
      Also I added checking of code 501. I wanted to hide the content as above when ”Term doesn’t exist”. And here data.method is also returning ‘indefined’. It seems that data.method is correctly detected only with ‘data.status === 200’. With method ‘infscr’ everything works just because there is no detection of ‘data.method’, its just using ‘else’. Please check this also. I’m looking for a few hours to solve this but I could’t find solution. It’s to hard.

    3. I did it ;) I just add ‘method’ => $pager in functions.php, to else with ‘status’ => 201. Now it’s working perfect. Ufff…
      But still no progress with infscr.

    1. Michael,

      My earlier comment wasn’t about infinite scroll at all. I made a comment about an entirely different, and then followed it up shortly afterwards when I realised I had made a mistake and fixed it.

  22. I fix the bug with infscr.
    Just add this below after success in js file:
    $pager.attr(‘href’, ‘#page-2’); // Added to fix infscrl

  23. Is it possible to have multiple instances of this?

    For example, ‘post type a’ on one page, and ‘post type b’ on another?

    Maybe I’m missing something but I can’t get it to work?

  24. I just realized I posted this under your first post by accident. It was meant for this one, so I’m re-posting my comment:

    Hi, this is really great! I have it working for the most part; however, I need to create a 2 column list of categories (specifically children of 2 categories) that filters posts for a specific custom post type and need some help.

    (1) I’m using WP’s default categories and need to be able to limit the categories and posts shown to the children of specific categories and not a list of all categories/all posts from all categories. How do I do this?

    (2) Is there a way to change it so that it displays checkboxes/uses select functionality?

    You’re help is greatly appreciated!

  25. Is there anyway to have the page scroll to the top when clicking either of the pagination links? For some reason, the usual methods of achieving this are not working.

Leave a Reply

Your email address will not be published. Required fields are marked *