Creating a navigation menu

This page is a part of a tutorial, which you should follow sequentially from beginning to end. Go to the first page: Using the Xperience interface.

In the previous step of this tutorial, you have implemented the functionality that retrieves page data from the database and displays it on the live site. In this step, you will code a dynamic menu that allows your content editors to manage the website’s navigation.

Website navigation tends to change based on the site’s current content and the requirements of its owner. The Navigation item feature of page types allows content editors to set the Show in menu flag for individual pages, and thus control which pages from the content tree appear in the navigation menu.

On the side of the live site application’s code, you need to retrieve page data based on the Show in menu flag, and then handle the menu’s presentation, design, and behavior. This dynamic approach gives editors full control over the navigation menu items, without the need to adjust and redeploy the site’s code.

You will learn about:

Creating a view model for the menu items

  1. In Visual Studio, create a new Menu subfolder in the Models folder.

  2. Select the Menu subfolder and add a new MenuItemViewModel class.

  3. Define the view model properties using the following code:

    
    
    
     namespace MEDIOClinic.Models
     {
         public class MenuItemViewModel
         {
             // Defines the properties of the MenuItem view model
             public string MenuItemText { get; set; }
             public string MenuItemRelativeUrl { get; set; }         
         }
     }
    
    
     
  4. Save your changes.

Creating a view component for the navigation menu

We’ll add the site’s navigation menu using an ASP.NET Core’s view components. The view component’s InvokeAsync method loads and displays the content of the site’s navigation menu. The method retrieves the menu data and organizes it into a collection of MenuItemViewModels that gets passed to the component’s view.

  1. Create a new ViewComponents folder in your Core project.

  2. Add a new NavigationMenuViewComponent class to the folder.

  3. Replace the default code of the class with the following:

    
    
    
     using System.Threading.Tasks;
     using System.Collections.Generic;
     using System.Linq;
    
     using Microsoft.AspNetCore.Mvc;
    
     using CMS.Core;
     using CMS.DocumentEngine;
     using CMS.DocumentEngine.Routing;
    
     using Kentico.Content.Web.Mvc;
    
     using MEDIOClinic.Models;
    
     namespace CoreTutorial.ViewComponents
     {   
         public class NavigationMenuViewComponent : ViewComponent
         {
             private readonly IPageRetriever pageRetriever;
             private readonly IPageUrlRetriever pageUrlRetriever;
    
             public NavigationMenuViewComponent(IPageRetriever pageRetriever, IPageUrlRetriever pageUrlRetriever)
             {
                 // Initializes instances of required services using dependency injection
                 this.pageRetriever = pageRetriever;
                 this.pageUrlRetriever = pageUrlRetriever;
             }
    
             public async Task<IViewComponentResult> InvokeAsync()
             {
                 // Retrieves a collection of page objects with data for the menu (pages of all page types)
                 IEnumerable<TreeNode> menuItems = await pageRetriever.RetrieveAsync<TreeNode>(query => query
                                                         // Selects pages that have the 'Show in menu" flag enabled
                                                         .MenuItems()
                                                         // Only loads pages from the first level of the site's content tree
                                                         .NestingLevel(1)
                                                         // Filters the query to only retrieve required columns
                                                         .Columns("DocumentName", "NodeID", "NodeSiteID")
                                                         // Loads pages together with their URL path data (performance reasons)
                                                         .WithPageUrlPaths()
                                                         // Uses the menu item order from the content tree
                                                         .OrderByAscending("NodeOrder"));
    
                 // Creates a collection of view models based on the menu item data
                 IEnumerable<MenuItemViewModel> model = menuItems.Select(item => new MenuItemViewModel()
                 {
                     // Gets the name of the page as the menu item caption text
                     MenuItemText = item.DocumentName,
                     // Retrieves the URL for the page (as a relative virtual path)
                     MenuItemRelativeUrl = pageUrlRetriever.Retrieve(item).RelativePath
                 });
    
                 return View(model);
             }
         }
     }
    
    
     
  4. Save your changes.

The code retrieves page data for all page types using the Xperience DocumentQuery API. DocumentQuery provides an abstraction layer over your SQL database and allows you to call additional methods that adjust how the page data is retrieved.

For example, the OrderByAscending method sets the order of the retrieved data items, and the Columns method ensures that the underlying SQL query only loads the data columns that you need. For performance reasons, we strongly recommend limiting columns in all data retrieval API calls.

