2

As some of you may have seen I've been asking on here and on Slack regarding the best way to go about creating a navigation on a site.

As yet, nothing I have tried is working and any code that I've been given to try also hasn't worked. (Thanks to everyone who has given code so far though).

Therefore I have literally stripped down everything in the hope that someone might be able to tell me where I'm going wrong as I'm sure that something as simple as this shouldn't be as difficult.

I have a completely fresh install of Craft 2 with the standard templates that come with Craft and I have then created a Structure called (creatively enough) Navigation of which the hierarchy can be seen below.

Home Services ---- Service 1 ---- Service 2 --------- Sub Service 1 ---- Service 3 Galleries ---- Gallery 1 ---- Gallery 2 ............ The above is actually just an example but reflects the sort of items that could go into the site.

Any of the items above could also be either an entry, a single or a template that has a channel on it and so on and so forth.

First up I'm not sure if I actually need to place any information into the Entry URL Format and Entry Template settings for the section as I'm guessing these aren't maybe all that important as the section is literally just being used for creating navigation on the site. That said I did fill them out with standard information just in case.

Next on the standard news/_entry.html template I placed this code :

{% set pages = craft.entries.section('navigation') %} <nav> <ul> {% nav page in pages %} <li> {{ page.getLink() }} {% ifchildren %} <ul> {% children %} </ul> {% endifchildren %} </li> {% endnav %} </ul> </nav>

This allows me to spit out the entire navigation which is good so far but in my Structure section I have an Entries field which allows me to choose an entry. I first of all have no idea of how to spit out this as the link instead of the {{ page.getLink() }} code above.

My Entries field is called singleEntrySelector and I tried {{ page.singleEntrySelector }} to see if it would spit anything out but that didn't work and I got an error on the page.

So I'm wondering how do I go about spitting out the content of the singleEntrySelector field so that my navigation items can point to the correct URL?

Once that is working I'd like to have it so that when I'm on say Sub Service 1 that I can create a secondary navigation on the page which will show me everything from Services down to (in this example) Service 3 so pretty much a very standard secondary navigation output.

