🚚 How to migrate to Kirby Tips & tutorials
Skip to content

Fun with loops and collections

Foreach loops are needed everywhere. No matter if you build a menu, a simple list of subpages, a gallery, a table of contents, in short, whenever dealing with collections, arrays etc. – foreach loops are your best friends.

A simple menu

In this example, we build a very basic menu for all listed main pages in the content folder.

<nav>
  <ul>
    <?php foreach($pages->listed() as $item): ?>
      <li><a href="<?= $item->url() ?>"><?= html($item->title()) ?></a></li>
    <?php endforeach ?>
  </ul>
</nav>

As you can see, this is just a matter of seven lines of code. As soon as you want to get some more control over each item in the loop, it can quickly get very complex.

Anatomy of a set of pages

Whenever you get a set of pages in Kirby, like:

$items = $pages->listed();

// or

$items = $pages->find('projects')->children();

The result ($items) is not just a plain, boring array. Kirby has its own array implementation, which comes with a few nice methods that make your life easier.

First and last items

In many situations, we have to get the first or the last item, for example, to add a special class to these items (to style them or for other reasons). This is very easy with Kirby's methods:

First element in a set of pages

$items = $pages->listed();

// get the first element in a set of pages
$first = $items->first();

// get the title of the first item in a set of pages
echo $items->first()->title();

// this can be done with any variable of the first page

Last element in a set of pages

$items = $pages->listed();

// get the last element in a set of pages
$last = $items->last();

// get the title of the last item in a set of pages
echo $items->last()->title();

// this can be done with any variable of the last page

With these methods we can create a nicer version of the menu above, with a class added to one of these items.

Let's do that menu thing again

<?php

$items = $pages->listed();
$last  = $items->last();

?>
<nav>
 <ul>
   <?php foreach($items as $item): ?>
   <li<?php if($item == $last) echo ' class="last"' ?>><a href="<?= $item->url() ?>"><?= html($item->title()) ?></a></li>
   <?php endforeach ?>
 </ul>
</nav>

This can be modified to work for the first item instead…

<?php

$items = $pages->listed();
$first = $items->first();

?>
<nav>
 <ul>
   <?php foreach($items as $item): ?>
   <li<?php if($item == $first) echo ' class="first"' ?>><a href="<?= $item->url() ?>"><?= html($item->title()) ?></a></li>
   <?php endforeach ?>
 </ul>
</nav>

Search a set of pages

You can search an element within a set of pages with the find() or the findBy() methods:

$items = $pages->find('projects')->children();

// let's search for a specific element in that set of pages by it's uid
$needle = $items->find('project-2');

// or let's do that search by title:
$needle = $items->findBy('title', 'Project 2');

Both methods will only return a single page object, like the first() and last() methods.

We can use that search result to assign a class selector in our menu to a specific item:

<?php

$items  = $pages->listed();
$needle = $items->findBy('title', 'Contact');

?>
<nav>
 <ul>
   <?php foreach($items as $item): ?>
   <li<?php if($item == $needle) echo ' class="special-item"' ?>><a href="<?= $item->url() ?>"><?= html($item->title()) ?></a></li>
   <?php endforeach ?>
 </ul>
</nav>

Doesn't sound useful? Think about it again! You can use this to customize single items in any kind of menu, list, gallery, etc. without writing huge amounts of extra code. Since the findBy method can search for all custom fields in your content file for each page, this can be very powerful. Just use your imagination :)

Get an item by position in the collection

You can also fetch a particular item by its position in the collection, using the nth() method:

$items  = $pages->listed();
$item = $items->nth(3); // get the 4th element in the collection (index starts at 0)
echo $item->title();

Get the index number of an element in the collection

In some situations it is useful to get the index number of an element in the collection, for example to add the number to a class name for a gallery or something like this. This can be achieved with the indexOf() method:

<?php $items = $page->children()->listed(); ?>
<ul>
  <?php foreach($items as $item): ?>
  <li class="item-<?= $items->indexOf($item) ?>"><?= $item->title() ?></li>
  <?php endforeach ?>
</ul>

Limiting the number of items

With the limit() and offset() methods we can pick only a number of elements from our collection:

// only get the first three items
$items = $page->children()->listed()->limit(3)

// or get the first and then the rest
$items = $page->children()->listed();
$firstItem = $items->first();
$allOtherItems = $items->offset(1); // all but the first item

Excluding elements

We can also exclude elements, for example to get all main pages for our menu, no matter if they are listed or unlisted, but not the error and home pages. This can be done with the not() method:

$items = $pages->not('error', 'home');
// then loop through them to build a list

Not just pages

The same methods outlined above are not only available for pages, but also for collections of files, users, structure items or any other collection.

More methods in the Reference

Author