Wayfinder Multilevel Menus with Bootstrap 5

I am trying to use Wayfinder to allow for additional levels of menu items but am hitting a snag. My innerTpl chunk will need to serve two different versions depending on the level of the resource and if it has children.

Level 2 menu item without children

  <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
    [[+wf.wrapper]]
  </ul>

Level 2 menu item with children

  <li class="dropdown-submenu">
        <a class="dropdown-item dropdown-toggle" href="#">[[+wf.linktext]]</a>
          <ul class="dropdown-menu">
              [[+wf.wrapper]]
          </ul>
   </li>

Sadly no ‘haschildren’ output filter or snippet exists or I could use [[+id]] with If/Then logic.
Thank you!

I would suggest to use pdoMenu not Wayfinder.
pdoTools is faster and has more options.

Then you have:

[[pdoMenu?
&parents=`0`
&tpl=`rowWithoutChildren`
&tplParentRow=`rowWithChildren`
&levelClass=`level`
]]

Then inside the row chunks you can use output filters:

[[[[+level:is=`level2`:then=`
$chunkLevel2
`:else=`
$chunkLevelOther
`]]]]

I agree that pdoTools would be a better solution and will switch in a bit, but essentially the same issue. Checking that it is a level 2 menu item only solves one half of the problem, I need to ensure that resource also has published children.

Edit: it would be very cool to have an output filter for use with the [[+id]] tag to return a boolean value but I don’t know php yet.

pdoMenu has a property &countChildren that can be enabled in the snippet call.
See the documentation for more details.

I switched it to pdoTools and made use of the &countChildren property. However when I use the following I get duplicates for menu items with submenus. This seems to lead to an output like this, where there is a dud link in white taking up space above the functional menu.

[[pdoMenu?
        &parents=`0`
        &tplOuter=`outerTpl`
        &tpl=`rowTpl`
        &tplInnerRow=`innerRowTpl`
        &tplInner=`innerTpl`
        &tplParentRow=`parentRowTpl`
        &levelClass=``
        &countChildren=`1`
      ]]

innerTpl

[[+children:gte=`1`:then=`
    [[+level:is=`2`:then=`
    
      <li class="dropdown-submenu">
            <a class="dropdown-item dropdown-toggle" href="#">[[+menutitle]]</a>
              <ul class="dropdown-menu">
              [[+wrapper]]
              </ul>
        </li>
        
    `:else=`
    
        <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
            [[+wrapper]]
        </ul>
             
    `]]
`:else=`
    <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
        [[+wrapper]]
    </ul>
`]]

Output

<li class="nav-item dropdown">
    <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
        Event Name
    </a>
    <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
            <li class="nav-item dropdown">
    <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
        Run Packages
    </a>
    <li class="dropdown-submenu">
            <a class="dropdown-item dropdown-toggle" href="#">Run Packages</a>
              <ul class="dropdown-menu">
              <li><a class="dropdown-item" href="index.php?id=25">Purchase Packages</a></li><li><a class="dropdown-item" href="index.php?id=21">Run Pass Scholarships</a></li>
              </ul>
        </li>
</li>

innerRowTpl

<li><a class="dropdown-item" href="[[+link]]">[[+menutitle]]</a></li>

I did try using chunks instead of doing it all inline to keep things clean, but it crashed the site on Modx Cloud.

The reason why the same page is output twice, is that the row is output once in rowTpl/parentRowTpl and then you output it a second time in innerTpl ( → <li class="dropdown-submenu">...)

Generally, you output the <li> in the row template (&tplParentRow, &tpl, &tplInnerRow) and only the <ul> in &tplInner/&tplOuter.


If you (for some reason) have to combine <li> and <ul> in the same template, then set tplInner to just the wrapper placeholder (without <ul>) and move the <ul> to the row template.

Something like this might work:

[[pdoMenu?
    &parents=`0`
    &tplInner=`@INLINE {{+wrapper}}`
    &tpl=`myRowTpl`
]]

myRowTpl (using Fenom syntax)

{if $level == 2 && $wrapper?}
    <li class="dropdown-submenu">
        <a class="dropdown-item dropdown-toggle" href="#">{$menutitle}</a>
        <ul class="dropdown-menu">{$wrapper}</ul>
    </li>
{else}
    <li{$classes}>
        <a href="{$link}" {$attributes}>{$menutitle}</a>
        {if $wrapper?}
            <ul class="dropdown-menu" aria-labelledby="navbarDropdown">{$wrapper}</ul>
        {/if}
    </li>
{/if}