How to filter by multiple parameters at the same time?

I read some similar discussions but can’t figure out how to do it.

I want to give products 4 or more filters, like: country, producer, type, and make possible to select any combinations, for example, one or two tags from country, then some from producer etc.

Like in this example: https://take.ms/Realy

Solution I am currently using for one group of filters is same as here: https://getkirby.com/docs/cookbook/content/filtering-with-tags

1 Like

Nice solution, didn’t find it, thanks a lot! Will try right now, only noticed that here https://www.dev.constantinweb.com/kirby-filter-content/related-multiple after applying filter https://www.dev.constantinweb.com/kirby-filter-content/related-multiple/type:Album, the order of years changes, but that should be easy to change.

Checked this solution, working fine, only doesn’t select several filters in the same group:

Maybe you can give some hint how to make possible to select both “Single” and “My new” at the same time? :slight_smile: @anon3573533

1 Like

hmm this is a good question… I thought to this “option” also, without having tried to make it for the moment because no idea about how to build it. Will add it to github if I find the correct solution. If anyone has any tips… :wink:

Yes, I’m sur there are some improvements and corrections to add to these codes, for exemple urlencode on links etc…

Kirby 3.3.0 has a new $collection->when() method that makes filtering a lot easier.

Apart from that, if you get an array of options rather than a single value, you can use the available filter methods.

@anon3573533’s multi-filters form example uses radio fields, for multiple values in one category you need a checkboxes field instead.

1 Like

Resurrecting this thread because I have run into a similar issue while building a cookbook.

I have several different types of params and can easily filter between those. However, when I want to combine multiple instances of the same param, I am not sure how to properly filter my collection.

Here is my controller:

<?php

return function ($site, $page) {

  $recipes = collection('recipes');
  
  /* FILTERS */
  $diets = Str::split(param('diet'), ',');
  $type = param('type');
  $author = param('author');
  $random = param('random');
  $query   = get('q'); // Search

  $recipesFiltered = $recipes
    ->when(($diets), function ($diets) {
      return $this->filterBy('diet', 'in', $diets, ', ');
    })
    ->when($type, function ($type) {
      return $this->filterBy('type', $type, ',');
    })
    ->when($author, function ($author) {
      return $this->filterBy('author', "- $author");
    })
    ->when($random, function ($random) {
      return $this->shuffle()->limit($random);
    })
    ->when($query, function ($query) {
      return $this->search($query, 'title|ingredients|diet');
    });


  return [
    'query'   => $query,
    'recipes' => $recipes,
    'recipesFiltered' => $recipesFiltered

  ];

};

All except for the $diets param are single-select, so no problem there. However, I want to be able to combine different types of diet and filter the recipes accordingly. I therefor split the param into an array and then filter the recipes by that array. The problem is that this matches recipes that fit any of the items in the array and not all of them.

So if I have 10 vegan and 15 vegetarian recipes and I filter for “vegan, vegetarian” it should return only 10, not 15 recipes.

As @texnixe suggested in her last comment, I should probably use the filter method instead but I haven’t been able to get this working. I tried turning the diet-information of the individual recipe into an array (it’s saved via checkboxes) and I guess I could somehow compare that to my params array?!

I am a bit lost but if someone would be able to point me in the right direction, I’d really appreciate it.

If you check if something is in an array, or logic will be applied, so the result is correct. You would have to use the filter method with a callback using and logic.

This should work:

<?php

->when($diets, function ($diets) {
      return $this->filter(function($child) use($diets) {
          return count(array_diff($diets, $child->diet()->split())) === 0;
      });
  })

On a side note, why are you returning two different collections?

The author filter looks a bit strange as well.

1 Like

Thanks so much, that’s exactly what I was looking for!

Oh, yes, the second recipes collection was a leftover. I have a recipe-counter in the nav and used it for that but I guess I can also just use 'collection('recipes')' again.

Regarding the author filter: that’s actually a solution I found here in the forum. It works, but not sure if it’s really the best solution? If I just use $author the filter does not work because users are saved with a preceeding '- ' in the text files.

I’d say it’s a bit of a hacky solution. A filter with a callback that turns the field value into a user and then compares the values would be the more correct approach, I think.

 ->when($author, function ($author) {
     return $this->filter(function($child) use($author) {
        $user = $child->author()->toUser();
        if ($user) {
             return $user->name() === $author;    // or whatever you have to compare here
        });
    });
});
1 Like

That does sound more reasonable, will look into it, thanks!

I posted a possible solution above, not tested.

A lot more complicated then your original solution, though. Wondering if there is no better solution. I’ll check…

The easier, but definitely better than the original version, solution for the author would be:

  ->when($author, function ($author) {
      return $this->filterBy('author', $author, '-');
    })
1 Like

Ah yes, perfect. Thanks a lot!

I am trying to work with that but I think it is not possible in the way I did.

return function($site) {

    $filterBy = get('filter');

    $projects = $site
    ->find('Projekte')
    ->children()
    ->listed()
    ->when($filterBy, function($filterBy) { 
        return $this->filterBy('category', $filterBy); 
    })
    ->when($filterBy == 'prämiert', function() { 
        return $this->filterBy('toggle', true); 
    })
    ->shuffle();

    return [
        'filterBy' => $filterBy,
        'projects' => $projects
    ];
};

The first when() is working. Now I have a special case where the URL can be:
projekte?filter=prämiert

So I thought, maybe I can say when the $filterBy is ‘prämiert’, Kirby could filter the projects for all with toggle set to true. But that is not working. How should I do this?

Nope, you can’t put a condition in there, try

 ->when($filterBy, function($filterBy) { 
        if ( $filterBy === 'prämiert' ) {
            return $this->filterBy('toggle', true); 
        }
    })

Now I get:

Error: Call to a member function shuffle() on null

But only when I click on a filter which worked before. When I click on “prämiert” nothing happens.

Hm, maybe try something else then

    $projects = $site
    ->find('Projekte')
    ->children()
    ->listed()
    ->when($filterBy, function($filterBy) { 
        if ($filterBy === 'prämiert' ) {
          return $this->filterBy('category', $filterBy)->filterBy('toggle', true); 
        }
        return $this->filterBy('category', $filterBy); 
    })
    ->shuffle();
1 Like

Thank you, this worked:

if ($filterBy === 'prämiert' ) {
        return $this->filterBy('toggle', true); 
        }
return $this->filterBy('category', $filterBy);