Stacking Filtering & Sorting with URL Parameter

Hello, I am trying to both do filtering and sorting of articles at the same time. I did everything like it says in this Cookbook entry and managed to get filtering working to my liking. Now I want to sort these entry via links on the page the same way i am filtering. I guess the best way to go would to append the sort url parameter?

I already got it sorted by date without it breaking.

My Controller:

<?php 

return function($page) {

// Here i am doing the sorting
$works = $page->children()
              ->listed()
              ->sortBy(function ($page) {
                return $page
              ->year()
              ->toDate();}, 
        'desc'
        );;

$filters = $works->pluck('category', ',', true);

if($filter = param('filter')) {
  $works = $works->filterBy('category', $filter, ',');
}

return compact('works', 'filters', 'filter');

};

My Template:

...

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

...

What’s is the best way to go?

Yes, a second parameter for sorting would be an option.

Is there something built into kirby to append URL parameters?

You mean add additional params to the existing params, I guess. So when the filter param is active, also add sorting?

<a href="<?= $page->url() ?>?filter=">All</a>
      <?php foreach ($filters as $filter): ?>
        <a href="<?= url($page->url(), ['params' => ['filterby' => $filter, 'sortby' => param('sortBy']]) ?>">
            <?= html($filter) ?>
        </a>
      <?php endforeach ?>
<a href="<?= url($page->url(), ['params' => ['filterby' => param('filterby'), 'sortBy' => 'title']]) ?>">
    <?= 'Sort by title' ?>
</a>

In controller:

return function($page) {

        $filterBy = param('filterBy');
        $sortBy   = param('sortBy');

        $works = $page
        ->children()
        ->listed()
        ->when($filterBy, fn($filterBy) => $this->filterBy('category', $filterBy))
        ->when($sortBy, function($sortBy) {
            // depending what you want to sort by use conditions here
            return $this->sortBy($sortBy);
        });



    return [
        'filterBy' => $filterBy,
        'sortBy'   => $sortBy,
        'works'    => $works->paginate(6),
        'filters'  => $works->pluck('category', ',', true),
    ];

};

Thank you so much! Unfortunately i got sick yesterday, so I couldn’t try it out so far. But will once i am better!

Get well soon :pray:!

Thanks, I am a bit better!

This works, thank you so much!

Now if I want it to sort either descending or ascending, do i have to introduce an additional param, or is there a simpler solution?

Probably another parameter, yes, but depends on how you want to set it up in the frontend.

I would like it to “cycle” through ascending and descending. So you click the link, adding the parameter to the url (including e.g. ascending) and on the next click it would change that parameter to descending.

Yes, that would be possible, e.g.

<a href="<?= url($page->url(), ['params' => ['filterBy' => $filterBy, 'sortBy' => 'title,desc' === $sortBy ? 'title,asc' : 'title,desc']]) ?>">
    <?= 'Sort by title' ?>
</a>

In your controller, you would have to explode the param:

return function($page) {

    $filterBy       = param('filterBy');
    $sortParams     = param('sortBy');
    $sort           = explode(',', $sortParams);
    $sortBy         = $sort[0] ?? null;
    $direction      = $sort[1] ?? null;

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

    // filter conditionally
    $notes = $works
        ->when($filterBy, fn ($filterBy) => $this->filterBy('tags', $filterBy, ','))
        ->when($sortBy, fn ($sortBy) => $this->sortBy($sortBy, $direction));


    return [
        'filterBy'       => $filterBy,
        'sortBy'         => $sortParams,
        'works'          => $works->paginate(6),
        'filters'        => $works->pluck('category', ',', true),
    ];

};

Hmmm i cant get it to work. I don’t understand what function $notes = $works serves here. Right now passing the params to the URL works fine, but neither sorting nor filtering is applied to $works.

Also i have trouble figuring out when the syntax would allow spaces and when it would not. For example here:
fn ($filterBy)

$notes should be $works, I forgot to rename it from my example code

 $works = $works
        ->when($filterBy, fn ($filterBy) => $this->filterBy('tags', $filterBy, ','))
        ->when($sortBy, fn ($sortBy) => $this->sortBy($sortBy, $direction));

It doesn’t matter, just coding style

Ok got it to work! Thanks so much!

Another thing came up.

How can I add an arrow depending on what direction it is sorting?

I’ve already looked through the forum and came up with this:

<?php e($sortBy === param('sortBy'), 'class="active"', '') ?>

But this only checks if sorting is being done or not. How can it check the specific sortBy Parameter?

Add a class depending on the value of $sortBy:

class="<?= $sortBy === 'title,desc' ? 'sort-desc' : 'sort-asc' ?>"
1 Like

Awesome! This works! One last thing: Is it possible to have “default” sorting? So the initial is not unsorted but rather already being sorted by e.g. date? Thank you so much, this forum is really the best!

Well, yes, of course you can filter the original collection before applying the other filters conditionally.

       $works = $page
        ->children()
        ->listed()
        ->sortBy('whatever')
        ->when($filterBy, fn($filterBy) => $this->filterBy('category', $filterBy))
        ->when($sortBy, function($sortBy) {
            // depending what you want to sort by use conditions here
            return $this->sortBy($sortBy);
        });