11

This seriously can't be this hard. How are you handling complex navigation on your sites? Do you hard-code them? Do you use a dedicated "Menu" Structure Section? I am curious what everyone is doing.

I have a few customers who really need to have control over their menu and its structure, but as I build the templates, I am finding I need complete control over the first level and especially the sub/children of them. I am finding it nearly impossible to come up with a way to be able to loop through children and style them how I need to.

Basically, I am trying to access the Dropdown portion of this menu example and as you can see, I need to be able to add a class and data-toggle to those items.

<nav class="navbar navbar-default" role="navigation">
  <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
    <ul class="nav navbar-nav">
      <li class="active"><a href="#">Link</a></li>
      <li><a href="#">Link</a></li>
      <li class="dropdown">
        <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <span class="caret"></span></a>
        <ul class="dropdown-menu" role="menu">
          <li><a href="#">Action</a></li>
          <li><a href="#">Another action</a></li>
          <li><a href="#">Something else here</a></li>
          <li class="divider"></li>
          <li><a href="#">Separated link</a></li>
          <li class="divider"></li>
          <li><a href="#">One more separated link</a></li>
        </ul>
      </li>
    </ul>
  </div>
</nav>
11
  • Hey @JacobGraf, there are lots of other questions on this topic in addition to P&T's suggestion. It'd be best to focus this into a more specific, answerable question or jump into one of the many other more detailed posts.
    – Matt Stein
    Commented Aug 12, 2014 at 18:14
  • I have already read Pixel and Tonics answer and my questions stems from it. I need a way to customize the HTML for the "children" and I am not finding any solutions to that problem.
    – Jacob Graf
    Commented Aug 12, 2014 at 18:19
  • I would think that the {% ifchildren %} and {% children %} tags would be sufficient so that you could control them from a parent class, but if I'm oversimplifying then it may still be worth getting more specific with your question.
    – Matt Stein
    Commented Aug 12, 2014 at 18:21
  • Yes, you are oversimplifying it. {% children %} outputs list items. I need control over the classes on those list items as well as some data-options, etc. It is too simple and I need to find a way to be able to access the children loop and completely customize its output. I have modified my question slightly to help.
    – Jacob Graf
    Commented Aug 12, 2014 at 18:25

5 Answers 5

20

If the {% nav %} tag doesn't give you enough control over the output, you can always replicate the behavior with custom loops and the getChildren() method. Just nest them as far as your navigation hierarchy possibly gets.

Edit:

Here an example code on how you'd dynamically call getChildren() on each top-level entry of your structure. If you need to go deeper another level in the structure's hierarchy, I marked the line where you would do that:

