Make the most of the structure field

The structure field is great for storing repeated blocks of data like addresses, events, team members, references, and a lot more. In this recipe, we will look at some examples of how to create a form for the field in the Panel, and how to render the data in a template.

Define a structure field in a blueprint

Note: If you don't use the Panel, you can skip this part.

We start with defining a structure field in the blueprint, in this example a pretty simple events field:

title: A New Blueprint
fields:
  events:
    label: Events
    type: structure
    style: table
    fields:
      event_title:
        label: Title
        type: text
      event_date:
        label: Date
        type: date
      event_location:
        label: Location
        type: text
      event_image:
        label: Image
        type: image

The structure field can have any number and sort of fields, except another structure field.

There are some options to use with the structure field:

- entry
- style
- modalsize
- default
- readonly

Find out more about these options in the structure field docs.

Structure field entries in the content file

The data is saved to the content file as YAML structured data. After we have added some data via the Panel, our content file will look somewhat like this:

events:

-
  event_title: Cabramatta Moon Festival
  event_date: 2016-09-04
  event_location: Sydney
  event_image: cabramatta.jpg
-
  event_title: Paris Hip-Hop Festival
  event_date: 2016-07-04
  event_location: Paris
  event_image: hip-hop.jpg
-
  event_title: Pyronale
  event_date: 2016-09-09
  event_location: Berlin
  event_image: pyronale.jpg

Accessing the data in a template

To access these entries in a templates, Kirby provides two methods. The yaml() method returns an array, while the toStructure() method turns your data into a full-blown Kirby collection. Kirby collections are a bit easier to work with, but we will look at how to work with both methods.

Working with a data array

We use the yaml() method to get an array of all our events entries:

<?php
$events = $page->events()->yaml();
?>

Now we can loop through this array and output all events. Note that we access the individual fields by their keys:

<?php
$events = $page->events()->yaml();

foreach($events as $event): ?>

  <h1><?= $event['event_title'] ?></h1>
  <p><?= date('Y-m-d', strtotime($event['event_date'])) ?></p>
  <p><?= $event['event_location'] ?></p>
  <?php if($image = $page->image($event['event_image'])) echo $image->html() ?>

<?php endforeach ?>

When we output the values of the array, we deal with strings, not with an object. We therefore have to use the html() or kirbytext() helpers, and cannot use object notation. So this would not work: <?= $event['event_title']->html() ?>.

Now, what's happening in the following line?

<?= date('Y-m-d', strtotime($event['event_date'])) ?>

The strtotime() function turns our date from the content file into a UNIX timestamp, which we can pass to the PHP date() function to format the date.

In the last line of code before the end of the loop, we output the image:

<?php if($image = $page->image($event['event_image'])) echo $image->html() ?>

We check if the image exists first, and only then we echo it. If you want more control over how the image is rendered, you can of course use an image or figure tag and only pass the URL of the image to the src attribute like this:

<?php if($image = $page->image($event['event_image'])): ?>
  <img class="some-class" src="<?= $image->url() ?>">
<?php endif ?>

After having created our array with the yaml() method, we can use PHP functions or toolkit array functions to do all sorts of manipulations, like sorting, shuffling, or slicing.

Some examples:

$events = $page->events()->yaml();

// get the first element of the array
$first = a::first($events);

// get the last element of the array
$last = a::last($events);

// shuffle the array
$shuffle = a::shuffle($events);

// sort the array by date field
$sorted = a::sort($events, 'event_date', 'desc');

Working with a collection

Now, let's do the same with the toStructure() method:

<?php
// get all events
$events = $page->events()->toStructure();

// loop through the collection of events:
foreach($events as $event): ?>

  <h1><?= $event->event_title()->html() ?></h1>
  <p><?= date('Y-m-d', strtotime($event->event_date())) ?></p>
  <p><?= $event->event_location()->html() ?></p>
  <?php if($image = $event->event_image()->toFile()) echo $image->html() ?>

<?php endforeach ?>

When we use the toStructure() method, we use object notation to access each individual field of the structure field, and we can also use Kirby's chaining syntax with the html(), kirbytext() etc. methods.

Also, we can use the toFile() method to turn the value from our image field into a file object.

Another cool advantage of using the toStructure() method is that we can use all collection methods like shuffle(), sortBy(), limit() etc. on our structure field collection.

Some examples:

$events = $page->events()->toStructure();

// get three random events
$random = $events->shuffle()->limit(3);

// get the first event
$first = $events->first();

// get the last event
$last = $events->last();

// sort events by date
$events = $events->sortBy('event_date', 'desc');

So, why then use the yaml() method at all? One reason would be your personal preference. Here's another one: If we want to add data to a structure field, we need to fetch the data as array, so we can add new data to it and yaml::encode() the array to update the page. For an example, see Creating pages from frontend.

Convert an array to a structure object

Kirby has a pretty useful helper that turns an array into a Kirby structure object: the structure() function. We can use this helper to easily convert an array created with the yaml() method into a structure object:

$events = $page->events()->yaml();
$structureObject = structure($events);

Check out the example in the docs.

Merging structure fields

If several of our pages contain the same structure field and we want to merge all entries, we can do this by creating a new structure object.

To make this reusable, we create a new function that we save as a plugin into /site/plugins/structure.php.

function createNewStructure($pages, $field) {
  // instantiate a new structure object
  $structure = new Structure();

  $key = 0;

  // loop through the pages collection
  foreach($pages as $p) {

    //loop through the structure object of each page
    foreach($p->$field()->toStructure() as $item) {

      // append each entry to the new structure object
      $structure->append($key, $item);

      $key++;
    }
  }

  return $structure;
}

Now whenever we need to merge the structure entries of multiple pages, we just call this function and pass it a collection of pages and the name of a field:

// fetch a collection of pages
$pageCollection = page('events')->children();

// pass the collection of pages and the name of the field to the function
$entries = createNewStructure($pageCollection, 'registrations');

Beyond the structure field

The structure field only handles the same fields for each entry. If you need more flexibility, check out the Kirby Page Builder field by Tim Ötting. It is based on the structure field, but allows you to predefine content blocks with different field sets that can be used as a page builder.