Filter-menu from a multiselect of other page’s titles

Hi

I have posts with a multiselect based on all “artists” childres.

type: fields
fields:
  artists
    type: multiselect
    options: query
    query: site.page("artists").children

The regular value coming out of this select is something like artists/name-bla. To translate it in a readable output I use something like >toPages()->title(). Works.

If I want to build a filter like below but with those page’s titles instead of their shorturl I somehow need to do something similar. But I’m so lost. How would this code look like with >toPages()->title() integrated?

$filterBy = get('filter');

$ungefiltert = $page->children()->listed();

$articles = $ungefiltert
  ->when($filterBy, function($filterBy){
  return $this->filterBy('artists', $filterBy, ',');
});
  
$filters = $ungefiltert->pluck('artists', ',', true); 

Additional question: If I have the same situation but with just one value because there it would be a standard select field (not a multiselect), I can use the same code, do I?

This won’t work, you cannot call title() on a collection, but only on a single item inside the collection.

For a multiselect field, you would have to pass the separator to toPages(',') anyway, because the default separator is yaml.

The filter should then look somthing like this:

$filterBy  = get('filter');
$articles  = $page->children()->listed();
$filters   = $articles->pluck('artists', ',', true); 
$articles  = $articles
    ->when($filterBy, fn ($filterBy) => $this->filter(fn ($item) => $item->artists()->toPages(',')->findBy('title', $filterBy)));

Note that if your filters contain spaces, you would have to urlencode/decode.

Hmm … i still get the url instead of the name. What can I show you, for further help?

First, the code you use to output your filter list…

Wait. Now I have the title of the post and not the title of the related artist to the post.
The filter output looks like this:

<?php foreach ($articles as $filter): ?>
  <a href="<?= $page->url() ?>?filter=<?= $filter ?>"><?= $filter ?></a>
<?php endforeach ?>

Maybe this helps understanding: I have newsposts related to specific artists.
The relation is made by the YML you see on top.

In the news post itself i get the related artists with this code:

<?php $artist = $item->artists()->toPages(','); ?>
<ul>
  <?php foreach($artist as $bla): ?>
  <li>
    <a href="<?= $bla->url() ?>">
      <?= $bla->title() ?>
    </a>
  </li>
  <?php endforeach ?>
</ul>

Works.

Now I need to use those titles as filters.

Ok, these links should really point to the parent page, not the current page, where you want to show the list of (filtered) articles:

<?php $artists = $item->artists()->toPages(','); ?>
<ul>
  <?php foreach($artists as $artist): ?>
  <li>
    <a href="<?= $artist->parent()->url(['params' => ['artist' => urlencode($artist->title())]]) ?>">
      <?= $artist->title() ?>
    </a>
  </li>
  <?php endforeach ?>
</ul>

In the parent page’s controller, you then use the filter, as above, only we need to urldecode the parameter value (I’m using param here instead of the query string):

$filterBy  = urldecode(param('artist'));
$articles  = $page->children()->listed();
$filters   = $articles->pluck('artists', ',', true); 
$articles  = $articles
    ->when($filterBy, fn ($filterBy) => $this->filter(fn ($item) => $item->artists()->toPages(',')->findBy('title', $filterBy)));

I think I need to be more clear.
This is my reduced news overview, showing all its children.
The children each have a field “artists” which is a dynamic multiselect of all artists children.
This works perfect.

<?php foreach ($page->children()->flip() as $item): ?>
  
<article>
<?php $artist = $item->artists()->toPages(','); ?>
  <ul>
    <?php foreach($artist as $bla): ?>
    <li>
      <a href="<?= $bla->url() ?>">
        <?= $bla->title() ?>
      </a>
    </li>
    <?php endforeach ?>
  </ul>
</article>

<?php endforeach ?>

Now i just like to use those titles as a filter. So I added the code before:

<?php
  $filterBy = get('filter');
  $ungefiltert = $page->children()->listed();
  $articles = $ungefiltert
    ->when($filterBy, function($filterBy){
    return $this->filterBy('artists', $filterBy, ',');
  });
  $filters = $ungefiltert->pluck('artists', ',', true); 
?>

<hr>
Filter:
<?php foreach ($filters as $filter): ?>
  <a href="<?= $page->url() ?>?filter=<?= $filter ?>"><?= $filter ?></a>
<?php endforeach ?>

This works so far but the output of $filter is an url like artists/carl-andre.

Oh, ok, I see, so the links in the article list are not the filter.

Now for your filters:

// Instead of looping through the plucked filters, get all children pages for the filters: 
$artists = page('artists')->children()->filterBy('id', 'in', $filters);
foreach($artists as $artist): ?>
<a href="<?= $page->url() . '?filter=' .  urlencode($artist->title()) ?>"><?= $artist->title() ?></a>
<?php endforeach ?>

The for the filtering, use the code I posted above.

Yes! One step closer. Thank you so much for your patience. And sorry for mess.
Now I have the filter-urls i like to have: …/news?filter=Stanley%20Brown
But the post loop is not reacting to the filter. See GIF attached.

So went back to my old filter loop from another project to compare them.
How do we marry these two?

<?php
  $filterBy = get('filter');
  $ungefiltert = $page->children()->listed();
  $articles = $ungefiltert
  ->when($filterBy, function($filterBy){
    return $this->filterBy('artists', $filterBy, ',');
  })
  ->sortBy(function ($page) {
    return $page->date()->toDate();
  });
  $filters = $ungefiltert->pluck('artists', ',', true); 
?>

<hr>

<!-- 👇🏻 Doesn’t work but nice output like "…/news?filter=Stanley Brown" -->
Filter 1: 
<?php $artists = page('artists')->children()->filterBy('id', 'in', $filters);
foreach($artists as $artist): ?>
<a href="<?= $page->url() ?>?filter=<?= $artist->title() ?>"><?= $artist->title() ?></a>
<?php endforeach ?>

<hr>

<!-- 👇🏻 Works but ugly output like "…/news?filter=artists/andrea-buettner" -->
Filter 2: 
<?php foreach ($filters as $filter): ?>
<a href="<?= $page->url() ?>?filter=<?= $filter ?>"><?= $filter ?></a>
<?php endforeach ?>

<hr>

<div class="grid">
  <?php foreach ($articles as $item): ?>
  <article>
    <?php snippet("post-news", ['item' => $item]) ?>
  </article>
  <?php endforeach ?>
</div>

CleanShot 2022-04-13 at 21.49.48


Side question: why the urlencoding? With urlencoding it renders a + instead of the whitespaces. Is this okay? I ask because on another project in progresss – Architektur Forum Ostschweiz – the filtering is working without urlencoding.

This only works with ids, to filter by the titles, you have to use the filter code I posted above, repeating it here again:

$articles  = $articles
    ->when($filterBy, fn ($filterBy) => $this->filter(fn ($item) => $item->artists()->toPages(',')->findBy('title', $filterBy)));


If you urlencode() the title, you need to urldecode() it again in the controller, if it works without, then all good. I always use urlencoding/deconding

Thank you so much for your help.

Coming from Wordpress with Twig/Timber this is the first more complex project with Kirby and I have a lot to learn.