Pre-format information from page fields for a clean template

Hi!

Tldr below, but let’s say I have some pages on my site which is an archive of events at a gallery. Now, i want to list them all on the front page, but with dates and other information arranged depending on different factors: i.e if the event was in the past, it will be shown as “Past event A with 2 participants happened from June 3, 2018 to June 4, 2018”, for a future event “Future event B with 10 participants will happen on January 1st”.

So far, so good. The tricky parts for me:

  1. I want to format the shown date in a way that i.e if the year of the formatted starting date is the same as the year of the ending date, the year will not be shown (June 3 to June 4, 2018 instead of June 3, 2018 to June 4, 2018)
  2. if there are less than, say, 4 participants, i want to show “Event A with Person A, B, C”, if not, i want to show “Person D & E: Event B”
  3. the events should be visibly grouped in categories on the front page with a corresponding headline: “Upcoming: A, Current: B, Past: C, D, E…”

I would like to know how and if it could be possible to use the page controller (?) to save the pre-formatted data in a simpler way than what I am doing now:

<?php foreach ($past as $event): ?>
  <div class="event">
<?php if ($event->hasImages() != 0): ?>
  <a href="<?= $event; ?>"><?= $event->title(); ?></a>
  <?php if ($event->mainimage()->isNotEmpty()) {                 
    $image = $event->mainimage()->toFile();
    echo '<div class="bgimg" style="background-image: url(' . $image->url(). ')"></div>';
  } ?>
  <p>
<?php else: ?>
  <p><?= $event->title(); ?><br>
<?php endif; ?>
<?php if ($event->artists()->isNotEmpty()):
  echo $event->artists() . '<br>';
endif; ?>

<?php
echo $event->date('F j, Y', 'startDate')
." <span class='stretch'>—</span> "
.$event->date('F j, Y', 'endDate');
?>

  </p>
</div>
<?php endforeach; ?>

and inside the home.php controller:

$past = $exhibitions->filter(function($event) {
if (strtotime($event->endDate()) < time()) {
  return $event;
}
});

Of course this is only the past events, so the whole thing is repeated in a similar way a painful three times in my home template. Plus I would obviously have to add more and more loops to reach all my goals but before i get completely lost, I was hoping to find a solution to be able to save the pre-formatted data in a way so I could echo them in my template like i.e foreach ($events->filter()->upcoming() as $event) {echo $event->dates()->format()} etc, or something comparably straightforward — but this is where my php and kirby knowledge ends… I would love to know what could be possible here!

tldr: can I group my pages into upcoming, current and past and echo them somehow similar to foreach ($events->filter()->upcoming() as $event) {echo $event->dates()->format();}?

I’d be super grateful for any hints!

All the best!

Check out the examples here: https://getkirby.com/docs/cookbook/sorting-and-grouping#complex-grouping

The second one in that section does sort of the same thing, you just would have to adapt it to your use case. You then get three groups to loop through. You would define that stuff in your controller, then do the looping in your template.

For the other logic, with events’ participants, you can probably outsource some of the logic into custom functions.

On a side note: Try not to echo HTML elements (bad coding style). And this:

<?php if ($event->mainimage()->isNotEmpty()) {                 
    $image = $event->mainimage()->toFile();
    echo '<div class="bgimg" style="background-image: url(' . $image->url(). ')"></div>';
  } ?>

can be shorter:

<?php if(  $image = $event->mainimage()->toFile()): ?>
  <div class="bgimg" style="background-image: url(<?= $image->url() ?>)"></div>
<?php else: ?>
  <!-- rest of code -->
<?php endif ?>

No need to check if the field is empty first, because if it is, not image will be returned from the toFile() method.

1 Like

Thank you so much for the prompt answer @texnixe ! I will have a look into this!

I’m currently revisiting this project. Thanks again for the input; grouping worked very well & I’m trying to abandon my bad coding style as well as I can ;~)
I’m now looking to sort the $groups output, but I can’t get it to work. My home.php controller has:

$groups = page('exhibitions')
->children()
->visible()
->sortBy('startDate', 'desc')
->group(function($exhibition) {

$now = new DateTime('now');
$start = new DateTime($exhibition->startDate());
$end = new DateTime($exhibition->endDate());

if ($start > $now)                  return 'Upcoming';
if ($start < $now && $end > $now)   return 'Current';
return 'Past';

});


  return [
    'groups' => $groups,

  ];