<ul class="Navigation">

    {# Get top-level entries in pages structure (exclude home page)  #}
    {% set pages = craft.entries.section('pages').id('not 1').level(1) %}

    {# Loop through top-level entries #}
    {% for page in pages %}

        {# Check if entry has descendants #}
        {% if not page.hasDescendants %}

            {# List navigation item if entry has NO child entries #}
            <li class="Navigation-item">
                <a href="{{ page.getUrl }}" class="Navigation-link">{{ page.title }}</a>
            </li>

        {% else %}

            {# List navigation item if entry does have child entries #}
            <li class="Navigation-item Navigation-item--dropdown">

                <a href="{{ page.getUrl }}" class="Navigation-link Navigation-link--dropdown">{{ page.title }}</a>
                <ul class="Navigation Navigation--subMenu">

                    {# Get child entries of current iteration's top-level entry #}
                    {% set subPages = page.getChildren() %}

                    {# Loop through those 2nd-level entries #}
                    {% for subPage in subPages %}

                        {# This is the place where you'd nest the next menu hierarchy !! #}

                        {# List sub-menu navigation item #}
                        <li class="Navigation-item Navigation-item--subMenu">
                            <a href="{{ subPage.getUrl }}" class="Navigation-link Navigation-link--subMenu">{{ subPage.title }}</a>
                        </li>

                    {% endfor %}

                </ul>
            </li>
        {% endif %}
    {% endfor %}

</ul>

.

Because most of your question's topics are already covered by other Q/As, I'd also recommend you to read through these too.

How to realize a client editable navigation:

More control over the HTML output of the nav tag:

6
  • Two questions I have for this. 1) How would you dynamically getChildren? Using an ID or something? and 2) You would need to code the exact nav structure and there wouldn't be any flexibility for the client to add another level if they needed to.
    – Jacob Graf
    Commented Aug 12, 2014 at 19:23
  • @JacobGraf I just updated my post. This should answer your question on how to get the child entries dynamically. And about that second concern, isn't the exact control over the HTML output of each hierarchy level what you want to achieve?
    – carlcs
    Commented Aug 13, 2014 at 13:40
  • This was the answer I was looking for. I appreciate your help. Not sure why you had to insert the rude comments in your responses, but I appreciate it anyway.
    – Jacob Graf
    Commented Aug 14, 2014 at 14:28
  • Yes you're right, @Jacob. There was no need for being rude, sorry!
    – carlcs
    Commented Aug 14, 2014 at 15:56
  • What's the .id('not 1') for when grabbing the top level entries?
    – veritascs
    Commented Oct 24, 2014 at 18:05
6

It sounds like you want utmost flexibility. So instead of trying to write any code for you, let me just describe all of the tools which should make this possible...


To begin, you definitely want a Structure section. Then your clients will have the complete control that you are looking for... adding, removing, and rearranging their entries (aka pages).

{% nav %}

This one is pretty obvious. Run the whole section through a nav tag like this:

{% nav entry in craft.entries.section('pages') %}

Within that loop, you can use some of these other tools...

{% ifchildren %}

Also pretty obvious. You'll need this to run sub-loops for all of your nested entries. Every time an entry contains sub-entries, this Twig tag will be triggered (no matter how deep it goes).

entry.hasDescendants()

Optional. If you want to check if an entry has any children without actually navigating down that branch, you can use this function. In fact, many of the EntryModel methods can be useful to you in various circumstances.

entry.level

This will tell you exactly what level you are on. So for example, "1" is the top level, "2" is their respective children, etc.

entry.type

If you're using various entry types (which you probably are), then this will help you sort out which type of entry you're dealing with at that exact moment.

Bonus: {% cache %}

You can wrap the whole nav in a cache tag to make it easier for the system to deal with in the future.


If you feel like I missed something, or that you still don't have the control that you want, just let me know what you're trying to accomplish. I can 99% guarantee that there is an existing Craft way of handling it.

Hope that helps!

5
  • {% nav entry in entries %} makes sense if you want to make use of {% children %}. If you want more control over the html of nested items, I'd use basic for loops: {% for entry in entries %}.
    – carlcs
    Commented Aug 13, 2014 at 8:54
  • I meant the self-nesting feature of the {% children %} tag.
    – carlcs
    Commented Aug 13, 2014 at 9:25
  • @carlcs He specifies that his nav has children... It seems like a perfect fit to me.
    – Lindsey D
    Commented Aug 13, 2014 at 16:24
  • Ok, I didn't know that you can use methods like level or hasDescendants within the {% nav %} tag. But that's great if you can! Always thought {% nav %} is something very special and that it is restricted to the simple use case shown in the docs. Thanks for the info, Lindsey!
    – carlcs
    Commented Aug 14, 2014 at 16:21
  • Using {% nav %} in combination with level is probably the cleanest way to add further HTML to a navigation. Marion's solution is really clever, but it seems kind of hacky to "misuse" macros like this.
    – carlcs
    Commented Aug 14, 2014 at 16:22
4

I am a big fan of using a recursive macro when the {% nav %} tag is not enough. This answer has an example.

2

I've posted a recipe for dynamically-generated nav menus which encompasses a lot of logic to determine the active page/section/etc: Fully Dynamic Hierarchical Nav Menu Template

0

IMO, this is a much more flexible soloution, albeit a plugin: https://github.com/am-impact/amnav/

I have used the methods above, but kept running into issues where a client's single exception to the navigation structure would cause a bunch of extra work. For example, they might want a single in the middle of a structure hierarchy or a channel entry as the child of a single, typical requests for clients.

Using a dedicated structure for the menus and another for the content only confuses clients and they end up messing things up in result.

Using the A&M nav plugin solved most of these issues. You can change the Menu label to whatever you want, and having a menu called 'Menus' is much easier for the client than searching through the entries for the 'menu structure' The only drawback to the plugin is that the menus must be added to the manually, however there is a handy Navigation field type to handle that.

Hope this helps.

1
  • Why did you use two structures, one for the nav and one for the content / metadata. I think it's the whole advantage of that approach to hAve it all together in one structure. To "import" foreign entries from other sections an entry type with an entries field is all you need.
    – carlcs
    Commented Aug 6, 2015 at 19:57

Your Answer

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

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