17

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 %}

7 Answers 7

19

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

5
  • 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. Commented Jun 12, 2014 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. Commented Jun 13, 2014 at 10:03
  • How would you add an active class to each link with this configuration?
    – KSPR
    Commented May 21, 2015 at 13:25
  • For each link in the loop just add an if craft.request.url == this link Commented May 22, 2015 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. Commented Mar 16, 2018 at 17:07
14

I pulled together this brain-dump of 8 different ways of doing navigation. There are 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. Don't be afraid of hardcoding things, it's a totally acceptable solution.

One "mistake" (in my opinion) that I see often in inherited sites is a navigation system that is a direct reflection of the actual content - usually this takes the form of a multi-level "Pages" Structure section that contains everything in the entire site! Ultimately, your content model should be fully divorced from your actual navigation: for a more thorough monologue about why that's a bad idea in any CMS, not just Craft, see my article, Content Utopia.

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 (see example code below)
    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 unnecessary 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 called primaryNavigation in a global set called navigation 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>
8

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.

1
  • That looks very cool, since it is for a super simple Navi.
    – KSPR
    Commented Jun 12, 2014 at 18:50
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 %}
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)

2
  • What does [] in the first variable do?
    – KSPR
    Commented Jun 12, 2014 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. Commented Jun 12, 2014 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 %}
1
  • 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) Commented Feb 1, 2017 at 16:53
2

This doesn't work for every project, but for brochure style sites I like having all the client-arrangeable pages in one Structure, with toggles on each entry to optionally show in primary or secondary navigation, and Entry Types for different requirements such as a team page or articles section, and reserving Singles for things like homepage, privacy policy etc. It makes things very intuitive for clients to get the hang of (even though it makes it easier for them to potentially break things).

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.