How to control which parent menu items are shown?

Hi, I would like to hide some parent menu items. How does it work? Do I have to write something in page blueprint to make it hidden in menu? Best would be to have a checkbox field in panel in that page editing, so that client manage himself.

Also sometimes would need, that page, that is first level, would be displayed as child (2 level) in menu. How is it possible to do?

My current menu structure is:

  <div class="navbar">
    <!-- Navbar Links -->
    <?php foreach ($site->children()->listed() as $p): ?>

    <?php if ($p->menu() == "yes" && ($p->hasvisibleChildren() && $p->template() != 'default')): ?>
    <div class="navbar-item has-dropdown is-hoverable">
      <a class="navbar-link <?php e($p->isOpen(), 'is-active') ?>" href="<?= $p->url() ?>">
      <?= $p->title() ?>
      </a>

      <div class="navbar-dropdown">
      <?php if($p->template() == "blog"): ?>
        <?php foreach($p->children()->listed()->flip()->limit(10) as $n): ?>
        <a class="navbar-item <?= e($n->isOpen(), 'is-active') ?>" href="<?= $n->url() ?>">
          <?= $n->title() ?>
        </a>
        <?php endforeach ?>
      <?php else: ?>
        <?php foreach($p->children()->flip()->listed() as $n): ?>
        <a class="navbar-item <?= e($n->isOpen(), 'is-active') ?>" href="<?= $n->url() ?>">
          <?= $n->title() ?>
        </a>
        <?php endforeach ?>
      <?php endif ?>
      </div>
    </div>

    <?php else: ?>

    <a class="navbar-item <?php e($p->isOpen(), 'is-active') ?>" href="<?= $p->url() ?>">
      <?= $p->title() ?>
    </a>

    <?php endif ?>
  
  <?php endforeach ?>
  

  
  
  </div>

I use a toggle field in each page to control menu visibility. Then you can just do this in your loop:

<?php if($sitenav->featureinnav()->bool()): ?>
<!-- menu link -->
<?php endif ?>
1 Like

I’d probably use a nested structure field to fully control the menu manually (with pages fields inside the structure)

      fields:
        type: fields
        headline: Menu
        fields:
          menu:
            type: structure
            fields:
              mainitem:
                label: Main Menu Item
                type: pages
                query: site.index # you can adapt this query to exclude pages that shouldn't appear in the main menu, like the error page or subpages etc.
              level2:
                type: structure
                fields:
                  subitem:
                    label: Child Menu Item
                    type: pages

The toggle field approach works fine if you just want to include/exclude pages from the navigation. But if you want to control the level where in the navigation an item appears, or the order in which the items appear in the navigation manually, the toggle field approach is not enough.

Thank you very much, will try both approaches.

texnixe, how do I filter out second level pages from this selection?

Also can you please guide how to display menu? What code should it be?

Have time now and want to implement in one project, can you please help with second level?

My template now is:

<?php foreach($site->links1()->toStructure() as $menu):	
  $p = $menu->level1()->toPage(); ?>
    <li><a href="<?= $p->url() ?>"><?= $p->title() ?></a></li>
<?php endforeach ?>

And blueprint for links:

title: Site
tabs:
...
  menu:
    label: meniu
    icon: menu
    columns:
      - width: 2/3
        sections:
          mymenu:
            type: fields
            fields:
              links1:
                label: Navigation
                type: structure
                fields:
                  level1:
                    type: pages
                  links2:
                    label: Navigation
                    type: structure
                    fields:
                      level2:
                        type: pages

How can I print second level items inside <li> of corresponding parents?

<?php foreach($site->links1()->toStructure() as $menu):	
  $p = $menu->level1()->toPage(); ?>
    <li>
        <a href="<?= $p->url() ?>"><?= $p->title() ?></a>
         <?php if ($menu->links2()->toStructure()->count() > 0) ?>
           <ul>
              <?php foreach ($menu->links2()->toStructure() as $item): ?>
                 <li><!-- here the code for the item --></li>
              <?php endforeach ?>
           </ul>
    </li>
<?php endforeach ?>
1 Like

Great, thank you! :blush:

Tested with this code (changes variables a bit to read easier):

<?php foreach($site->links1()->toStructure() as $menu1):	
  $p = $menu1->level1()->toPage(); ?>
    <li>
        <a href="<?= $p->url() ?>"><?= $p->title() ?></a>
         <?php if ($menu1->links2()->toStructure()->count() > 0) ?>
           <ul>
              <?php foreach ($menu1->links2()->toStructure() as $menu2):
	               $p2 = $menu2->level2()->toPage(); ?>
                 <li><a href="<?= $p2->url() ?>"><?= $p2->title() ?></a></li>
              <?php endforeach ?>
           </ul>
    </li>
<?php endforeach ?>

