FluidTYPO3's extension vhs comes with a range of viewhelpers to render all kinds of menus. To demonstrate its ease of use and flexibility I compiled some examples.

Caution: Since version 3.0.0 of vhs the menu viewhelpers are no longer located below the 'page' namespace but moved to the top level so make sure to replace <v:page.menu ... /> with <v:menu ... />.

Here's a typical - at least I guess so - page tree:

Home [id=0]
__Home [id=1, shortcut to id=0]
__Page A [id=2]
____Subpage A [id=5]
____Subpage B [id=6]
____Subpage C [id=7]
__Page B [id=3]
__Page C [id=4]

Let's start with the most basic implementation:

<nav>
    <v:page.menu />
</nav>

Result is the following, pretty straightforward markup:

<nav>
    <ul>
        <li><a href="index.php?id=1" title="Home">Home</a></li>
        <li class="sub"><a href="index.php?id=2" title="Page A">Page A</a></li>
        <li><a href="index.php?id=3" title="Page B">Page B</a></li>
        <li><a href="index.php?id=4" title="Page C">Page C</a></li>
    </ul>
</nav>

As you can see there's a little flaw: since 'Home' (id=1) is a shortcut to the site root (id=0) it is not marked as being 'current' when it is selected. To achieve this we add useShortcutData="TRUE" to the menu tag which will replace menu items that are shortcuts with their target items:

<nav>
    <v:page.menu useShortcutData="TRUE" />
</nav>

resulting in the correct markup:

<nav>
    <ul>
        <li class="current"><a class="current" href="index.php?id=1" title="Home">Home</a></li>
        <li class="sub"><a href="index.php?id=2" title="Page A">Page A</a></li>
        <li><a href="index.php?id=3" title="Page B">Page B</a></li>
        <li><a href="index.php?id=4" title="Page C">Page C</a></li>
    </ul>
</nav>

To deal with shortcuts there are two more arguments available: useShortCutUid and useShortCutTarget. When set to TRUE the former would only replace the linked page UID and the latter would replace everything except the page UID of the shortcut.

Subtrees

'Page A' (id=2) is parent of a subtree thus the added css class sub. If Page A is selected the subtree can be expanded and rendered as a nested list by setting the desired depth with levels. Some useful CSS classes are added which are of course configurable with classActive and classCurrent. 'current' meaning the currently selected page and 'active' meaning page is in the rootline:

<nav>
    <v:page.menu useShortcutData="TRUE" levels="2" />
</nav>

results in

<nav>
    <ul>
        <li><a href="index.php?id=1" title="Home">Home</a></li>
        <li class="active current sub">
            <a href="index.php?id=2" title="Page A">Page A</a>
            <ul class="lvl-1">
                <li><a href="index.php?id=5" title="Subpage A">Subpage A</a></li>
                <li><a href="index.php?id=6" title="Subpage B">Subpage B</a></li>
                <li><a href="index.php?id=7" title="Subpage C">Subpage C</a></li>
            </ul>
        </li>
        <li><a href="index.php?id=3" title="Page B">Page B</a></li>
        <li><a href="index.php?id=4" title="Page C">Page C</a></li>
    </ul>
</nav>

Add expandAll="TRUE" in case you'd like to always expand subtrees for example when building pulldown menus.

Split menus

Now, chances are you want to split up your menu into two parts: The classic 'first level in the page header and deeper levels in the sidebar'. To achieve that we first configure our menu to render only one level by setting levels="1" and add another menu that starts with the second level:

<nav id="main-menu">
    <v:page.menu useShortcutData="TRUE" levels="1" />
</nav>

and

<nav id="sub-menu">
    <v:page.menu levels="2" entryLevel="1" />
</nav>

Now the submenu will only be rendered in case there's a subtree below the currently selected page in the main menu. Of course you can add as many menus as you like/need by using the right combination of entryLevel and levels.

Auto- vs. manual rendering

The above examples automatically render the configured menus with best practice markup which of course not always fits the actual use case. That's where vhs powered manual rendering comes into play:

<nav>
    <v:page.menu>
        <f:for each="{menu}" as="item">
            <f:link.page pageUid="{item.uid}" title="{item.linktext}">{item.linktext}</f:link.page>
        </f:for>
    </v:page.menu>
</nav>

Inside the <v:page.menu /> tag (and only inside it) we can access the template variable {menu} - which actually is an array of page records - and iterate over it as shown in the minimal example above.

For subtree rendering there's some extra work required now of course:

<nav>
    <v:page.menu>
        <f:for each="{menu}" as="item">
            <f:link.page pageUid="{item.uid}" title="{item.linktext}">{item.linktext}</f:link.page>
            <f:if condition="{item.hasSubPages}">
                <v:page.menu pageUid="{item.uid}">
                    <f:for each="{menu}" as="subItem">
                        <f:link.page pageUid="{subItem.uid}" title="{subItem.linktext}">{subItem.linktext}</f:link.page>
                    </f:for>
                </v:page.menu>
            </f:if>
        </f:for>
    </v:page.menu>
</nav>

In cases - like the simplyfied one above - where subtrees should be rendered in the exact same way as their parents we can shorten our code with the <v:page.menu.sub /> viewhelper:

<nav>
    <v:page.menu>
        <f:for each="{menu}" as="item">
            <f:link.page pageUid="{item.uid}" title="{item.linktext}">{item.linktext}</f:link.page>
            <f:if condition="{item.hasSubPages}">
                <v:page.menu.sub pageUid="{item.uid}" />
            </f:if>
        </f:for>
    </v:page.menu>
</nav>

This viewhelper will sort of 'copy' the parent's menu configuration and render it (manual rendering is not available here for obvious reasons).

Deferred rendering

If you're now like "Wow, that's flexible!" hold tight, there's more: Deferred rendering. In not so rare cases the markup surrounding a menu is dependent on the menu itself. We can solve this by holding back the rendering of a menu by setting deferred="TRUE" and finally outputting it with <v:page.menu.deferred />:

<v:page.menu deferred="TRUE">
    <div class="sidebar{f:if(condition: '{menu}', then: ' has-menu')}">
        <f:if condition="{menu}">
            <div class="submenu-wrapper">
                <nav>
                    <v:page.menu.deferred />
                </nav>
            </div>
        </f:if>
        <f:render section="Sidebar" />
    </div>
</v:page.menu>

For most cases manual rendering is not necessary though as the viewhelpers are highly configurable. You may want to add some more CSS classes like marking the first item with classFirst or the last item with classLast or use classHasSubpages for parents of subtrees. To not link the currently selected page in the menu set linkCurrent="FALSE" or equally linkActive="FALSE" for parents of the current page. Hide the current link with showCurrent="FALSE" or override the backend setting 'Hide in menu' with showHidden="TRUE". Even the used wrapping and enclosing tag - <ul/> and <li/> by default - can be changed with tagName and tagnameChildren. The latter can also be used to enable 'non wrapping mode' by setting it to 'a' which removes the inner wrapping tags (<li/>).

Other menu types

The standard viewhelper discussed above can be understood as a Fluid equivalent to the HMENU object in TypoScript and there are more of course: <v:page.breadcrumb />, <v:page.menu.list />, <v:page.menu.browse /> and <v:page.menu.directory />. We'll get to those in another post so that's all for today.

Updates

  • the above examples only depend on vhs, no other FluidTYPO3 EXTs are required (but recommended of course ;) )
  • I only mentioned the most important arguments of the viewhelper and there are a lot more. You can find them in the reference
  • As mentioned in the comments use $item['linktext'] with manual rendering for link titles and, well, link texts. It is automatically populated with either navtitle or title