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.

5 Answers 5

17

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.

3
  • Yep, Best answer of the bunch so far. Thanks Ben.
    – Low
    Jun 30, 2014 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, 2014 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, 2014 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).

2
  • 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, 2014 at 22:31
  • 1
    I'd prefer the CSS workaround too. I think what we have here is a bug in the nav tag. Jun 29, 2014 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!

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

You can now use the filter tag of Twig.

So you can just do something like this:

{% nav page in subnav|filter(page => page.slug == craft.request.getSegment(page.level) ) %}
    do some stuff
{% endnav %}

Another solution would be to print out the children only if the condition is true:

  {% ifchildren %}
    {% if page.slug == craft.request.getSegment(page.level) %}
          <ul>
            {% children %}
          </ul>
    {% endif %}
  {% endifchildren %}

Or combine them to get your wished result.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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