Branch menu as submenu with excluded sub(-sub)pages, displayed on one level

Hello everybody,

I am using a treemenu snippet, as suggested here: https://github.com/bastianallgeier/kirbycms-extensions/tree/master/snippets/treemenu.

My snippet:

<?php if(!isset($subpages)) $subpages = $pages->find('artists')->children() ?>
<ul>
  <?php foreach($subpages->visible() as $p): ?>
  <li class="depth-<?php echo $p->depth() ?>">
    <a<?php echo ($p->isActive()) ? '
	     class="active"' : '' ?> href="<?php echo $p->url() ?>"><?php echo $p->title() ?></a>
    <?php if($p->hasChildren()): ?>
    <?php snippet('treemenu', array('subpages' => $p->children())) ?>
    <?php endif ?>
  </li>
  <?php endforeach ?>
</ul>

Background:
A website for an exhibition, with a representation space for each artist. The artist has the choice to start with a template (default, portfolio, blog) and then (if desired) add more pages to this page .

This is the topography for the “artists” page and hierarchy:

home/    
  artists/
      artist 1 = default/ (first level)
        subpage = blog (second level)
          sub-subpage = blogarticle/
        subpage = portfolio/
          sub-subpage = portfolioitem/
      artist 2 = blog/
        subpage = blogarticle/
        subpage = default/
        subpage = portfolio/
          sub-subpage = portfolioitem/
      artist 3 = portfolio/
        subpage = portfolioitem/
        subpage = default/
        subpage = blog/
          sub-subpage = blogarticle/

“Subpage” = relative subpage; relative to “artist…”

Here are several challenges for the submenu, and I hope I can get some help:

1. Remove the blogarticles and the portfolioitems from the menu

Should be done with something like

$excluded = $page->children()>filterBy('template', 'blogarticle') ...;
foreach($page->children()->not($excluded) as $subpage):

But I still didn’t got it to work.

2. Display the artist’s page and the subpages onto one level in the menu

Something like this: John Doe - Blog - Documents - Biography
That could be achieved with CSS, but maybe there is a simpler way to do it?

The not() method does not accept a collection as parameter in 2.2.3, that will be a new feature in 2.3.

You could however filter all children with a template different from “blogarticle”:

foreach($page->children()->filterBy('template', '!=', 'blogarticle') as $subpage):
...
```

Thank you texnixe, this worked indeed, but only for ONE template. “filterBy” cannot be used with two templates, as far as I understood?

<?php if(!isset($subpages)) $subpages = $pages->find('artists')->children() ?>
<ul>
  <?php foreach($subpages->visible()->filterBy('template', '!=', 'blogarticle') as $p): ?>
  <li>
    <a<?php echo ($p->isActive()) ? '
	     class="active"' : '' ?> href="<?php echo $p->url() ?>"><?php echo $p->title() ?></a>
    <?php if($p->hasChildren()): ?>
    <?php snippet('treemenu', array('subpages' => $p->children())) ?>
    <?php endif ?>
  </li>
  <?php endforeach ?>
</ul>

You can use multiple filterBy():

<?php foreach($subpages->visible()->filterBy('template', '!=', 'blogarticle')->filterBy('template', '!=', 'another_one') as $p): ?>

or use a filter with a callback:

<?php 
foreach ($subpages->visible()->filter(function($p) {
  return $p->template() !== 'blogarticle' && $p->template() !== 'some_other_template';
}) as $p):
?>

I`d save all that into a variable first and then use the variable in the loop to keep your code clean.

Excellent! Thanks a lot.

PS.: Lukas marked this as “Solved” (thanks for that) but the all horizontal menu is still quite tricky…

I’ve reopened it, @lukasbestle just loves to close topics :slight_smile:

Edit: As regards the one line menu, I’d say that should be solved with HTML/CSS. Maybe it’s advisable not use nested lists for the tree?

1 Like

No problem with that… Because I will close it now :slight_smile: Here is the whole thing:

