Access nested foreach with toStructure()

I’m using the “Structured Field Content” method for my menu carts to structure my content.
With <?php a::show($menus) ?> I got a nice looking array object:

Array
(
[0] => Array
    (
        [Headline] => Vorspeisen
        [Subline] => Subline
        [ID] => 1
        [0] => Array
            (
                [Title] => Ruccola in Mangovinaigrette
                [Description] => mit gebratenen Riesengarnelen und Baguette
                [Price] => 9,00
            )

        [1] => Array
            (
                [Title] => Tomaten Bruschetta
                [Description] => mit Ruccola, Knoblauch, Olivenöl & Grana Padano auf Röstbrot
                [Price] => 4,60
            )

    )

[1] => Array
    (
        [Headline] => Suppen
        [Subline] => Subline
        [ID] => 2
        [0] => Array
            (
                [Title] => Flädlesuppe
                [Description] => mit gebratenen Riesengarnelen und Baguette
                [Price] => 3,40
            )

        [1] => Array
            (
                [Title] => Curryschaumsuppe
                [Description] => mit gebratenem Putenspieß
                [Price] => 4,60
            )

    )

[2] => Array
    (

Unfortunately, I don’t know how to access the nested arrays like Title, Description and Price.

For the first level Headline, Subline and ID I’m using succesfully <?php $menus = yaml($item->menus()) ?> with <?php echo $menu['Headline'] ?>.

How to access the deeper levels?

Ok, I’m using toStructure() now and getting this:

<?php foreach($item->menus()->toStructure() as $menu): ?>
	<?php echo $menu->headline() ?> <br>
	<?php echo $menu->subline() ?> <br>
	<?php echo $menu->id() ?> <br>

	<?php foreach ($menu as $foo) {
		echo $foo->title()->kirbytext();
		echo $foo->description()->kirbytext();
		echo $foo->price()->kirbytext();
	} ?>
<?php endforeach ?>

Unfortunately, the nested foreach prints the headline, subline and id too.

Any suggestions how to output only the content of the nested array in the nested foreach?

Not sure, what you want to do, but you could just remove

<?php echo $menu->headline() ?> <br>
<?php echo $menu->subline() ?> <br>
<?php echo $menu->id() ?> <br>

from your code. If u want this to be your headline, you could do sth like:

<?php $is_first = true; ?>
<?php foreach($item->menus()->toStructure() as $menu): ?>
   
<?php if($is_first == true): ?> 
  <?php echo $menu->headline() ?> <br>
  <?php echo $menu->subline() ?> <br>
  <?php echo $menu->id() ?> <br>
  <?php is_first = false; ?>
<?php endif; ?>

<?php foreach ($menu as $foo) {
	echo $foo->title()->kirbytext();
	echo $foo->description()->kirbytext();
	echo $foo->price()->kirbytext();
} ?>
<?php endforeach ?>

Hi Philipp,

I’m trying to build a restaurant menu cart from this yaml:

Where the Headline , Subline and ID loops through the first foreach and Title, Description and Price loops through the nested foreach.

(I’m not even sure if my yaml is valid)

This code loops the nested foreach, but also through the first one:

<?php foreach($item->menus()->toStructure() as $menu): ?>
	<?php echo $menu->headline() ?> <br>
	<?php echo $menu->subline() ?> <br>
	<?php echo $menu->id() ?> <br>

	<?php foreach ($menu as $foo): ?>
		<?php echo $foo->title()->kirbytext() ?> <br>
		<?php echo $foo->description()->kirbytext() ?> <br>
		<?php echo $foo->price()->kirbytext() ?> <br>
	<?php endforeach ?>
<?php endforeach ?>

i cant test it right now, but texnixe posted the following code a while back: It looks quite similar to what you are trying to archive. Btw. i am becoming hungry, menu looks very tasty :wink:

<table>
  <?php foreach($page->training()->toStructure() as $item): ?>
  <?php $count = count($item) ?>
    <tr>
      <?php foreach($item as $key => $detail): ?>
        <td><?= $detail ?></td>
      <?php endforeach ?>
    </tr>
  <?php endforeach ?>
 </table>

Unfortunately the documentation is limited to not nested yaml / structured field, since nesting structured fields in the panel is not possible:

https://getkirby.com/docs/cookbook/structured-field-content

1 Like

In your example, $menu->id() already prints all the menu items, because that element is nested under id…

<?php foreach($page->menus()->toStructure() as $menu): ?>
  <?php echo $menu->headline() ?> <br>
  <?php echo $menu->subline() ?> <br>
  <?php echo $menu->id()->value() ?> <br>
    <?php foreach($menu->id() as $item) {
      echo $item->title() . '<br>';
      echo $item->description() . '<br>';
    } ?>
<?php endforeach ?>

A little dump() or var_dump() -ing of stuff helps a lot to debug these kinds of issues…, just saying :wink:

And as @philipp already pointed out, hope you are not planning to use the Panel with that sort of nested content.

1 Like

@texnixe
Actually it should be fully accessible through the panel via blueprints :wink:

Is it not possible that way?

No, the structure field does not allow any nesting: https://getkirby.com/docs/cheatsheet/panel-fields/structure

You might want to look into using subpages for the categories and structure fields for the menu items.

Sorry, but I’m stuck on this issue right now :frowning:

I’m not quite sure how to achive the following requirements with kirby:

  • loop through the arrays [Vorspeisen, Suppen, etc]
  • loop through the arrays [Vitello Tonato, Tomaten Bruschetta, etc ]
  • the user should be able to add, edit and delete menu courses (title, description and price) from the panel via the blueprint

My suggestion:

Make subpages:

/menu
  menu.txt --> create menu.yml blueprint that only allows menu-category subpages
  /subpage Vorspeisen
    menu-category.txt
  /subpage Suppen
  /subpage Hauptgerichte

Blueprint menu-category.yml

title: Menu Category
pages: false
fields:
  title:
    label: Titel
    type: text
  subtitle:
    label: Subtitle
    type: text
  menuItems:
    label: Menu Items
    type: structure
    style: table
    fields:
      title:
        label: Title
        type: text
      description:
        label: Beschreibung
        type: textarea
      price:
        label: Preis
        type: number

In template: Loop through subpages of menu:

<?php 
foreach(page('menu')->children() as $child): ?>
  <h1><?= $page->title() ?></h1>
  <h2><?= $page->subtitle() ?></h2>

  <?php 
    $menuItems = $child->menuItems()->toStructure();
    foreach($menuItems as $item):
      echo $item->title()->html();
      echo $item->description()->kt();
      echo $item->price();
    endforeach;
  ?>
<?php endforeach ?>
1 Like

@andalusi:

I support @texnixe’s idea, but I would do the next step and build a page in the panel for every menu table menu or drink one level under the menu-category, that means to build one level more, because I hate to use structure fields for this.

My suggestion means, that every “line of the menu table” (German: “Gericht/Getränk der Speisekarte”) has an own page in the panel and can be changed in the visibility by the normal panel ways.

That means that every “line of the menu table” can have added fields like the “sale time” of this line (e.g. for special lines like asparagus (German: “Spargel”), which is not availible the whole year, because of the protection of this species by law. Or like pictures, aditional tags to prevent showing this cheap produkt in the preview on the homepage and so on.

That means furthermore, that you have to adapt your templates and snippets, may be to prevent showing these deep pages, e.g. in the main menu, the html- and xml-sitemaps and to the viewers of the normal webpages of these levels, but this similarly for @texnixe’s solution.

And you can use different blueprints for eating and drinks, because drinks need something like “0,2l” as an additional information.

Good luck!

I’d only use subpages for each menu item, if you plan to add additional information. For just that little bit of content as in your example, I don’t think it is worth it to create separate pages.

As regards drinks, they would have a different page on the same level as menu anyway.

Seasonal dishes could have their own pages/structure fields.

But it really depends on your use case and what would be easier to handle for the users.

Hello @texnixe and @anon77445132,

thank you very much for your support!

@texnixe
I followed your suggestions and I’m quite happy with the result except of the following:

Right now it loops through the array regardless of the different sections (see screenshot for detail) .

It should be possible to create subpages for the different sections like Vorspeisen, Suppen, Hauptgerichte and than loop the menu courses in it.

content/
content/restaurant
content/restaurant/vorspeisen
content/restaurant/suppen

Must admit that I don’t understand the result. What did you do? Have you created subpages for the different categories like Vorspeisen Hauptgerichte etc. like I suggested?

@texnixe let me explain:

It would be cool to use a bootstrap-modal (like the screenshot above) for the different menu carts.

Each section like Vorspeisen, Suppen of a menu cart is in an bootstrap-collapse to avoid to get lost (because some carts are very long).

Now, it should be possible to create, edit & delete the single courses like we already did.
What is missing are the titles for each sections like ‘Vorspeisen’, Suppen

What I need are two nested loops:

 Vorspeisen
 Vorspeisen/Bruschetta
 Vorspeisen/Ruccola

 Suppen
 Suppen/Flädlesuppe
 Suppen/Curryschaumsuppe

 ... etc ...

Maybe there’s something I don’t get. So you need the title twice? Maybe you can outline the final HTML you need?

@texnixe … just a moment, I will outline the HTML …

@texnixe

This is what I need :smile:

Requirements:

  • Each Bootstrap-Modal is only one kirby blueprint or at least one site in the panel (because there are quite some modals)
1 Like

If you put the required HTML around the code I posted above, you should get exactly what you want (pseudo HTML)

<modal>
  <?php 
  foreach(page('menu')->children() as $child): ?>
  <accordeon>
    <accordeon-title>
      <!-- Here comes the title, Vorspeise, Hauptgerichte etc. -->
      <h1><?= $page->title() ?></h1>
      <h2><?= $page->subtitle() ?></h2>
    </accordeon-title>
 
   <?php 
     // list of items in accordeon
      $menuItems = $child->menuItems()->toStructure();
      foreach($menuItems as $item):
        echo $item->title()->html();
        echo $item->description()->kt();
        echo $item->price();
      endforeach;
    ?>
  </accordeon>
  <?php endforeach ?>

</modal>

Provided that each accordeon (Vorspeise, Hauptgerichte etc.) is a child page of menu (or whatever you call that page) and has a structure field in it.

Unfortunately, it is not so simple because there a a lot of modals.

So, my structure of the content folder are:

  content/

  content/restaurant
  content/restaurant/vorspeisen
  content/restaurant/suppen
  content/restaurant/vegetarisch

  content/bar
  content/bar/vorspeisen
  content/bar/suppen
  content/bar/vegetarisch

  content/catering
  content/catering/vorspeisen
  content/catering/suppen
  content/catering/vegetarisch