XF 2.1 Converting Menu Into Multi-Drop Down

LenKaiser

Member
Is it possible to convert the default menu system that comes in XenForo 2.1 to a Multi-level menu system? And if so where do I find the code to make the change?

What I want to do is as shown in the screen shot, I want to take the link in yellow and make them a flyout of the main link above them. So in this instance it would be:

Popular Content
--->(flyout) Most Replied Threads
--->(flyout) Most Like Threads
...and so on.

Here is what it is currently:

menuscreen.jpg

And here is what I want to achieve:

menuscreen2.png
 

LenKaiser

Member
I've been attempting to do this for a while with still buggy results. Ideally I'd like the solution to follow Xenforo semantics and not be a "hack", will release a free addon if I can pull this off.

That would be great. I can help you test it if you get that far a long.

https://www.customizexf.com/ has a mega menu that might help you some of the way.

Yes I know, but I'm not interested in that one.
 

Nulumia

Well-known member
The HTML code for the menus is actually in PAGE_CONTAINER. Note that the the initial navigation content is set higher in the template at:
<xf:set var="$navHtml">

If you want to get at the actual HTML for the dropdown menus and sublinks, you'll find that at the bottom in the following two macros:
<xf:macro name="nav_link" arg-navId="!" arg-nav="!" arg-class="" arg-titleHtml="" arg-shortcut="{{ false }}">
<xf:macro name="nav_menu_entry" arg-navId="!" arg-nav="!" arg-depth="0">

In-case you're not too familiar with macros, these are just the HTML "loops" for producing the menus. Keep in mind that nav_menu_entry is literally looped within itself, where nav_link macro renders everything from top links (navigation tabs) all the way down to dropdown menu sublinks. So if you want to display levels of sublinks or insert content in the menus, you need to do so depending on the $depth -- for example, submenu separator inserted via:
Code:
<xf:if is="$depth == 0">
       <hr class="menu-separator" />
</xf:if>
 

LenKaiser

Member
The HTML code for the menus is actually in PAGE_CONTAINER. Note that the the initial navigation content is set higher in the template at:
<xf:set var="$navHtml">

If you want to get at the actual HTML for the dropdown menus and sublinks, you'll find that at the bottom in the following two macros:
<xf:macro name="nav_link" arg-navId="!" arg-nav="!" arg-class="" arg-titleHtml="" arg-shortcut="{{ false }}">
<xf:macro name="nav_menu_entry" arg-navId="!" arg-nav="!" arg-depth="0">

In-case you're not too familiar with macros, these are just the HTML "loops" for producing the menus. Keep in mind that nav_menu_entry is literally looped within itself, where nav_link macro renders everything from top links (navigation tabs) all the way down to dropdown menu sublinks. So if you want to display levels of sublinks or insert content in the menus, you need to do so depending on the $depth -- for example, submenu separator inserted via:
Code:
<xf:if is="$depth == 0">
       <hr class="menu-separator" />
</xf:if>

Ok I thought the code was in the PAGE_CONTAINER but really did not see a way to make it work right there so that's when I started looking elsewhere. I'll look this stuff over and see what I can come up with, if anything.
 

Nulumia

Well-known member
SIGH Well I looked at this and I can see why its a PITA. Wasn't this easier in earlier forms of XF?
;) Same reactions. I did manage to pull off some flyout functionality but still rough. One of the tricky parts is making it respect canvas edge and invert directions if there's not enough room, like how all XF menus work currently.
 

LenKaiser

Member
Yeah I did not try to implement any code as I looked at it, it looked pretty impossible for me. I'm well versed with code, but it seems they have made the whole menu system seriously difficult to change code wise for whatever reason. :(
 

Russ

Well-known member
I haven't really tested this thoroughly... but maybe it'll get you going.

PAGE_CONTAINER

