16

After endless trial and error I figured out the right syntax for a simple one level navigation.

{% set navi = craft.entries.section('not projekteintrag' ) %} 
{% for entry in navi.order('id asc') %}
  <li><a href="{{ entry.url }}">{{ entry.navigationTitle }}</a></li>
{% endfor %}
  1. Is there a parameter to just trigger single pages? Right now I grabbed the singles with filtering the entries.
  2. What is the difference between writing the upper syntax and:

-

{% for entry in craft.entries.section('not projekteintrag' ).order('id asc') %}
  <li><a href="{{ entry.url }}">{{ entry.navigationTitle }}</a></li>
{% endfor %}
14

My solution is to setup a dedicated "Menu" section and use an entries field type for the links. This allows you to pull together entries from multiple sections into a single menu.

My normal setup is to have to custom fields

  • relatedEntry - Entries field allowing you to select any other entry
  • customURL - text field for custom url input

The idea being that you could fill in either or and have a dynamic menu with links to entries or custom links.

Then on the template side you simple would do this

{% set menu = craft.entries.section('menu') %}
<ul> 
{% nav link in menu %}
    <li>

        {% if link.relatedEntry|length %}
                <a href="{{ link.relatedEntry[0].url }}">{{ link.title }}</a>
        {% else %}
                <a href="{{ link.customURL }}">{{ link.title }}</a>
        {% endif %}

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

I hope that helps

  • 2
    This is the best answer in my opinion and if you want to ensure it's a single level you need only to set the structure section to a max level of 1 and remove the {% ifchildern %} tags from the above code. Complete flexibility for nav link order, where links go, etc. – Stuart McCoy Jun 12 '14 at 17:14
  • I have also used this method on 4 sites now and it works well. I just wish we could add the fields to a tab in a field set instead of needing a separate section. – Bryan Garrant Jun 13 '14 at 10:03
  • How would you add an active class to each link with this configuration? – KSPR May 21 '15 at 13:25
  • For each link in the loop just add an if craft.request.url == this link – Keith Mancuso May 22 '15 at 11:40
  • The main reason I don't like this approach (other than the performance hit), is that it requires the editor to manage the menu in the 'Entries' area, when intuitively it feels like navigation should be managed in the 'Globals' area. – James Smith Mar 16 '18 at 17:07
10

I pulled together this brain-dump of 8 different ways of doing navigation. There's probably more. With any method make sure you keep a close eye on the query count and load speed, as some of these methods are very resource-intensive. You can mitigate with the cache tag, but ideally should strive for simplicity where possible...

nav approaches

  1. Completely hardcoded
    Pros: Pure awesome
    Cons: Not translatable, no client access
  2. A global table with plain text columns for URI and label
    Pros: Light (zero extra queries), flexibility to add any URI
    Cons: No entry selector possible - need to find (and update) URIs manually. No child items (update: actually, you can add a column to mark items as 'level-2'... the template coding to gather them up and build nested lists under the previous parent item is a little complex, but it works well... if you only need 2 levels!).
  3. Hardcoded but with static translations
    Pros: Fairly awesome
    Cons: A bit fiddly, and need to translate URIs manually. No client access.
  4. A structure with fields for entry select and arbitrary URL override
    Pros: Can accommodate child items easily using craft's nav tag. Intuitive UI.
    Cons: Heavy query count, hard to administer all nav items in one go as they're separate entries. Nav management really belongs in Globals IMO, not Entries.
  5. A global matrix with fields for entry select, label override, and arbitrary URL override
    Pros: Very flexible
    Cons: Heavy query count (8 with eager loading, 10 without). Matrix interface is a bit unwieldy just for a list of links. Can't handle child items without adding a table or supertable block.
  6. A global entries selector
    Pros: Nice simple interface. Automatically updating URIs and labels
    Cons: Fairly heavy query count. No flexibility to override (unless you put extra fields against the entries). No child items (without adding fields to each entry). No arbitrary items (everything must be an entry).
  7. Hybrid. Depending on the nature of the site, some parts of the nav could be pulled from section entries or category groups while others parts are hardcoded.
    Pros: Lots of client control. Changeable dynamic nav.
    Cons: Fairly heavy query count (use cache tag). Nav is not fully administered in one single place.
  8. A plugin (ugh)
    Cons: it's an unecessary plugin, just don't do it!

Just in case anyone else likes the idea of #2 above (I think it's becoming my favourite approach personally), here's how you can use that and also cater for a second level of child items. Probably pushes Twig into overly-complex territory, but I'd still rather do this than install a plugin. This approach uses a global table field with 3 columns: Label (text), Link (text), Level2 (checkbox).

