Creating the 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 frequently change based on the site’s content and various other requirements. 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 MVC 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 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 the Menu controller

The controller class will define a GetMenu action that loads and displays the content of the site’s navigation menu. The action retrieves the menu data and organizes it into a collection of models that gets passed to the view.

  1. In the Controllers folder, create a new MenuController class.

  2. Replace the default code with the following:

    
    
    
     using System.Collections.Generic;
     using System.Linq;
     using System.Web.Mvc;
    
     using CMS.Core;
     using CMS.DocumentEngine;
     using CMS.DocumentEngine.Routing;
    
     using Kentico.Content.Web.Mvc;
    
     using MEDIOClinic.Models;
    
     namespace MEDIOClinic.Controllers
     {
         public class MenuController : Controller
         {
             private readonly IPageRetriever pageRetriever;
             private readonly IPageUrlRetriever urlRetriever;
    
             public MenuController()
             {
                 // Initializes instances of required services
                 // NOTE: This method of instantiating services is not recommended for 
                 // real-world projects and is only used for the sake of brevity in this tutorial.
                 // Instead, we recommend configuring a dependency injection container to resolve 
                 // object dependencies (e.g., Autofac). See the Xperience documentation for details.
                 pageRetriever = Service.Resolve<IPageRetriever>();
                 urlRetriever = Service.Resolve<IPageUrlRetriever>();
             }
    
             // GET: Loads and displays the site's navigation menu
             public ActionResult GetMenu()
             {                        
                 // Retrieves a collection of page objects with data for the menu (pages of all page types)
                 IEnumerable<TreeNode> menuItems = pageRetriever.Retrieve<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 include necessary 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 = urlRetriever.Retrieve(item).RelativePath
                 });
    
                 return PartialView("_SiteMenu", model);
             }
         }
     }
    
    
     
  3. 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 OrderBy 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. For this reason, you will define the menu’s output in a partial view, which can then be added to the site’s main layout (or into the views of other pages if necessary).

  1. In the MenuController, right-click the GetMenu method and select Add View.

  2. Fill in the following values:

    • View name: _SiteMenu
    • Template: Empty (without model)
    • Create as a partial view: Yes (selected)
  3. In the view code, add a using statement for the MEDIOClinic.Models namespace.

  4. Add the @model directive and specify the model type as an IEnumerable collection of MenuItemViewModel.

  5. Define the menu output by rendering HTML links within a <nav> element (using the menu item URL and text values from the model). The partial view code should look like this:

    
    
    
     @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="@Url.Content(menuItem.MenuItemRelativeUrl)" title="@menuItem.MenuItemText">@menuItem.MenuItemText</a>
         }
     </nav>
    
    
     
  6. 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 the Html.Action Razor call. Invoke the GetMenu action in the Menu controller.

  3. Locate the <a> element in the <header> section that displays the MEDIO clinic logo, and set the value of the href attribute to: ~/

    The tilde character with the forward-slash (~/) ensures that the Medio clinic logo link targets the root page of the website.

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">
    @Styles.Render("~/Content/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="~/" title="MedioClinic homepage" class="logo">MEDIO clinic</a>
            </div>
            <div class="col-sm-6 nav">
                @* Loads the partial view with the navigation menu, including the <nav> element *@
                @Html.Action("GetMenu", "Menu")
            </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="http://www.kentico.com" title="Kentico CMS">Kentico CMS for ASP.NET</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 MVC 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 the page content — Next page: Next steps in development

Completed pages: 9 of 10