which outputs the groups in a mostly working order, but if there’s an entry for “Past” which is newer than an entry for “Current”, the whole section will be displayed before “current” which obviously doesn’t really make sense for the user. I would like to always show them in the order 1. Upcoming, 2. Current, 3. Past.

I see there’s a way to create custom ordering in the controllers as is shown in this example https://getkirby.com/docs/cookbook/content-structure/sorting#custom-sorting buuuuut I’m not really able to abstract from this…
My best try was to add something like this before my final return:

$groups = $groups->map(function($item) {
  $names = ['Upcoming' => '0', 'Current' => '1', 'Past' => '2'];
  // $item->order = ???????????
  return $item;
}); 

but as you can tell I am lost… How could I achieve my task? Grateful for any hints!

All the best

In this case, I think you should assign your categories (upcoming etc) first within the map() methods callback, not in the grouping callback

$groups = page('exhibitions')
->children()
->visible()
->sortBy('startDate', 'desc')
->map(function($exhibition) {

$now = new DateTime('now');
$start = new DateTime($exhibition->startDate());
$end = new DateTime($exhibition->endDate());

if ($start > $now)    {
  $exhibition->category =  'Upcoming';
  $exhibition->sortOrder = 0;
}
if ($start < $now && $end > $now)  {
   $exhibition->category =  'Current';
    $exhibition->sortOrder = 1;
} else {
  $exhibition->category =  'Past';
   $exhibition->sortOrder = 2;
}
 return $exhibition;

})->sortBy('sortOrder', 'asc')->groupBy('category');

Not tested.

Okay, I believe I get the idea of this – however, I receive the error “Call to a member function sortOrder() on null”. Does this mean I have to add a sortOrder field to all the pages or am I completely mistaken?

Yes, it doesn’t work if the field to group by is empty.

:+1: cool! Before I go ahead and save all pages manually, is there a script to add the field to all existing pages?

Yes, you can use $page->update() in a loop.

http://k2.getkirby.com/docs/cheatsheet/page/update

Thank you so much! Will try it out and let you know if it worked.

I updated all the pages (double checked if it worked by echoing the sortOrder in the frontend) but the error persists :confused: I don’t suppose it would have anything to do with the predefined sortOrder, because that should be updated through the page controller, no?

Are we talking about Kirby 2 here?

Yes!

Are you using the exact same code as posted above? Please post it just in case…

my whole home.php controller looks like this:

<?php

date_default_timezone_set('Europe/Vienna');

return function($site, $pages, $page) {

  $groups = page('exhibitions')
    ->children()
    ->visible()
    ->sortBy('startDate', 'desc')
    ->map(function($exhibition) {

$now = new DateTime('now');
$start = new DateTime($exhibition->startDate());
$end = new DateTime($exhibition->endDate());

if ($start > $now)    {
  $exhibition->category =  'Upcoming';
  $exhibition->sortOrder = 0;
}
if ($start < $now && $end > $now)  {
   $exhibition->category =  'Current';
    $exhibition->sortOrder = 1;
} else {
  $exhibition->category =  'Past';
   $exhibition->sortOrder = 2;
}

})->sortBy('sortOrder', 'asc')->groupBy('category');

  return [
    'groups' => $groups,

  ];

};

Ah, I know what the problem is, there’s a return statement missing within the map() method:

<?php

date_default_timezone_set('Europe/Vienna');

return function($site, $pages, $page) {

  $groups = $page
    ->children()
    //->visible()
    ->map(function($exhibition) {

$now = new DateTime('now');
$start = new DateTime($exhibition->startDate());
$end = new DateTime($exhibition->endDate());

if ($start > $now)    {
  $exhibition->category =  'Upcoming';
  $exhibition->sortOrder = 0;
}
if ($start < $now && $end > $now)  {
   $exhibition->category =  'Current';
    $exhibition->sortOrder = 1;
} else {
  $exhibition->category =  'Past';
   $exhibition->sortOrder = 2;
}
return $exhibition;
})->sortBy('sortOrder', 'asc');

$groups = $groups->groupBy('category');

  return [
    'groups' => $groups,

  ];

};

Let me know if it works for you with the fix, otherwise, feel free to send me your project so I can look into it more closely.

Unfortunately, it didn’t work… it is now telling me “Invalid grouping value for key:” + the title of the first page I want to show. Thank you for offering your help, how can I send you my project folder?

You could zip it up and send me a download link via PM…

@bruno Hm, what you sent me doesn’t throw any errors?