{# ======================================
FIRST, LET's MAKE A HASH MAP to match up 
parents with their children based on their
position in the list
========================================= #}

{% set primaryNav = navigation.primaryNavigation %}
{% set children = {} %}

{% for item in primaryNav %}
    {% set targetArray = {} %}

    {% if item.level2 %}
        {% if attribute(children, lastKnownTopLevelItem) is not defined %}
            {% set children = children|merge({(lastKnownTopLevelItem): [item]}) %}
        {% else %}
            {% set targetArray = attribute(children, lastKnownTopLevelItem) %}
            {% set targetArray = targetArray|merge([item]) %}
            {% set children = children|merge({(lastKnownTopLevelItem): targetArray}) %}
        {% endif %}
    {% endif %}

    {# make a note of the loop index of the last known top level item... #}
    {% if not item.level2 %}
        {% set lastKnownTopLevelItem = 'children-of-' ~ loop.index %}
    {% endif %}
{% endfor %}

{# ======================================
NOW OUTPUT...
========================================= #}

<ul>
    {# just loop through top level, as we will fetch child items from the hash map... beware, don't combine the 'not level2' conditional into the for loop as otherwise the loop indices won't match up! #}
    {% for item in primaryNav %}
        {% if not item.level2 %}

            {% set childrenOfThisItem = attribute(children,'children-of-' ~ loop.index) ?? {} %}

            {% if childrenOfThisItem|length %}
                <li>
                    <a href="{{ item.link }}">{{ item.label }}</a>
                    <ul>
                        {% for child in childrenOfThisItem %}
                            <li><a href="{{ child.link }}">{{ child.label }}</a></li>
                        {% endfor %}
                    </ul>
                </li>
            {% else %}
                <li><a href="{{ item.link }}">{{ item.label }}</a></li>
            {% endif %}
        {% endif %}
    {% endfor %}
</ul>
7

If you have more complex menus in the future, you should take a look at the Nav-Tag.

.

Getting singles:

There isn't an easy way to get singles (yet). But you can use the same method which is used in crafts backend:

{% set allSections = craft.sections.getAllSections() %}
{% set singleSections = [] %}

{% for section in allSections %}
    {% if section.type == 'single' %}
        {% set singleSections = singleSections|merge([section]) %}
    {% endif %}
{% endfor %}

{% set navi = craft.entries.section(singleSections) %}

.

Queries:

In your case there isn't a difference. But if you plan on using the same query twice, you should save it into a var and reuse it:

This example only makes one db call:

{% set navi = craft.entries.section('not projekteintrag' ).order('id asc') %}

{% for entry in navi %} ... {% endfor %}

{% for entry in navi %} ... {% endfor %}

{% for entry in navi %} ... {% endfor %}

But this makes three:

{% for entry in craft.entries.section('not projekteintrag' ).order('id asc') %} ... {% endfor %}

{% for entry in craft.entries.section('not projekteintrag' ).order('id asc') %} ... {% endfor %}

{% for entry in craft.entries.section('not projekteintrag' ).order('id asc') %} ... {% endfor %}
7

In some cases I use a pre-defined (very simple) nav that I don't need to be driven from the CMS yet I want to share it between the header / footer. In these cases I'll set an array in my base layout template so I can use it throughout.

In "_partials/layout.twig" template:

{% set nav = [
    {'title':'Home', 'link':siteUrl, 'class':'homepage'},
    {'title':'About', 'link':url('about'), 'class':'about-us'},
    {'title':'Contact', 'link':url('contact'), 'class':'contact-us'},
]%}

In "_partials/header.twig" template:

<nav>
{# logo, etc... #}
{% if (nav is defined) and (nav is iterable) %}
    <ul>
    {% for navItem in nav %}
        <li class="{{ navItem.class }}"><a href="{{ navItem.link }}">{{ navItem.title }}</a></li>
    {% endfor %}
    </ul>
{% endif %}
<nav>

In "_partials/footer.twig" template:

{% if (nav is defined) and (nav is iterable) %}
    <ul>
    {% for navItem in nav %}
        <li class="{{ navItem.class }}"><a href="{{ navItem.link }}">{{ navItem.title }}</a></li>
    {% endfor %}
    </ul>
{% endif %}

Very simple concept I know. But at the end of the day when I know the nav isn't going to change drastically or I don't want the end user to alter it, this is what I do. It has practically no impact on performance and allows me to manage the items in one place.

  • That looks very cool, since it is for a super simple Navi. – KSPR Jun 12 '14 at 18:50
4

1) This is not so simple of an answer. Craft itself uses JS to handle the content of indexes, so I'm not sure how they're doing this. Using what's available you would need to build a list of Single Sections, then pull entries based on that list.

{% set sections = [] %}
{% for section in craft.sections.getAllSections() %}
    {% if section.type == 'single' %}
        {% set sections = sections|merge([section]) %}
    {% endif %}
{% endfor %}

Then your query would be (however you choose to use it):

craft.entries.section(sections).order('id asc')

2) Both examples are the same. You can keep adding/editing criteria until you actually pull the entries by using an output function (find, first, etc) or using it in a loop (such as for and nav)

  • What does [] in the first variable do? – KSPR Jun 12 '14 at 18:51
  • 1
    It's an empty array. Later we add our section to it my making it a single-item array and merging them together. – Bryan Redeagle Jun 12 '14 at 19:08
4

Another easy way to handle a dynamic menu in Craft is to add a Matrix field to a Global.

For example, I have a Global called "Main Menu". That Global has a Matrix field called "Menu Item". I then have a block called "Item" that has two plain text fields. One field is for the "label" and one field is for the "URI" that I want users to be directed to.

Then in my template file I have the following.

{% for block in mainMenu.menu %}
    {% if block.type == "item" %}
      <li class="item">
        <a href="{{ block.itemUri }}">{{ block.label }}</a>
      </li> 
    {% endif %}
{% endfor %}
  • 2
    If you're only using plain text fields, you can do this more efficiently with a table fieldtype instead of a matrix. The matrix approach would be if you want to use an entries selector to get the URIs (however, I've found that to be highly inefficient, adding an extra 10 queries per page load) – James Smith Feb 1 '17 at 16:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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