15

I've got a Structure section where the top level is the main menu. The submenu consists of the children of the active main menu item. This works well.

For the submenu, I'm using this code:

{% set mainEntry = craft.entries.slug(craft.request.getSegment(1)).first() %}
{% set subnav = craft.entries.descendantOf(mainEntry) %}

<ul>
{% nav page in subnav %}
  <li{% if page.slug == craft.request.getSegment(page.level) %} class="active"{% endif %}>
    {{ page.getLink() }}
    {% ifchildren %}
      <ul>
        {% children %}
      </ul>
    {% endifchildren %}
  </li>
{% endnav %}
</ul>

...which shows all children of each page.

How can I only show the children of the active parent? I've tried to wrap the ifchildren block inside another if statement (the same I used to set the class="active" to the <li>), but that results in a Template error:

Unexpected tag name "ifchildren" (expecting closing tag for the "if" tag defined near line x)

Putting that if statement inside the ifchildren block also throws a template error.

PS. With the current HTML output, I could also just use CSS to hide/show the <ul>s based on the .active class, but I'd still like to know how to generate that tree without the need for a CSS workaround.

16

A recursive macro will help you get around the limitations of ifchildren (which P&T will hopefully resolve soon), as follows:

{% set mainEntry = craft.entries.slug(craft.request.getSegment(1)).first() %}
{% set subnav = craft.entries.descendantOf(mainEntry).level(2) %}

{% macro recursiveChildren(page) %}
  {% if page.hasDescendants and page.slug == craft.request.getSegment(page.level) %}
    <ul>
      {% for subpage in page.children %}
          <li{% if subpage.slug == craft.request.getSegment(subpage.level) %} class="active"{% endif %}>
            {{ subpage.link }}
            {{ _self.recursiveChildren(subpage) }}
          </li>
      {% endfor %}
    </ul>
  {% endif %}
{% endmacro %}

<ul>
{% for page in subnav %}
  <li{% if page.slug == craft.request.getSegment(page.level) %} class="active"{% endif %}>
    {{ page.link }}
    {{ _self.recursiveChildren(page) }}
  </li>
{% endfor %}
</ul>

More on macros here.

  • Yep, Best answer of the bunch so far. Thanks Ben. – Low Jun 30 '14 at 20:17
  • Ben, I edited your example: the subnav should only contain level 2 pages to avoid duplicates, and I added the if statement to the macro to add class="active" to the list items. – Low Jul 1 '14 at 8:11
  • great!! i suppose there are many tweaks that could be made to the code but the recursive macro is the key to the solution. – Ben Croker Jul 1 '14 at 11:07
5

I know this has been answered already, but here's my code for showing the children of the current parent:

{% if entry.getParent(entry)|length %}
{% set subMenu = craft.entries.descendantOf(entry.getParent(entry)) %}

<ul>
    {% nav link in subMenu %}
    <li><a href="{{link.getUrl}}">{{link.title}}</a></li>
    {% endnav %}
</ul>

{% endif %}
4

This will do it:

{% set mainEntry = craft.entries.slug(craft.request.getSegment(1)).first() %}
{% set subnav = craft.entries.descendantOf(mainEntry) %}

{% set numSegs = craft.request.segments|length %}
<ul>
{% nav page in subnav %}
  {% set thisOneIsActive = page.slug == craft.request.getSegment(page.level) %}
  {% set pageUrlSegs = (page.url|split('//'))[1]|split('/') %}
  {% set wantThisOne = thisOneIsActive or (pageUrlSegs|length > numSegs and pageUrlSegs[numSegs] == craft.request.getLastSegment) %}
  {% if wantThisOne %}
    <li{% if thisOneIsActive %} class="active"{% endif %}>
      {{ page.getLink() }}
  {% endif %}
      {% ifchildren %}
        <ul>
          {% children %}
        </ul>
      {% endifchildren %}
  {# at this point for whatever reason we have lost our context variables and have to recalculate them #}
  {% set thisOneIsActive = page.slug == craft.request.getSegment(page.level) %}
  {% set pageUrlSegs = (page.url|split('//'))[1]|split('/') %}
  {% set wantThisOne = thisOneIsActive or (pageUrlSegs|length > numSegs and pageUrlSegs[numSegs] == craft.request.getLastSegment) %}
  {% if wantThisOne %}
    </li>
  {% endif %}
{% endnav %}
</ul>

I think it's kind of a hack. It works around the limitations of not being able to have the ifchildren in an if by calculating for every element whether or not we want to show it. And it works around the nav context no longer being set after the ifchildren block by making that calculation twice.

I think the nav tag has room for improvement (easier to say than to do, I know).

  • 1
    Thanks Marion! If that's the way to do it, I'd prefer the CSS workaround... Maybe the P&T guys can offer some insight. :) – Low Jun 29 '14 at 22:31
  • 1
    I'd prefer the CSS workaround too. I think what we have here is a bug in the nav tag. – Marion Newlevant Jun 29 '14 at 23:44
3

I was working on a site where I needed dynamic sub nav similar to what I think you are describing - in order to solve the issue I set my arrays at the top of the page calculating by entry.level and page parent. I didn't do anything with active classes yet.

So if the nav tree is:

About
--Leaders
--History
Groups
--Men
----Men Book Group
--Women

When you click on About you will be presented only with the sub nav "Leaders" and "History" and with my particular implementation it will display only the sub nav that belongs to a 2nd level nav item as well... so if you navigate to "Men" it will only show "Men Book Group" in the sidenav. I think you should be able to make it show that whole tree if you took of the if statement related to the 3rd level.

{% if entry.level == '1' %}

    {% set pages = craft.entries.section('page').descendantOf( entry.id ).descendantDist(1) %}
    {% set parentLink = entry %}

{% elseif entry.level == '2' %}

    {% if entry.children.first == TRUE %}

        {% set pages = craft.entries.section('page').descendantOf( entry.id ) %}
        {% set parentLink = entry %}

    {% else %}

         {% set pages = craft.entries.section('page').descendantOf( entry.parent ) %}
         {% set parentLink = entry.parent %}

    {% endif %}

{% elseif entry.level == '3' %}

    {% set pages = craft.entries.section('page').descendantOf( entry.parent ) %}
    {% set parentLink = entry.parent %}

{% endif %}

My sub nav code is as follows:

          <ul><li><a href="{{ parentLink.url }}">{{ parentLink.title }}</a>

            <ul>
            {% nav page in pages %}
                <li>
                    {% if page.type == 'link'  %}
                        {% set link = page.destination %}
                        <a href="{{ link.url }}">{{ page.title }}</a>
                    {% else %}
                        <a href="{{ page.url }}">{{ page.title }}</a>

                    {% endif %}

                    {% ifchildren %}
                        <ul>
                            {% children %}
                        </ul>
                    {% endifchildren %}
                </li>
            {% endnav %}
            </ul>

            </li></ul>

I hope this helps and I apologize if I have misunderstood the question!

  • You understood, but that approach won't work for an unknown amount of levels. – Low Jun 30 '14 at 20:16
  • @Low Totally makes sense! I will try Ben's solution as well. – Alan Miller Jun 30 '14 at 22:14

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.