<div class="tree menu-centered">
  <?php foreach($subpages->visible()->filterBy('template', '!=', 'blogarticle')->filterBy('template', '!=', 'project') as $p): ?>
  <ul class="menu">
    <li> 
      <a<?php echo ($p->isActive()) ? 'class="active"' : '' ?> href="<?php echo $p->url() ?>"><?php echo $p->title() ?>
      </a>
    </li>
  </ul>
  <?php if($p->hasChildren()): ?>
  <?php snippet('treemenu', array('subpages' => $p->children())) ?>
  <?php endif ?>
  <?php endforeach ?>
</div>

Here the corresponding CSS (for a centered menu that I wanted):

.tree.menu-centered .menu-centered {display: inline;}
.menu-centered {text-align: center; }
.menu-centered > .menu {display: inline-block; }
.menu {list-style-type: none; }
.menu > li {display: table-cell;vertical-align: middle; }
.menu > li > a {display: block; }

This works with the Zurb Foundation Framework that I use so I didn’t check whether some adjustments are necessary without it .

Sorry to harp on this.

What I did before was a tree display, but actually I try to get just the branch.
This Kirby blog article helped to solve at least the second level part:
https://getkirby.com/blog/fun-with-menus

What I could do is to call two loops: one for the first level (in this case “artist …” – in the Kirby hierarchy a sublevel) and one for the second level (in the Kirby hierarchy a sub-sublevel).

The loop (for “artist …”) taken from the blog example is this:

<?php

$items = false;

// get the open item on the first level
if($root = $pages->findOpen()) {

  // get visible children for the root item
  $items = $root->children()->visible();
}

// only show the menu if items are available
if($items && $items->count() > 0):

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

What I obtain is that all the artists are listed. But I only want to see the current artist, both on the artist level and the second level. Is there any way to solve this?

I think your should change the first line to:

if($root = page('artists')->children()->findOpen()) ...

And integrate that into your main loop, if necessary.

Regrettably this only displays the second level again, and again all its pages.

Hi Kate, I just stumbled upon this post while doing a search. Did you find your answer?

No, I could not solve this, alas. But I did not want to bother too much about it. So in case you find how to do it…

I love trying to fix this kind of situations, and I find that most of the time, the challenge is actually in the plain text part and not in the code part :smiley:

Reading through the post. I think I missed at some point the actual goal you are trying to achieve. Has your goal changed since your first post?
If so, would you be able to lay it out again?

Let’s get to the bottom of this !

Haha, thank you very much! I usually don’t like to do that: to post a link to non-working websites, hm. But here you go because a living example is always better than too many confusing words. So please take it as a work in progress; I will delete the link again after a while:

EDIT: Link deleted.

Just to clarify:

So you got your sidebar menu and a top menu. Now, if I understand correctly what you explained above, the top menu should only show the artist you selected in the sidebar menu together with the artist’s subpages all in one line?

So your top menu would just look like this:

Künstler 1  --- Einfache Seite --- Projekte

Yes, exactly! And I would love the current page to be marked as active (for instance now when you click on “Einfache Seite” or “Projekte” the link is underlined. But not “Künstler 1”).

The code is in my post above: https://forum.getkirby.com/t/branch-menu-as-submenu-with-excluded-sub-sub-pages-displayed-on-one-level/3999/9?u=kate

Actually, I think all you need is a link to the currently open page (which is $page) and it’s children:

<nav>
  <a <?php ecco($page->isOpen(), ' class="active"') ?> href="<?= $page->url() ?>"><?= $page->title() ?></a>
  <?php if($page->hasChildren()): ?>
    <ul class="submenu">
      <?php foreach($page->children()->visible() as $item): ?>
        <li><a<?php ecco($item->isOpen(), ' class="active"') ?> href="<?php echo $item->url() ?>"><?php echo html($item->title()) ?></a></li>
     <?php endforeach ?>
   </ul>
 <?php endif ?>
</nav>

There is still something missing. When I do as you suggest I get something like this:

But what I need is this:

And this is my file structure:

I think you need to pass the correct reference to $page to the snippet, when you include it on the subpages like this:

<?php snippet('name_of_snippet', array('page', $page->parent())) ?>

Can’t test this right now.

I tried this with several combinations, but it did not work. Also I need to call a template sooner than a page.

Anyhow: it seems more tricky than it looks, especially for me as a PHP beginner. I am very grateful for your help! But if this takes too much time to solve, please leave it the way. Visually I can solve this (in a very hacky way, I know) with CSS.