Dynamic menu based on page tags

This seems like it should be easier than I’m making it out to be, but here’s what I’m looking to do: I want to have a page that checks all sub pages for a specific field, and creates a single menu entry based on the value of that field. Pages that share a value result in only one menu entry.

So lets say I have 10 pages, each with a field called “genre.” 3 of those pages have a genre value of “non-fiction” and 7 have a value of “fiction.” When you load the page, it would create a menu item for non-fiction and a menu item for fiction. If I later add a new page with a genre value of “other”, then a third menu item would get generated.

Any thoughts?

You can use the pluck() method:

$pages->pluck($field, $split = null, $unique = false)
<ul>
<?php foreach($page->children()->pluck('genre', ",", 'true') as $genre): ?>
  <li><?php echo $genre->html() ?></li>
<?php endforeach ?>
</ul>

http://getkirby.com/docs/cheatsheet/pages/pluck

Should it be $pages or $page? I’m also not sure what the $split and $unique variables are referencing.

Nevermind, I figured it out based on your code. Really appreciate you pointing me in the right direction.

So the pluck() method is working fine with the main menu, but when I attempt at listing all of the items below, it seems to fail. Here’s the code:

<ul>
  <?php foreach($page->children()->visible()->sortBy('category', 'asc')->pluck('category', null, true) as $cat): ?>
    <div class="subcat"><li><?php echo $cat->html() ?></li>
      <?php foreach($page->children()->visible()-sortBy('category', 'asc') as $subcat): ?>
        <li><?php echo $subcat->html() ?></li>
      <?php endforeach ?>
    </div>
  <?php endforeach ?>
</ul>

For what it’s worth, I’m trying to parse through a bunch of pages and list them as groups (based on a tag). I have a feeling my problem is just a php syntax issue, but I’m also open to suggestions if there is a more efficient method of doing this that’s built into Kirby. Thanks again.

First, your HTML list structure is not correct. It should be something like this:

<ul>
  <li>
    <ul>
      <li> ... </li>
    </ul>
  </li>
</ul>

You can use a div inside an li element, but not inside ul.

Secondly, this line does not make sense:

<li><?php echo $subcat->html() ?></li>

It should be:

<li><?php echo $subcat->title()->html() ?></li>

Or whatever field you want to display.

BTW: I suggest you turn on debugging in your config.php if you haven’t already.

c::set('debug', true);

Or check the php error logs of your server.

title() was what I was missing. I also had a typo but didn’t update the post earlier to reflect that. One thing that I’ve been really struggling with the last few days is Kirby documentation. A lot of it seems to be missing, although maybe I’m just not looking in the right place. For example, trying to figure out the difference between $page and $pages means going through the docs section page by page, because searching under the term “page” just comes up with all kinds of stuff. I know I saw it somewhere, but my brain is too fried to remember where.

Also, I was hoping to find some information about the pluck() method, in terms of what $field, $split, and $unique reference ($field is obvious enough though). I figured out what $unique does, although I don’t know why (seems to only pick one of each possibility if true), and $split only worked for me if left blank. I have no idea where to find more info on that (and other details like it) :confused:

