Filtering items using select field

Hi all. I have a case studies page where I loop through case studies as items. I’m using this cookbook and have barely changed a thing as it’s just what i needed. However I now want to add a second filter option, again for tags (with the name ‘type’) and have these work in conjunction with the first selection.

So for example if I have domain.co/projects/tag:london being able to also have type:commercial
as well to show both results in the one url.

Is there a demo of this floating about that I’m not aware of.

Current controller:

<?php
return function($site, $pages, $page) {

  // fetch the projects
  $projects = $page->children()->listed();

  // fetch all tags used in projects. pluck($field, 'separator', unique)
  $tags = $projects->pluck('tags', ',', true);

  // add the tag filter if there is a tag in the url
  if($tag = urldecode(param('tag'))) {
	  	$projects = $projects->filterBy('tags', $tag, ',');
	}
  $projects = $projects->paginate(50);
  $pagination = $projects->pagination();
	// Return the list of projects and tags to template
  	return compact('projects', 'tags', 'tag', 'pagination');
};

This approach should help, with the only difference that you would filter two times instead of sorting:

Thanks so much. Might be in over my head here though and can’t get it working. It might be to do with the fact the function names and params are similar names so I’m getting confused.

To elaborate, I have 2 sets of tags in the blueprint:

type:
  label: Tags
type:
  label: Tags

For my UI I’m just looping through the tags for the filter, but need two of these:

$tags = $page->children()->listed()->pluck('location', ',', true);
<?php foreach($tags as $tag): ?>
  <a href="<?= url($page->url(), ['params' => ['location' => $tag]]) ?>">
    <?= html($tag) ?>
  </a>
<?php endforeach ?>

So I am assuming I create a second method for the second filter? I’m a bit lost how that second filter works in the referenced code in the other thread sorry.

That looks wrong, what are the names of the fields?

I assume you mean location and tags?

Controller:

return function($page) {

    $tagFilter      = param('tag');
    $locationFilter = param('location');

    $notes     = $page->children()->listed();
    $tags      = $notes->pluck('tags', ',', true);
    $locations = $notes->pluck('locations', ',', true);

    // filter conditionally
    $notes = $notes
        ->when($tagFilter, fn($tagFilter) => $this->filterBy('tags', $tagFilter, ','))
        ->when($locationFilter, fn($locationFilter) => $this->filterBy('locations', $locationFilter, ','));


    return [
        'tagFilter'      => $tagFilter,
        'locationFilter' => $locationFilter,
        'notes'          => $notes->paginate(6),
        'tags'           => $tags,
        'locations'      => $locations,
    ];

};

Filters in template:

<?php foreach ($tags as $tag) : ?>
  <a href="<?= url($page->url(), ['params' => ['tag' => $tag, 'location' => $locationFilter]]) ?>">
    <?= html($tag) ?>
  </a>
<?php endforeach ?>
<?php foreach ($locations as $location) : ?>
  <a href="<?= url($page->url(), ['params' => ['tag' => $tagFilter, 'location' => $location]]) ?>">
    <?= html($location) ?>
  </a>
<?php endforeach ?>

Apologies for late reply @texnixe , I was away for Easter break. Thanks for help so far. Unfortunately i was unable to get both tags working together for a subtractive filtering. I have changed it slightly so that now one blueprint field is tags (‘location’) and the other is a select (‘demographic’).

My blueprint is as follows:

      location:
        type: tags
        label: location
        width: 1/2
      sector:
        label: Type
        width: 1/2
        type: select
        required: true
        options:
          commercial: Commercial
          residential: Residential

Controller:

return function($page) {

    $locationFilter = param('location');
    $sectorFilter = param('location');

    $projects  = $page->children()->listed();
    $location = $projects->pluck('location', ',', true);
    $sector = $projects->pluck('sector', ',', true);

    // filter conditionally
    $projects = $projects
        ->when($locationFilter, fn($locationFilter) => $this->filterBy('location', $locationFilter, ','))
        ->when($sectorFilter, fn($sectorFilter) => $this->filterBy('sector', $sectorFilter, ','));


    return [
        'locationFilter' => $locationFilter,
        'sectorFilter' => $sectorFilter,
        'projects'       => $projects->paginate(6),
        'location'      => $location,
        'sector'      => $sector,
    ];

};

filters in template:

<?php foreach ($location as $location) : ?>
  <a href="<?= url($page->url(), ['params' => ['sector' => $sectorFilter, 'location' => $location]]) ?>">
    <?= html($location) ?>
  </a>
<?php endforeach ?>

<?php foreach ($sector as $sector) : ?>
  <a href="<?= url($page->url(), ['params' => ['sector' => $sector, 'location' => $locationFilter]]) ?>">
    <?= html($sector) ?>
  </a>
<?php endforeach ?>

The filter options are correctly displayed but when clicking them nothing happens, so not sure where I’ve gone wrong; i’ve essentially just replaced your code with my new terms but must have screwed up somewhere. Any help appreciated!

$sectorFilter is the same as $locationFilter, this cannot possibly work.

Ah thanks yes my bad, fixed now. So now the issue is back to being that they don’t filter together.

For example if I’m on the main projects page and click ‘london’ in the location filter results i get this url:

www.domain.com/projects/location:London

This works fine. But then if I try to use the sector filter as well (lets say i have two options, ‘commercial’ and ‘residential’) then they will only work if there are some tagged with ‘london’ & ‘commercial’. So it means I have to loop through all available locations that also have that sector as well as an option, with a reset button too. Becomes very complicated. Any quick suggestions here? Otherwise I’ll just switch back to them being independent.

Yes, of course, that’s the whole purpose if you filter by two parameters.

If you want them to work individually, then you don’t need all this an just use two independent filter. But that’s not what you explained in your first post.

Yes thanks I’ve had to adapt as I’ve realised the complexity. I guess from a UI/UX point of view you’d want to pass a disabled attribute to any tag that doesn’t contain the current select one from the other filter. Will mull it over! Thanks

The easiest way around this problem would be to move the pluck code after the filter. That way, you would only get any locations/sectors that are actually available in the filtered list (note: I changed the variable names):

return function($page) {

    $locationFilter = param('location');
    $sectorFilter   = param('location');

    $projects  = $page->children()->listed();
    $locations = $projects->pluck('location', ',', true);
    $sectors   = $projects->pluck('sector', ',', true);

    // filter conditionally
    $projects = $projects
        ->when($locationFilter, fn($locationFilter) => $this->filterBy('location', $locationFilter, ','))
        ->when($sectorFilter, fn($sectorFilter) => $this->filterBy('sector', $sectorFilter, ','));
    
    $locations = $projects->pluck('location', ',', true);
    $sectors = $projects->pluck('sector', ',', true);

    return [
        'locationFilter' => $locationFilter,
        'sectorFilter' => $sectorFilter,
        'projects'       => $projects->paginate(6),
        'locations'      => $location,
        'sectors'      => $sector,
    ];
};

On a side note:

Do not use the same variable for the iterable and the value in a loop. You overwrite the first variable which defines your iterable (array, collection) with the item in the loop which can result in issues. So make it

<?php foreach ($sectors as $sector) : ?>

which is also much more understandable semantically.

1 Like

Thanks as always @texnixe