That should (I'm hoping) allow me to do what I need here although I am definitely open to other ways of approaching this if I'm completely seeing things wrong here?

Many thanks,

Mark

P.S. I'd also like to be able to in the end spit out the navigation using whatever HTML code I need to as I will be utilising this on a responsive site and so have no idea yet as to what code I will need to encapsulate it all in. Therefore if the {% nav %} tag isn't the right fit here in that it won't allow me to do that then I'd really appreciate being shown how to spit it out in some other way that will allow me to do just that.

6

First up I'm not sure if I actually need to place any information into the Entry URL Format and Entry Template settings for the section

Nope, you can probably just un-check the Entries in this section have their own URLs checkbox in the Section settings and don't worry about it – I'm guessing the nav items should only function as pointers to "actual" entries, and won't need their own URLs or template.

[...] in my Structure section I have an Entries field which allows me to choose an entry. I first of all have no idea of how to spit out this as the link instead of the {{ page.getLink() }} code above.

You can't use the getLink() function when the URL you want is the URL for the entry in the Entries field, and not the actual URL for the nav item (phew). You'll have to build the <a> tag yourself – something like this should work:

{% set pages = craft.entries.section('navigation') %}
<nav>
    <ul>
        {% nav page in pages %}
        <li>
            <a href="{{ page.singleEntrySelector.first().url|default }}">{{ page.title }}</a>
            {% ifchildren %}
                <ul>
                    {% children %}
                </ul>
            {% endifchildren %}
        </li>
        {% endnav %}
    </ul>
</nav>

The crucial part here is the page.singleEntrySelector.first().url|default part for the <a> tags's href attribute.

It's important to realize that whenever you call an Entries attribute/field – such as page. singleEntrySelector in this case – Craft returns an instance of ElementCriteriaModel, which essentially is an array-like object. That means that even if the Entries field only has 1 entry inside it, you can't call page. singleEntrySelector.url directly – you'll need to explicitly select an entry inside the model, either by looping over it, calling a specific index (page. singleEntrySelector[0].url) or use the first() method to return the first entry. This is true for Asset, Tags, User and Category fields as well – basically any fieldtype that returns one or more related elements.

If you're wondering about the |default filter applied to the URL, that's there to prevent Craft from throwing an error if the page doesn't actually have an entry selected in the singleEntrySelector field. If you make the latter a required field, you can skip the |default part.

[...] I'd like to have it so that when I'm on say Sub Service 1 that I can create a secondary navigation on the page which will show me everything from Services down to (in this example) Service 3 so pretty much a very standard secondary navigation output.

To output a sub navigation containing all nav "descendants" of the current entry, you'll first need to find the relevant nav item using the current entry in a relation query. Then, you can use the descendantOf() method to build the pages array, using the nav item as the ancestor:

{% set navItem = craft.entries.section('navigation').relatedTo(entry).first() %}
{% if navItem %}
{% set pages = craft.entries.descendantOf(navItem) %}
<nav>
    <ul>
        {% nav page in pages %}
            <li>
                <a href="{{ page.singleEntrySelector.first().url | default }}">{{ page.title }}</a>
            ...
{% endif %}

If you want the sub nav to display the entire sub tree for the current entry – not just the descendents – this is how that could look:

{# Get the related nav item for the current entry #}
{% set navItem = craft.entries.section('navigation').relatedTo(entry).first() %}
{% if navItem %}
    {# Get the top level item #}
    {% set topItem = navItem.getAncestors().level(1).first()|default(navItem) %}
    {# Build a "pages" array w/ the top item and its descendents (child entries) #}
{% set pages = craft.entries.id([topItem.id]|merge(topItem.getDescendants().ids()|default([]))) %}
<nav>
    <ul>
    ....

Note: If you use the same Entry for the Entries field multiple times in your Navigation Structure, results may vary. The examples above will pull the first nav item that has a relationship with the current entry, and disregard any others. However, a many-to-one relationship between the Navigation Structure's entries and the entries in the Entries field, with single sub tree output for the sub nav doesn't really compute – so I've disregarded that particular issue and assumed the relationship will always be one-to-one.

  • Thanks for the nice, detailed answer, much appreciated. Actually I did end up getting the singleEntrySelector field to output the link however I did it like this - {{ page.singleEntrySelector.first().getLink() }}. Not sure if that's the right thing to do or not although it did indeed spit out the correct link that I was looking for. Don't really know what the difference here is though in doing it that way? – Mark Bowen May 28 '15 at 9:46
  • Also trying out your last piece of code to create a subnavigation wouldn't work for me I'm afraid. I placed it on the page with the same <nav> code as you showed above in your first piece of code but I just end up getting the whole navigation being spat out again as opposed to just the items in that section. Not sure where I'm going wrong with that though? – Mark Bowen May 28 '15 at 9:48
  • Actually I lie. I am getting no output at all. I had not renamed pages to subpages and so was still getting the output from the first piece of code. I am definitely not getting any output at all from a secondary navigation. – Mark Bowen May 28 '15 at 9:50
  • Yeah actually the entry.id will be wrong in my example - you need to do a relational query to get the nav item ID from the related entry ID. I'll have to look at that later, I'm afraid. – Mats Mikkel Rummelhoff May 28 '15 at 9:55
  • No problem at all. Many thanks for all of this. I can most definitely wait for the answer. :-) – Mark Bowen May 28 '15 at 9:57
1

Lots of different ways to skin the snake here but depending on how big your site site is, why create a separate structure section to begin with?

For a recent build, I created a "Pages" structure section and put most of the relevant content in there. For "feeds" of data, like a Gallery or News or Employees and what not, you definitely want to use separate Channel sections for those, though, and just pull in the relevant channel from your templates.

Coming from WordPress, it always annoyed me having to do the 2-step of creating a page and then hooking it up to the navigation and I found my clients agreed. They like that approach of mirroring the site structure with how the navigation actually works on the site.

That allows them (or you) to rearrange content very easily if necessary. In the Pages Structure, I also put a separate lightswitch that allows them to hide or show that page in the navigation depending on how they're using... for example, a landing page or something that they don't want to go "live" with just yet.

Code from that site (put this in your _layout.html file or whatever you're using as main site"shell") if you want to use it sitewide:

              {% macro addClass(t) %}
                 {%if t.level == 1 and t.slug != "news" %}has-dropdown{%endif%}
                 {%if t.slug == "login" %} hide-for-large-up{%endif%}
              {% endmacro %}

              {% set pages = craft.entries({
                             section: 'pages',entryType:'not landingPage'
                             }).showInNavigation('1').level('<=2') 
              %}

          <ul>
            {% nav page in pages %}

                <li class="{{ _self.addClass(page)}}">

                    {% switch page.type %}

                    {%case "linkToPage" %}
                         <a href="{{page.pageLink}}">{{page.title}}</a>

                    {% default %}
                        {{ page.getLink() }}
                    {%endswitch%}


                    {% ifchildren %}
                        <ul class="dropdown">
                            {% children %}
                        </ul>
                    {% endifchildren %}

                </li>


            {% endnav %}
         </ul>

I have an entry type called linktoPage which has a pageLink field on there (so the client can put in links to outside Craft install, for example their portal). There's some Foundation 5 presentation stuff in there (addClass) to get the top nav working. I hate sub navigation on drop down menus so I'm only telling Craft to spit out the first 2 levels there but to get multiple nested child levels like in your example just remove .level('<=2').

Again, YMMV, especially on what content you have.

  • Hiya. Many thanks for the code and the insights there. All very welcomed. Luckily the client won't mind adding entries directly to the navigation structure and it (to me anyway) feels like it will be better this way. I'm just thinking that if you want to take an entry out of the navigation then you can't just delete it as it would be gone forever and the only other way would be to disable it in the Structure which seems a little messy in itself. I may be wrong on that though and there might be a way around that though? So many ways to do things. There's never just one is there? ;-) – Mark Bowen May 29 '15 at 7:51
  • No doubt, there isn't just one solution! The lightswitch is there in the entry to disable it from showing up in the nav but to still keep it alive as a functioning entry. I've had clients come from WordPress where they actually do delete the entry/post and wonder why it's still shows up in the nav and then 404s. So yeh, there is no one right answer. I see benefits to both approaches, depending on the site. Every client is different—as always, no matter how you do it, just make sure they know how to use whatever solution you provide. – RitterKnight May 29 '15 at 17:59
  • Yep you're absolutely right there. Thank you again for the kind insight and code though. I'm sure it could well come in handy at some point and if not for me then I'm sure for others who are looking for this sort of thing. Many thanks. – Mark Bowen May 29 '15 at 18:00

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.