Find:
Code:
<xf:macro name="nav_menu_entry" arg-navId="!" arg-nav="!" arg-depth="0">
    <xf:macro name="nav_link"
        arg-navId="{$navId}"
        arg-nav="{$nav}"
        arg-class="menu-linkRow u-indentDepth{$depth} js-offCanvasCopy" />
    <xf:if is="$nav.children">
        <xf:foreach loop="$nav.children" key="$childNavId" value="$child">
            <xf:macro name="nav_menu_entry"
                arg-navId="{$childNavId}"
                arg-nav="{$child}"
                arg-depth="{{ $depth + 1 }}" />
        </xf:foreach>
        <xf:if is="$depth == 0">
            <hr class="menu-separator" />
        </xf:if>
    </xf:if>
</xf:macro>

Replace with:

Code:
<xf:macro name="nav_menu_entry" arg-navId="!" arg-nav="!" arg-depth="0">
    <xf:if is="$nav.children">
        <div class="nav-popout">
    </xf:if>
    <xf:macro name="nav_link"
        arg-navId="{$navId}"
        arg-nav="{$nav}"
        arg-class="menu-linkRow u-indentDepth{$depth} js-offCanvasCopy {{ $nav.children ? 'nav-has-children' : 'nav-has-children' }}" />
        <xf:if is="$nav.children">
        <div class="nav-popout--menu">
        <xf:foreach loop="$nav.children" key="$childNavId" value="$child">
            <xf:macro name="nav_menu_entry"
                arg-navId="{$childNavId}"
                arg-nav="{$child}"
                arg-depth="{{ $depth + 1 }}" />
        </xf:foreach>
        <xf:if is="$depth == 0">
            <hr class="menu-separator" />
        </xf:if>
        </div>
    </xf:if>
    <xf:if is="$nav.children">
        </div>
    </xf:if>
</xf:macro>

Add this in extra.less:

Code:
.menu--structural .menu-content
{
    overflow: visible !important;
    .nav-popout
    {
        position: relative;

        &:hover .nav-popout--menu { display: block; }
        > .nav-has-children:after
        {
            .m-faBase();
            .m-faContent("\f0da");
            float: right;
        }
        .nav-popout--menu
        {
            display: none;
            position: absolute;
            right: -200px;
            top: 0;
            width: 200px;
            background-color: @xf-contentBg;
            border: 1px solid @xf-borderColor;
        }
    }
}

Result:

menus.gif
 

LenKaiser

Member
I haven't really tested this thoroughly... but maybe it'll get you going.

PAGE_CONTAINER

Find:
Code:
<xf:macro name="nav_menu_entry" arg-navId="!" arg-nav="!" arg-depth="0">
    <xf:macro name="nav_link"
        arg-navId="{$navId}"
        arg-nav="{$nav}"
        arg-class="menu-linkRow u-indentDepth{$depth} js-offCanvasCopy" />
    <xf:if is="$nav.children">
        <xf:foreach loop="$nav.children" key="$childNavId" value="$child">
            <xf:macro name="nav_menu_entry"
                arg-navId="{$childNavId}"
                arg-nav="{$child}"
                arg-depth="{{ $depth + 1 }}" />
        </xf:foreach>
        <xf:if is="$depth == 0">
            <hr class="menu-separator" />
        </xf:if>
    </xf:if>
</xf:macro>

Replace with:

Code:
<xf:macro name="nav_menu_entry" arg-navId="!" arg-nav="!" arg-depth="0">
    <xf:if is="$nav.children">
        <div class="nav-popout">
    </xf:if>
    <xf:macro name="nav_link"
        arg-navId="{$navId}"
        arg-nav="{$nav}"
        arg-class="menu-linkRow u-indentDepth{$depth} js-offCanvasCopy {{ $nav.children ? 'nav-has-children' : 'nav-has-children' }}" />
        <xf:if is="$nav.children">
        <div class="nav-popout--menu">
        <xf:foreach loop="$nav.children" key="$childNavId" value="$child">
            <xf:macro name="nav_menu_entry"
                arg-navId="{$childNavId}"
                arg-nav="{$child}"
                arg-depth="{{ $depth + 1 }}" />
        </xf:foreach>
        <xf:if is="$depth == 0">
            <hr class="menu-separator" />
        </xf:if>
        </div>
    </xf:if>
    <xf:if is="$nav.children">
        </div>
    </xf:if>