Only can’t understand how to add active class to <li class= dropdown">, if parent is open. Using bootstrap 4.3 and it’s standard menu.

Here is my current code:

<ul class="navbar-nav mr-auto">
<?php foreach($site->links1()->toStructure() as $menu1):	
  $p = $menu1->level1()->toPage(); ?>
    
        <?php if ($menu1->links2()->toStructure()->count() > 0) : ?>
        <li class="nav-item <?php e($menu1->isOpen(), 'active') ?> dropdown">
        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><?= $p->title() ?></a>


           <div class="dropdown-menu" aria-labelledby="navbarDropdown">
              <?php foreach ($menu1->links2()->toStructure() as $menu2):
	               $p2 = $menu2->level2()->toPage(); ?>
                 <a class="dropdown-item <?php e($p2->isActive(), 'active') ?>" href="<?= $p2->url() ?>"><?= $p2->title() ?></a>
              <?php endforeach ?>
           </div>
        </li>
        <?php endif ?>

        <?php if ($menu1->links2()->toStructure()->count() < 1) : ?>
        <li class="nav-item">
        <a class="nav-link <?php e($p->isActive(), 'active') ?>" href="<?= $p->url() ?>"><?= $p->title() ?></a>
        </li>
        <?php endif ?>
        

<?php endforeach ?>
</ul>

If it’s:
<li class="nav-item <?php e($menu1->isOpen(), 'active') ?> dropdown">
then parent item always gets active class, even when it’s not open.

also tried:
<li class="nav-item <?php e($p->isOpen(), 'active') ?> dropdown">
it doesn’t change anything.


With different method works fine, for example:
<?php

// nested menu
$items = $site->pages()->filterBy('intendedTemplate', 'in', ['about','landing','default'])->listed();

// only show the menu if items are available
if($items->isNotEmpty()):

?>

  <ul class="navbar-nav mr-auto">
    <?php foreach($items as $item): ?>
    <li class="nav-item <?php e($item->isOpen(), 'active') ?> <?php if($item->children()->listed()->isNotEmpty()): ?>dropdown<? endif ?>
 ">

I take it you have read through the menu page in the cookbook? I find the Tree menu the most useful - its a snippet that keeps calling itself until it runs out of pages. You could probably adapt it to work with the structure field.

$p->isOpen() should do the job.

But you have messed up the order of your code, compare your current order to the snippet I posted above. Your if statement now comes too early.

Edit: Ah, you have added an else statement… That shouldn’t actually be necessary…

In any case $menu1->isOpen() doesn’t make sense because that’s not a page.

Yes, I was checking it, but as I understand it replicated the same structure, but in Bootstrap children and their wrapper are different than parents. And actually it’s only this class missing now, so hope can finish with current solution :slight_smile:

Ah… well i don’t use such things, but you should still be able to throw the classes where u need them quite easily.

<ul class="navbar-nav mr-auto">
    <?php foreach($site->links1()->toStructure() as $menu1):	
    if ($p = $menu1->level1()->toPage()): 
    $subItemsCount = $menu1->links2()->toStructure()->count();
?>
    
    <li class="nav-item <?php e($menu1->isOpen(), 'active') ?> <?= $subItemsCount > 0 ? 'dropdown' : '' ?>">
        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><?= $p->title() ?></a>
        <?php if ($subItemsCount > 0): ?>
           <div class="dropdown-menu" aria-labelledby="navbarDropdown">
              <?php foreach ($menu1->links2()->toStructure() as $menu2):
	               <?php if ($p2 = $menu2->level2()->toPage()); ?>
                        <a class="dropdown-item <?php e($p2->isActive(), 'active') ?>" href="<?= $p2->url() ?>"><?= $p2->title() ?></a>
                       <?php endif ?>
              <?php endforeach ?>
           </div>
        <?php endif ?>
    </li>
     <?php endif ?>
    <?php endforeach ?>
</ul>

I cleaned it up and added two if statements regarding the pages, because you didn’t check if they actually exist. I already mentioned it here: How to print field of page or parent page (if it's in parent page)?

Thank you, checking code now, there is some syntax error:

          <?php foreach ($menu1->links2()->toStructure() as $menu2):
               <?php if ($p2 = $menu2->level2()->toPage()); ?>
                    <a class="dropdown-item <?php e($p2->isActive(), 'active') ?>" href="<?= $p2->url() ?>"><?= $p2->title() ?></a>

Also checked the one you had before edit, it was working in PHP 7.1, but from PHP 7.2 was giving error, something about dropdown

Also parent <a> should be different if it has children or not.

<a> with children:
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><?= $p->title() ?></a>

<a> without children:
<a class="nav-link <?php e($p->isActive(), 'active') ?>" href="<?= $p->url() ?>"><?= $p->title() ?></a>