Responsive Hybrid Navigation

Heads Up!

This article is several years old now, and much has happened since then, so please keep that in mind while reading it.

In this post, I want to help Umbracians with a recurring topic that is not always simple to handle: The Navigation. We will work with Umbraco 7 as I'm not familiar enough with the 8th version yet.

This tutorial will start with a quick view on the Bootstrap 3 Navigation we will use. It will continue with the building of an automated navigation based on Home sub-pages. The third part will cover a manual navigation relying on selected pages. And we will eventually see what could still be improved from there.

 

The Navigation

First, let's create the backbone of the Navigation.

To emphasize the Umbraco side, we will not spend a lot of time on the backbone of the navigation. That's why I opted for the Bootstrap navigation. It is widely used, responsive and compatible with most browsers.

Why did I choose the 3rd version of Bootstrap and not the 4th? I wanted to have a navigation fully working in IE9 and IE10 (Internet Explorer); which is not possible in the latest version of Bootstrap.

If you are not familiar with Bootstrap 3, don't hesitate to read the documentation on How to get started and on the Navigation component.

 

Here is the base code I will use for the navigation:

<nav class="navbar navbar-default" role="navigation">
    <div class="container">

        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span>
            </button>

            <a class="navbar-brand" href="/">Site Name</a>
        </div>

        <div class="collapse navbar-collapse">
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Link</a></li>
                <li><a href="#">Link</a></li>
                <li><a href="#">Link</a></li>
                <!-- More links will be created, it's just an example. -->
            </ul>
        </div>

    </div>
</nav>

This will give us a responsive navigation with the name of the site on the left and links on the right.

Boostrap 3 navigation

Since this is working we will now start to work on the Umbraco side of the navigation.

 

The Document Type

You can set the navigation in your homepage document type or anywhere else. Since I usually work with a 'Navigation' node which is a child of a 'Configuration' node, that's what I'll do in this tutorial. 

Here are the properties we need to create:

  • A first TrueFalse to opt for automated navigation or not
  • A MultiUrlPicker for the manual navigation links
  • A second TrueFalse to opt for the display of sub-pages or not

Navigation document type

 

Create the Navigation

Here is what your content tree could look like.

Note: Don't forget to set the Permissions before trying to create your Navigation.

Navigation tree

And here is what the Navigation content could look like.

Navigation content

 

Code the Navigation

Let's dive into the coding of this Navigation: first the header of the navigation followed by the collapsible part; this part divided into an automated version and a manual one.

 

Navigation Header

We will first take back our HTML code and add the name of the root to use it in the navigation. We'll achieve that with the AncestorOrSelf() method.

We also want to create a variable pointing to our navigation settings that will be useful in the next steps.

Note: You can replace the Brand Name with a logo.

@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@using Umbraco.Web.Models

@{
    var navigation = Umbraco.TypedContentSingleAtXPath("//navigationSettings");
}

<nav class="navbar navbar-default" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">
                @(Model.Content.AncestorOrSelf(1).Name)
            </a>
        </div>

        <div class="collapse navbar-collapse">
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Link</a></li>
                <li><a href="#">Link</a></li>
                <li><a href="#">Link</a></li>
            </ul>
        </div>
    </div>
</nav>

 

Automated navigation links

To collect the links in an automated way, we will first check the automated navigation option is selected and list children of the root filtering on their Document Type to be sure to list the right elements. Finally, if the option to display sub-pages is on, list grand children of the Root. Again, filtering on the Document Type.

<div class="collapse navbar-collapse">
    <ul class="nav navbar-nav navbar-right">
        @{
            @* automated navigation *@
            // check the navigation is automated
            if (navigation.GetPropertyValue<bool>("automatedNavigation"))
            {
                // list children of the root
                var homeChildren = Model.Content.AncestorOrSelf(1).Children.Where(x => x.DocumentTypeAlias == "yourPageTypeAlias" || x.DocumentTypeAlias == "anotherPageTypeAlias");
                bool showSubpages = navigation.GetPropertyValue<bool>("displaySubPages");
                // make sure the root has children
                if (homeChildren.Any())
                {
                    foreach (var page in homeChildren)
                    {
                        @* manages subpage of a link *@
                        var pageChildren = page.Children.Where(x => x.DocumentTypeAlias == "yourPageTypeAlias" || x.DocumentTypeAlias == "anotherPageTypeAlias");
                        // check root has grand children and the navigation has to show sub pages
                        if (pageChildren.Any() && showSubpages)
                        {
                            <li class="dropdown">
                                <a href="@page.Url" role="button" aria-haspopup="true" aria-expanded="false">@page.Name <span class="caret"></span></a>
                                <ul class="dropdown-menu">
                                    @foreach (var subpage in pageChildren)
                                    {
                                        <li><a href="@subpage.Url">@subpage.Name</a></li>
                                    }
                                </ul>
                            </li>
                        }
                        else
                        {
                            <li><a href="@page.Url">@page.Name</a></li>
                        }
                    }
                }
            }
            @* / automated navigation *@
        }
    </ul>
</div>

Manual navigation links

To collect the links added manually, we will first check the automated navigation option isn't selected and list the links. Finally, if the option to display sub-pages is on, list children of the links filtering on their Document Type.