Yes, you are right, the documentation is not always very elaborate, but we are working on it (at least I will try to update the pluck() method docs ASAP.

Also, it is often more helpful to use Google search than the internal search to find sth. in the Kirby docs :worried:

As for the different methods available for $page (a single page), $pages (a page collection), $site, etc., the cheatsheet is the best place to find such information, although not always helpful when it comes to options. And the source code, of course.

OK, so I’ve taken it down to the following two examples.

Example 1:

<ul id="cat">
  <?php foreach($page->children()->visible()->sortBy('category', 'asc')->pluck('category') as $cat): ?>
    <li><?php echo $cat->title()->html() ?></li>
      <ul><?php foreach($page->children()->visible()->sortBy('title', 'asc') as $subcat): ?>
        <?php if($subcat->category() == $cat->category()): ?>
         <li><?php echo $subcat->title()->html() ?></li></ul>
       <?php endif ?>
      <?php endforeach ?>
  <?php endforeach ?>
</ul>

Example 2:

<ul id="cat">
  <?php foreach($page->children()->visible()->sortBy('category', 'asc')->pluck('category', null, true) as $cat): ?>
    <li><?php echo $cat->title()->html() ?></li>
      <ul><?php foreach($page->children()->visible()->sortBy('title', 'asc') as $subcat): ?>
        <?php if($subcat->category() == $cat->category()): ?>
         <li><?php echo $subcat->title()->html() ?></li></ul>
       <?php endif ?>
      <?php endforeach ?>
  <?php endforeach ?>
</ul>

The key difference between the two is in the following statements on line 2, regarding pluck().

pluck('category')
pluck('category', null, true)

What I’ve found is that with the first example (the unique attribute is set to false), I get a menu that lists the category, then the first page with that category. It then lists the category again, with the second page with that category, etc.

So it the result looks something like this:

Fiction
Of Mice and Men
Fiction
Enders Game
Fiction
1984
Non-fiction
Dead Wake
Non-Fiction
Red Notice

With the second example (the unique attribute is set to true), I get this:

Fiction
Of Mice and Men
Non-fiction
Dead Wake

Is pluck() maybe not the best option here?

Pluck() is the right method, but your nested list html code and the php code are still not correct, try this:

<ul id="cat">
  <?php foreach($page->children()->visible()->sortBy('category', 'asc')->pluck('category', null, true) as $cat): ?>
    // open list element
    <li><?php echo $cat->html() ?>
     // open ul for list of articles
      <ul>
       <?php foreach($page->children()->visible()->sortBy('title', 'asc') as $subcat): ?>
         <?php if($subcat->category()->toString() == $cat): ?>
          <li><?php echo $subcat->title()->html() ?></li>
        <?php endif ?>
      <?php endforeach ?>
    //close ul after the second foreach loop
    </ul>
    //close the list element after the nested list
   </li>
  <?php endforeach ?>
</ul>

First of all, thank you for the help understanding this.

So it looks like the key change was in

<?php if($subcat->category()->toString() == $cat): ?>

What was it about my version of that line which didn’t work properly? Hopefully knowing “why” will help me in the future. I was thinking that

<?php if($subcat->category() == $cat->category()): ?>

made sense, but obviously it’s not working as I expected (comparing the category value for each).

$page->field() returns an object, not a string; so you cannot compare that to a string in the if-statement.

The second thing was your $cat->title()->html() code, which does not make sense, because in that first loop you do not loop through the pages with their fields but through an array of categories.

The third thing was wrong nesting of list.

OK, that makes a little more sense. Thank you again.

Instead of the if statement, you can also use the filterBy() method:

<?php
  $pages = $page->children()->visible()->filterBy('category', '!=', "")->sortBy('title', 'asc');
?>
<ul id="cat">
  <?php foreach($pages->pluck('category', null, true) as $cat): ?>
    <li><?php echo $cat->html() ?>
      <ul>
       <?php foreach($pages->filterBy('category', $cat->toString()) as $subcat) : ?>
          <li><?php echo $subcat->title()->html() ?></li>
      <?php endforeach ?>
    </ul>
   </li>
  <?php endforeach ?> 
</ul>

BTW: Should you come across the groupBy() function in the docs and think, well, that would have made my life much easier: Unfortunately, the groupBy() function (still) does not work.

The issue with the groupBy() method will be fixed in the next Kirby release.

It was just fixed in the development branch, so if you want to test it you can use that branch.

I’ll test it in a bit. The previous solution worked really well once Texnixe pointed out the errors in my code, and I was able to implement a menu that scales perfectly for my project, but it would be helpful to know more about the (now working) groupBy() method.

Having said that, I’m curious what the intention is between groupBy(), filterBy(), and sortBy() with Kirby. Like, what were the use cases envisioned with each that warranted separate methods?

  • groupBy() offers an easy way, well, to group a collection of pages by a particular field. The syntax is as follows (the example groups the subpages of the projects page by the field year):
<?php
$years = page('projects')->children()->groupBy('year');
foreach($years as $year => $items): ?>
    <h2><?php echo $year ?><h2>
    <ul>
      <?php foreach($items as $item) : ?>
      <li><?php echo $item->title() ?></li>
      <?php endforeach; ?>
    </ul>
<?php endforeach ?>

This example would output each year as headline and list all articles with the corresponding year below.

groupBy() thus output the same number of pages as in the original collection.

  • filterBy() filters a collection and returns only the pages that meet the filter criteria, e.g. all pages in the collection that use a specific template, all pages where year = “2015”, etc. The number of pages this method returns can be lower than in the collection that is being filtered.
  • sortBy() sorts all pages in the collection according to one or more criteria, e.g. by date, alphabetically etc. The number of pages returned is the same as in the original collection.

Of course, the methods can also be used in combination.

1 Like