Model website navigation

Navigation is a necessary part of every website. Well-ordered navigation menus provide an overview of how the website’s content or its segments are structured. It also helps keep users engaged and leads them to explore more of the site.

We recommend modeling website navigation using a dedicated section of the site’s content tree. Developers prepare ways to render the contents of this section where required (e.g., using .NET view components).

The general process of creating navigation consists of the following steps:

  1. Developers prepare a dedicated “Navigation item” content type and a dedicated section in the content tree (e.g., /Navigation_menu).
  2. Editors create pages of the “Navigation item” type in the dedicated section. Each page links to a specific page in the site’s content tree, selected using the Page selector.
    • The selector populates its assigned field with the GUIDs of selected pages, which is used to retrieve the corresponding pages and their URLs.
  3. Editors order the “Navigation item” pages into a desired hierarchy and order using the content tree.
    • This order represents the final state of the navigation menu on the live site.
  4.  Developers implement a view component that takes all pages under the menu section in the content tree and transforms them into the finalized menu component.

The following screenshot shows an example of a simple website navigation:

Navigation menu example

Model and add navigation menus

This section describes a step-by-step process developers can follow to prepare a navigation menu. Developers need to create:

  • A Navigation content type
  • A corresponding view component that displays all pages based on it from the /Navigation_menu section of the content tree.

Model the navigation item page type

  1. Open the Content types application and select New content type.

  2. Fill in the following values:

    • Content type name: Navigation
    • Namespace: MySite
    • Name: NavigationItem
    • Use for: Pages
    • Make sure Include in routing is not selected. You do not want navigation items to be accessible under their own URLs in the site’s routing.
  3. Select Save.

Define the fields used to hold page data.

  1. Select the Fields tab.
  2. Select New field and fill in the following values:
    • FieldName: NavigationItemName
    • Data type: Text
    • Field caption: Name
    • Form component: Text input
  3. Select New field and fill in the following values:
    • Field name: NavigationItemLink
    • Data type: Pages
    • Field caption: Select a page
    • Tooltip text: Select a page from the content tree. Make sure that the page you have selected has a URL address.
    • Form component: Page selector
    • Maximum number of pages: 1
    • Tree path: empty (this will enable editors to select any page)
  4. Save your changes.
  5. Switch to the General tab.
    1. Select a relevant icon, for example: xp-arrow-right-top-square
    2. Save your changes.
  6. Switch to the Allowed in channels tab.
    1. Select channels where you want to use the content type.
    2. Save your changes.

Adding a new navigation item in a website channel

The navigation page type is now ready. Generate its code file and make sure it’s included in your project.

To create complex, multi-level navigation menus, developers can define multiple content types that editors then combine to create a navigation menu hierarchy. For example, Top Nav menu container > Nav menu column > Nav menu item.

Implement a view component that renders the menu

The next steps focus on creating a view component that renders all items from under the /Navigation_menu section of the content tree.

Begin by creating a dedicated model class for menu items and use it to pass the minimum required data to your views – typically at least a text caption and a URL for the navigation link.

Menu view model


public class NavigationItemViewModel
{
    // Defines the properties of the NavigationItem view model
    public string MenuItemText { get; set; }
    public string MenuItemRelativeUrl { get; set; }
}

Next, create a view component that collects all “pages” under /Navigation_menu and transforms them into links to actual pages in the system.

When loading page data for a navigation menu:

  • Use the content item query API to retrieve pages that represent individual menu items from the designated content tree section. For more information about working with page data, see Retrieve page content.
  • To reflect the page order from the content tree in your menus, sort the page data using the WebPageItemOrder column.
  • Retrieve navigation item pages as strongly typed objects using the generated classes – NavigationItem.
  • Use the IWebPageUrlRetriever.Retrieve to retrieve URLs from the linked page GUIDs. For more information about URL retrieval, see Retrieve page content.
  • For improved performance, cache all retrieved data.
Menu view component


using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;

using Kentico.Content.Web.Mvc.Routing;
using CMS.Websites;
using CMS.ContentEngine;
using CMS.Websites.Routing;

using MySite.Models;

public class NavigationMenuViewComponent : ViewComponent
{
    private readonly IContentQueryExecutor executor;
    private readonly IWebPageUrlRetriever urlRetriever;
    private readonly IWebsiteChannelContext channelContext;
    private readonly IPreferredLanguageRetriever preferredLanguageRetriever;

    private const string MENU_PATH = "/Navigation_menu";

    public NavigationMenuViewComponent(IContentQueryExecutor contentQueryExecutor,
                                       IWebPageUrlRetriever pageUrlRetriever,
                                       IWebsiteChannelContext websiteChannelContext,
                                       IPreferredLanguageRetriever preferredLanguageRetriever)
    {
        // Initializes instances of required services using dependency injection
        this.executor = contentQueryExecutor;
        this.urlRetriever = pageUrlRetriever;
        this.channelContext = websiteChannelContext;
        this.preferredLanguageRetriever = preferredLanguageRetriever;
    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        // Gets all pages under the 'Navigation' node in the content tree
        var navigationPagesBuilder = new ContentItemQueryBuilder()
                        .ForContentType(
                            contentTypeName: NavigationItem.CONTENT_TYPE_NAME,
                            configureQuery: config => config
                                .ForWebsite(
                                    websiteChannelName: channelContext.WebsiteChannelName,
                                    pathMatch: PathMatch.Children(path: MENU_PATH,
                                                                  nestingLevel: 1))
                                .OrderBy(nameof(IWebPageFieldsSource.SystemFields.WebPageItemOrder))
                        ).InLanguage(preferredLanguageRetriever.Get());

        // Materializes the navigation items query
        IEnumerable<NavigationItem> navigationPages = await executor.GetMappedWebPageResult<NavigationItem>(navigationPagesBuilder);

        // Retrieves URLs of linked pages
        IDictionary<Guid, WebPageUrl> urls = await urlRetriever.Retrieve(
                                                        webPageItemGuids: navigationPages.Select(f => f.NavigationItemLink.FirstOrDefault().WebPageGuid)
                                                                                         .ToList()
                                                                                         .AsReadOnly(),
                                                        websiteChannelName: channelContext.WebsiteChannelName,
                                                        languageName: preferredLanguageRetriever.Get(),
                                                        forPreview: channelContext.IsPreview);

        // Creates a list of view models from retrieved data
        IEnumerable<NavigationItemViewModel> menuItems = navigationPages.Select(t => new NavigationItemViewModel {
            MenuItemRelativeUrl = urls[t.NavigationItemLink.First().WebPageGuid].RelativePath,
            MenuItemText = t.NavigationItemName
        });

        return View("~/Components/ViewComponents/NavigationMenu/Default.cshtml", menuItems);
    }
}

In the component’s view, render the collection of view models.

Menu view


@model IEnumerable<MySite.Models.NavigationItemViewModel>

<ul class="nav-menu">
    @foreach (var navigationItem in Model)
    {
        <li>
            <a href="@navigationItem.MenuItemRelativeUrl">@navigationItem.MenuItemText</a>
        </li>
    }
</ul>

Invoke the view component within your site’s pages or layout as required.