<div class="collapse navbar-collapse">
    <ul class="nav navbar-nav navbar-right">
        @{
            @* manual navigation *@
            // check the navigation isn't automated
            else
            {
                // take the list of links
                var mainLinks = navigation.GetPropertyValue<IEnumerable<Link>>("mainNavigation");
                bool showSubpages = navigation.GetPropertyValue<bool>("displaySubPages");
                // make sure there are links
                if (mainLinks.Any())
                {
                    foreach (var link in mainLinks)
                    {
                        var linkId = Umbraco.GetIdForUdi(link.Udi);
                        if (linkId > 0)
                        {
                            @* manages subpage of a link *@
                            var linkChildren = ((IEnumerable<dynamic>)Umbraco.Content(linkId).Children).Where(x => x.DocumentTypeAlias == "yourPageTypeAlias" || x.DocumentTypeAlias == "anotherPageTypeAlias");
                            // check links have children and the navigation has to show sub pages
                            if (linkChildren.Any() && showSubpages)
                            {
                                <li class="dropdown">
                                    <a href="@link.Url" target="@link.Target" role="button" aria-haspopup="true" aria-expanded="false">@link.Name <span class="caret"></span></a>
                                    <ul class="dropdown-menu">
                                        @foreach (var sublink in linkChildren)
                                        {
                                            <li><a href="@sublink.Url">@sublink.Name</a></li>
                                        }
                                    </ul>
                                </li>
                            }
                            else
                            {
                                <li><a href="@link.Url" target="@link.Target">@link.Name</a></li>
                            }
                        }
                        else
                        {
                            <li><a href="@link.Url" target="@link.Target">@link.Name</a></li>
                        }
                    }
                }
            }
            @* / manual navigation *@
        }
    </ul>
</div>

Full navigation code

Here is the full code if you need it.

@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@using Umbraco.Web.Models

@{
    var navigation = Umbraco.TypedContentSingleAtXPath("//navigationSettings");
}

<nav class="navbar navbar-default" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">
                @(Model.Content.AncestorOrSelf(1).Name)
            </a>
        </div>

        <div class="collapse navbar-collapse">
            <ul class="nav navbar-nav navbar-right">
                @{
                    @* automated navigation *@
                    if (navigation.GetPropertyValue<bool>("automatedNavigation"))
                    {
                        var homeChildren = Model.Content.AncestorOrSelf(1).Children.Where(x => x.DocumentTypeAlias == "yourPageTypeAlias" || x.DocumentTypeAlias == "anotherPageTypeAlias");
                        bool showSubpages = navigation.GetPropertyValue<bool>("displaySubPages");
                        if (homeChildren.Any())
                        {
                            foreach (var page in homeChildren)
                            {
                                @* manages subpage of a link *@
                                var pageChildren = page.Children.Where(x => x.DocumentTypeAlias == "yourPageTypeAlias" || x.DocumentTypeAlias == "anotherPageTypeAlias");
                                if (pageChildren.Any() && showSubpages)
                                {
                                    <li class="dropdown">
                                        <a href="@page.Url" role="button" aria-haspopup="true" aria-expanded="false">@page.Name <span class="caret"></span></a>
                                        <ul class="dropdown-menu">
                                            @foreach (var subpage in pageChildren)
                                            {
                                                <li><a href="@subpage.Url">@subpage.Name</a></li>
                                            }
                                        </ul>
                                    </li>
                                }
                                else
                                {
                                    <li><a href="@page.Url">@page.Name</a></li>
                                }
                            }
                        }
                    }
                    @* / automated navigation *@

                    @* manual navigation *@
                    else
                    {
                        var mainLinks = navigation.GetPropertyValue<IEnumerable<Link>>("mainNavigation");
                        bool showSubpages = navigation.GetPropertyValue<bool>("displaySubPages");
                        if (mainLinks.Any())
                        {
                            foreach (var link in mainLinks)
                            {
                                var linkId = Umbraco.GetIdForUdi(link.Udi);
                                if (linkId > 0)
                                {
                                    @* manages subpage of a link *@
                                    var linkChildren = ((IEnumerable<dynamic>)Umbraco.Content(linkId).Children).Where(x => x.DocumentTypeAlias == "yourPageTypeAlias" || x.DocumentTypeAlias == "anotherPageTypeAlias");
                                    if (linkChildren.Any() && showSubpages)
                                    {
                                        <li class="dropdown">
                                            <a href="@link.Url" target="@link.Target" role="button" aria-haspopup="true" aria-expanded="false">@link.Name <span class="caret"></span></a>
                                            <ul class="dropdown-menu">
                                                @foreach (var sublink in linkChildren)
                                                {
                                                    <li><a href="@sublink.Url">@sublink.Name</a></li>
                                                }
                                            </ul>
                                        </li>
                                    }
                                    else
                                    {
                                        <li><a href="@link.Url" target="@link.Target">@link.Name</a></li>
                                    }
                                }
                                else
                                {
                                    <li><a href="@link.Url" target="@link.Target">@link.Name</a></li>
                                }
                            }
                        }
                    }
                    @* / manual navigation *@
                }
            </ul>
        </div>
    </div>
</nav>

Note: Don't forget to add this code in your Master template.

In my example, here is how the navigation looks like after all this preparation work.

Navigation


Modify the CSS

I prefer the sub navigation to open on hover than on click, that's why there isn't data-toggle="dropdown" in my HTML. In order to make the sub navigation work that way, we need to add some additional CSS to overwrite Bootstrap one. Here it is:

/* handles dropdown menu on small screen to be shown correctly */
@media only screen and (max-width: 767px) {
    .dropdown-menu {
        display: block;
        position: relative;
        float: none;
        border: none;
        webkit-box-shadow: none;
        box-shadow: none;
    }
}

/* handles dropdown on hover */
@media only screen and (min-width: 768px) {
    .dropdown:hover .dropdown-menu {
        display: block;
    }
}

 

Improve the Navigation

There is always room for improvements. For example there could be a third level (and more) in the navigation, or a Call-to-Action link, or styling choices in the Document Type.

Also working with Umbraco V8 instead of V7. My knowledge is limited for the brand new version but it could make the code even shorter.

Feel free to point out any flaws in my code you might find and suggest ideas for improvements.

Rémy Beumier

Rémy is on Twitter as