</xf:macro>

Add this in extra.less:

Code:
.menu--structural .menu-content
{
    overflow: visible !important;
    .nav-popout
    {
        position: relative;

        &:hover .nav-popout--menu { display: block; }
        > .nav-has-children:after
        {
            .m-faBase();
            .m-faContent("\f0da");
            float: right;
        }
        .nav-popout--menu
        {
            display: none;
            position: absolute;
            right: -200px;
            top: 0;
            width: 200px;
            background-color: @xf-contentBg;
            border: 1px solid @xf-borderColor;
        }
    }
}

Result:

View attachment 215861

Awesome! That works perfectly! Thanks! :)
 

gogo

Active member
I haven't really tested this thoroughly... but maybe it'll get you going.

PAGE_CONTAINER

Find:
Code:
<xf:macro name="nav_menu_entry" arg-navId="!" arg-nav="!" arg-depth="0">
    <xf:macro name="nav_link"
        arg-navId="{$navId}"
        arg-nav="{$nav}"
        arg-class="menu-linkRow u-indentDepth{$depth} js-offCanvasCopy" />
    <xf:if is="$nav.children">
        <xf:foreach loop="$nav.children" key="$childNavId" value="$child">
            <xf:macro name="nav_menu_entry"
                arg-navId="{$childNavId}"
                arg-nav="{$child}"
                arg-depth="{{ $depth + 1 }}" />
        </xf:foreach>
        <xf:if is="$depth == 0">
            <hr class="menu-separator" />
        </xf:if>
    </xf:if>
</xf:macro>

Replace with:

Code:
<xf:macro name="nav_menu_entry" arg-navId="!" arg-nav="!" arg-depth="0">
    <xf:if is="$nav.children">
        <div class="nav-popout">
    </xf:if>
    <xf:macro name="nav_link"
        arg-navId="{$navId}"
        arg-nav="{$nav}"
        arg-class="menu-linkRow u-indentDepth{$depth} js-offCanvasCopy {{ $nav.children ? 'nav-has-children' : 'nav-has-children' }}" />
        <xf:if is="$nav.children">
        <div class="nav-popout--menu">
        <xf:foreach loop="$nav.children" key="$childNavId" value="$child">
            <xf:macro name="nav_menu_entry"
                arg-navId="{$childNavId}"
                arg-nav="{$child}"
                arg-depth="{{ $depth + 1 }}" />
        </xf:foreach>
        <xf:if is="$depth == 0">
            <hr class="menu-separator" />
        </xf:if>
        </div>
    </xf:if>
    <xf:if is="$nav.children">
        </div>
    </xf:if>
</xf:macro>

Add this in extra.less:

Code:
.menu--structural .menu-content
{
    overflow: visible !important;
    .nav-popout
    {
        position: relative;

        &:hover .nav-popout--menu { display: block; }
        > .nav-has-children:after
        {
            .m-faBase();
            .m-faContent("\f0da");
            float: right;
        }
        .nav-popout--menu
        {
            display: none;
            position: absolute;
            right: -200px;
            top: 0;
            width: 200px;
            background-color: @xf-contentBg;
            border: 1px solid @xf-borderColor;
        }
    }
}

Result:

View attachment 215861

Just tried this on XF2.2RC2. It works well too!

Is there any way to make the expand/collapse of sub-menu work on off-canvas menu as well?
 

Russ

Well-known member
Just tried this on XF2.2RC2. It works well too!

Is there any way to make the expand/collapse of sub-menu work on off-canvas menu as well?
Not with my approach above, no. This is strictly a CSS approach sort of manipulating the way the sub-links show on the main menu, it doesn't create a clicking toggle feature. What you're after would require further edits.
 
Top