Filtering a Pagination

Hi all!
I have a list of events grouped and paginated by 30 days.
Basically, every time the user goes to the next page will be shown an additional 30 days list of events.

I was wondering if there any chance to apply a filter tag on pagination or if It needs some Javascript?

Well, usually you would first apply the filter, then paginate, not the other way round.

That’s right.
So I need something more like Ajax?

To me it’s not quite clear what you want to achieve and what the result should be. Do you want to be able to filter within each pagination page? Or do you want to filter the complete list?

I would like to filter the complete list, the pagination is not needed actually but instead a “load more” button that is always visible…

The filtering would take place in the controller, other than the additional filter, you can then follow the Load more recipe.

Hi @texnixe, I followed the Load More recipe and it’s quite perfect.
What is not properly working is that the “Load more” still shows events even if there are no more in the list.

Other than that the URL (page:2 / page:3 etc…) shows always the same events.
Do I miss something obvious?

The list of events is grouped by date.

Probably :wink:. But without knowing your code, I don’t know. To me it sounds as if you were not updating the list nor the button.

It is gonna be a bit long, sorry about that.

HTML Template

  • /site/templates/events.php :
    <div class="col-full events" data-page="<?= $pagination->nextPage() ?>">
      <div class="row-event-wrap">
        <?php foreach ($events as $day => $eventsPerDays) : ?>
          <?php snippet('event', ['day' => $day, 'eventsPerDays' => $eventsPerDays]) ?>
        <?php endforeach ?>
    <div class="load-more">more</div>
  • /site/snippets/event.php
// The events' content

HTML controller

  • /site/controllers/events.php

return function ($page) {
  $limit    = 2;
  $callback = function ($p) {
    return $p->date()->toDate('d M');
  $events = $page->children()->listed()->filterBy('template', 'in', ['event-item', 'festival-item'])->sortBy('date', 'asc')->group($callback)->paginate($limit);

  return [
    'limit'      => $limit,
    'events'   => $events,
    'pagination' => $events->pagination(),

JSON controller

  • /site/controllers/events.json.php

return function ($page) {
  $limit      = 2;
  $callback = function ($p) {
    return $p->date()->toDate('d M');

  $events   =
    $page->children()->listed()->filterBy('template', 'in', ['event-item', 'festival-item'])->sortBy('date', 'asc')->group($callback)->paginate($limit);
  $pagination = $events->pagination();
  $more       = $pagination->hasNextPage();

  return [
    'events' => $events,
    'more'     => $more,
    'html'     => '',
    'json'     => [],

JSON template

  • /site/templates/events.json.php

foreach ($events as $day => $eventsPerDays) {
  $html .= snippet('event', ['day' => $day, 'eventsPerDays' => $eventsPerDays], true);

$json['html'] = $html;
$json['more'] = $more;

echo json_encode($json);


  • assets/js/templates/events.js
document.documentElement.classList.replace("no-js", "js");
document.addEventListener("DOMContentLoaded", () => {
  const element = document.querySelector(".events");
  const button = document.querySelector(".load-more");
  let page = parseInt(element.getAttribute("data-page"));

  console.log({ element, button, page });

  const fetchProjects = async () => {
    let url = `${window.location.href}.json/page:${page}`;
    try {
      const response = await fetch(url);
      const { html, more } = await response.json();
      console.log({ html, more });
      button.hidden = !more;
      element.innerHTML += html;
    } catch (error) {
      console.log("Fetch error: ", error);

  button.addEventListener("click", fetchProjects);

The problem is that the JSON always shows the same 2 events…

What do you get in the browser, when you call and

Wether I call (domain/it/calendario.json) or (domain/it/calendario.json/page:2)this is the HTML:

What is in your event snippet?

Here’s the event details grouped and then sorted by date:

 <div class="events-per-date">
   <div class="event-data"><?= $day ?></div>
   <?php foreach ($eventsPerDays as $item) : ?>
     <div class="event-card <?= $item->city() ?> <?= $item->locations() ?> <?= str_replace(',', ' ', $item->category())  ?>">
       <div class="event-location">
         <?php foreach ($item->locations()->split() as $location) : ?>
             <?= $location ?>
         <?php endforeach ?>

       <div class="event-info">
         <div class="hours">
           <?= t('hours') ?>:
           <?= $item->time_start()->toDate('H:i') ?>-<?= $item->time_end()->toDate('H:i') ?>
   <?php endforeach; ?>

Hm, in general, this all looks ok to me.

Which Kirby version are you using? Feel free to send me a link to your project for testing.

Very appreciate! where should I send you the project link?
By the way, I’ve tested with Kirby 3.6.2 and now 3.6.6.

You can send me a link to a download via PM or if you have a repo, give me access to the repo.

1 Like

What I found is that it doesn’t work because of the grouping with callback in combination with the pagination.

I changed it to:

  $events = $page
      ->filterBy('template', 'in', ['event-item', 'festival-item'])
      ->sortBy('date', 'asc')

in both controllers.

To still show the date as d M in the snippet, change <?= $day) ?> to

<?= date('d M', strtotime($day)) ?>

I haven’t quite figured out why it doesn’t work otherwise. One thing I found is that the pagination object is of Kirby\Toolkit\Pagination with your original code, and of Kirby\Cms\Pagination when using the above code.

1 Like

Here it is!
Thank you very much.