See the documentation if you wish to learn more: Working with pages in the API

Creating the navigation menu view

A navigation menu is an element that appears on most pages of a website. We’ll define the menu’s output in a partial view that is returned by our view component. The menu can then be added to the site’s main layout (and reused on other pages if necessary).

  1. In Visual Studio under ~/Views/Shared, create a new Components folder.

  2. Within the Components folder, create a new NavigationMenu folder.

  3. Inside the NavigationMenu folder, create a new view called Default.cshtml.

  4. Replace the default code of the view with:

    
    
    
     @using MEDIOClinic.Models;
    
     @model IEnumerable<MenuItemViewModel>
    
     <nav>
         @foreach (MenuItemViewModel menuItem in Model)
         {
             @* Iterates through the view model collection and renders HTML links for the menu items *@
             <a href="@menuItem.MenuItemRelativeUrl" title="@menuItem.MenuItemText">@menuItem.MenuItemText</a>
         }
     </nav>
    
    
     
  5. Save your changes.

Updating the website layout

You have coded the functionality behind the navigation menu. Let’s put the code to use and add the menu to the site’s layout.

  1. Edit your _Layout.cshtml file (in the Views/Shared folder).
  2. Locate the section defined by <nav> tags, and replace it with a Component.InvokeAsync(“NavigationMenu”) call. This renders our NavigationMenuViewComponent using the Default.cshtml view we created. Since this is an asynchronous operation, don’t forget to include the await directive!
  3. Locate the <a> element in the <header> section that displays the MEDIO clinic logo, and set the value of the href attribute to: ~/home

Your final _Layout.cshtml markup should look like this:




<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    @* Dynamically resolves the page's title *@
    <title>Medio Clinic - @ViewBag.Title</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css">
    <link href="https://fonts.googleapis.com/css?family=Lato:400,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
    <link href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet" type="text/css">
    <link href="~/css/styles.css" rel="stylesheet" type="text/css" />
    @* Razor section for additional page-specific styles *@
    @RenderSection("styles", required: false)
</head>
<body>
    <header>
        <div class="col-sm-offset-3 col-sm-6">
            <div class="col-sm-6">
                @* Targets the root page of the website *@
                <a href="~/home" title="MedioClinic homepage" class="logo">MEDIO clinic</a>
            </div>
            <div class="col-sm-6 nav">
                @* Renders the view component with the navigation menu, including the <nav> element *@
                @await Component.InvokeAsync("NavigationMenu")
            </div>
        </div>
        <div class="clearfix"></div>
    </header>
    @* Loads the content of your Tutorial's pages as sub views *@
    @RenderBody()
    <footer>
        <div class="col-sm-offset-3 col-sm-6">
            <div class="row">
                <div class="col-sm-6">
                    <h4>MEDIO clinic</h4>
                    <ul>
                        <li><i class="fa fa-map-marker"></i> Address: <address>7A Kentico street, Bedford, NH 03110, USA</address></li>
                        <li><i class="fa fa-envelope-o"></i> E-mail: <a href="mailto:info@medio-clinic.com" title="Email us">info@medio-clinic.com</a></li>
                        <li><i class="fa fa-phone"></i> Phone number: <a href="tel:5417543010" title="Phone us">(541) 754-3010</a>
                    </ul>
                </div>
                <div class="col-sm-6">
                    <span class="cms">Powered by <a href="https://xperience.io/" title="Kentico Xperience CMS">Kentico Xperience CMS for ASP.NET Core</a></span>
                </div>
            </div>
        </div>
        <div class="clearfix"></div>
    </footer>
    @* Razor section for additional page-specific scripts *@
    @RenderSection("scripts", required: false)
</body>
</html>


Build your project and test your navigation menu on the live site website. Because the original layout already contained hard-coded HTML links, you won’t see a difference on the live site. However, you can test the menu’s dynamic functionality by adding another page in the Xperience administration interface (Pages application), or by changing the order of the Home and Medical center items. The implementation you’ve built ensures any menu item changes are immediately displayed in the navigation menu.

You can download packages with the exported MEDIO clinic website and the MEDIOClinic project files. To compare the sample solution with your own implementation, import the website into your Xperience installation in the Sites application or view the code of the MVC application in Visual Studio.

Previous page: Displaying page content — Next page: Next steps in Xperience development

Completed pages: 9 of 10