Managing navigation menus

How to model and manage your website navigation in Kentico Cloud

This overview explains 3 different ways to manage navigation menus in your Kentico Cloud projects. There is no one best way to build menus and you should always consider the particular requirements of your project. Generally, your approach should reflect how much control over website navigation you want to give to your content contributors.

A) Hard-coded navigation

No part of your navigation is modeled in Kentico Cloud, it's all in the code. For an example, see our sample application.

The Dancing goat sample app with a hard-coded navigation.

The Dancing goat sample app with a hard-coded navigation.

This approach is suitable for small projects with a static menu, where you don't want your contributors making changes to the website navigation. It's fast and simple at the beginning, but every subsequent change must be implemented by a developer.

B) Implicitly generated navigation (a hybrid approach)

Part of your navigation is hard-coded, and another part is generated based on the content of your project. You are not explicitly modeling your menu inside Kentico Cloud.

This approach is suitable for projects where the basic structure of your navigation is static, but certain parts of navigation change frequently as new content is added.

Here is a simple example: Your site has a hardcoded menu containing an "Articles" menu item. The subitems of "Articles" could be generated based on content items of the Article content type in your project.

  • Articles
    • Which brewing fits you?
    • On Roasts
    • Origins of Arabica Bourbon

Here is how to do it, assuming your project already contains a few articles:

1. Retrieve names and URL slugs of all articles

Use the projection capabilities of the Delivery API to retrieve only titles and URLs of all articles in your project.

curl --request GET \
  --url 'https://deliver.kenticocloud.com/975bf280-fd91-488c-994c-2f04416e5ee3/items?system.type=article&elements=title%2Curl_pattern' \
  --header 'content-type: application/json'
using KenticoCloud.Delivery;

// Initializes a content delivery client
IDeliveryClient client = DeliveryClientBuilder
      .WithProjectId("975bf280-fd91-488c-994c-2f04416e5ee3")
      .Build();

// Gets specific elements of all articles
// Tip: Generate strongly typed models via https://github.com/Kentico/cloud-generators-net
DeliveryItemListingResponse<Article> response = await client.GetItemsAsync<Article>(
    new EqualsFilter("system.type", "article"),
    new ElementsParameter("title", "url_pattern")
    );

var items = response.Items;
const KenticoCloud = require('kentico-cloud-delivery');

// Create strongly typed models according to https://github.com/Kentico/kentico-cloud-js/blob/master/doc/delivery.md#creating-models
class Article extends KenticoCloud.ContentItem {
    constructor() {
        super();
    }
}

const deliveryClient = new KenticoCloud.DeliveryClient({
    projectId: '975bf280-fd91-488c-994c-2f04416e5ee3',
    typeResolvers: [
        new KenticoCloud.TypeResolver('article', () => new Article())
    ]
});

deliveryClient.items()
  .type('article')
  .elementsParameter(["title", "url_pattern"])
  .getObservable()
  .subscribe(response => console.log(response));
import { ContentItem, DeliveryClient, Fields, TypeResolver } from 'kentico-cloud-delivery';

// Create strongly typed models according to https://github.com/Kentico/kentico-cloud-js/blob/master/doc/delivery.md#creating-models
export class Article extends ContentItem {
    public title: Fields.TextField;
    public summary: Fields.TextField;
    public post_date: Fields.DateTimeField;
    public teaser_image: Fields.AssetsField;
    public related_articles: Article[];
}

const deliveryClient = new DeliveryClient({
    projectId: '975bf280-fd91-488c-994c-2f04416e5ee3',
    typeResolvers: [
        new TypeResolver('article', () => new Article)
    ]
});

var articles: Article[];

deliveryClient.items<Article>()
  .type('article')
  .elementsParameter(["title", "url_pattern"])
  .getObservable()
  .subscribe(response => {
    articles = response.items;
    console.log(articles);
  });
import com.kenticocloud.delivery;

DeliveryClient client = new DeliveryClient("975bf280-fd91-488c-994c-2f04416e5ee3");

List<NameValuePair> params = DeliveryParameterBuilder.params()
    .filterEquals("system.type", "article")
    .projection("title", "url_pattern").build();

// Generate strongly typed models via https://github.com/Kentico/cloud-generators-java
List<ArticleItem> items = client.getItems(ArticleItem.class, params);
import com.kenticocloud.delivery_core.*;
import com.kenticocloud.delivery_rx.*;

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;

// Prepares an array to hold strongly-typed models
List<TypeResolver<?>> typeResolvers = new ArrayList<>();

// Registers the type resolver for articles
typeResolvers.add(new TypeResolver<>(Article.TYPE, new Function<Void, Article>() {
    @Override
    public Article apply(Void input) {
        return new Article();
    }
}));

// Prepares the DeliveryService configuration object
String projectId = "975bf280-fd91-488c-994c-2f04416e5ee3";
IDeliveryConfig config = DeliveryConfig.newConfig(projectId)
    .withTypeResolvers(typeResolvers);

// Initializes a DeliveryService for Java projects
IDeliveryService deliveryService = new DeliveryService(config);

// Gets specific elements of all articles using a simple request
List<Article> articles = deliveryService.<Article>items()
    .equalsFilter("system.type", "article")
    .elementsParameter(Arrays.asList("title", "url_pattern"))
    .get()
    .getItems();

// Gets specific elements of all articles using JavaRx2
deliveryService.<Article>items()
    .equalsFilter("system.type", "article")
    .elementsParameter(Arrays.asList("title", "url_pattern"))
    .getObservable()
    .subscribe(new Observer<DeliveryItemListingResponse<Article>>() {
        @Override
        public void onSubscribe(Disposable d) {
        }

        @Override
        public void onNext(DeliveryItemResponse<ContentItem> response) {
            // Gets the content items
            Article item = response.getItem();
        }

        @Override
        public void onError(Throwable e) {
        }

        @Override
        public void onComplete() {
        }
    });
<?php

// Defined by Composer to include required libraries
require __DIR__ . '/vendor/autoload.php';

use KenticoCloud\Delivery\DeliveryClient;
use KenticoCloud\Delivery\QueryParams;

$client = new DeliveryClient('975bf280-fd91-488c-994c-2f04416e5ee3');

$items = $client->getItems((new QueryParams())
            ->equals('system.type', 'article')
            ->elements(array("title", "url_pattern"));
import KenticoCloud
 
let client = DeliveryClient.init(projectId:"975bf280-fd91-488c-994c-2f04416e5ee3")
 
let customQuery = "items?system.type=article&elements=title,url_pattern"
client.getItems(modelType: Article.self, customQuery: customQuery) { (isSuccess, itemsResponse, error) in
      if isSuccess {
           if let articles = itemsResponse?.items {
               // Use your items here
           }
       } else {
           if let error = error {
               print(error)
           }
       }

For a taxonomy-driven navigation, you can retrieve items tagged with a specific taxonomy term.

2. Display the retrieved data as links in your menu

See the Pen Implicitly generated menu by Juraj Uhlar (@juraju) on CodePen.

As your content contributors write and publish more articles, your menu is updated automatically. They can't and don't have to change navigation manually, which means less work and less potential for mistakes. Structural changes still have to be implemented by developers.

C) Explicitly modeled navigation

Your navigation is completely managed from Kentico Cloud using specialized content items. Each of them represents a menu item in your navigation and their relationships are defined using a Linked items element.

This approach is the most flexible and empowers your content contributors to manage navigation menus without developer assistance. However, implementing it requires some setup in your project and inside your application. Here is a simple step-by-step example:

1. Create a Navigation item content type

To create a new content type, choose Content models in the app menu, then click Create new.

A **Navigation item** content type defines the structure of your navigation items.

A Navigation item content type defines the structure of your navigation items.

Name your content type "Navigation item". Add at least these elements:

  • Title Text element.
  • Url URL Slug element.
  • Subitems Linked items element for holding the child menu items.

You can also add more elements depending on your needs, such as a redirect URL.

2. Create navigation items

To create new content items, choose Content & Assets in the app menu, then click Create new. One by one, add and publish the individual items in your menu. Use the Subitems Linked items element to link the items together into a content tree.

A navigation item used to model the "Articles" menu item.

A navigation item used to model the "Articles" menu item.

3. Define the first-level navigation items

Choose which navigation items should be displayed at the first level of your menu and group them together inside another item:

  1. Create one more Navigation item and name it ROOT Navigation item.
  2. Populate its Subitems element with your first-level navigation items.
A root navigation item that holds all first-level menu items in its Linked items element.

A root navigation item that holds all first-level menu items in its Linked items element.

In the following step, you will request this item via the Delivery API to generate your menu.

Note: Only the Subitems element is important here, the Title and Url values are irrelevant. You don't necessarily have to use a Navigation item. You can use any item of any content type that has a Linked items element to hold your navigation items.

4. Retrieve your navigation items via the Delivery API

When requesting the root navigation item via the Delivery API, specify the depth parameter to retrieve more than one level of linked items.

curl --request GET \
  --url 'https://deliver.kenticocloud.com/24246f25-946d-4ab4-b170-fadef22fe7b2/items/root_navigation_item?depth=5' \
  --header 'content-type: application/json'
using KenticoCloud.Delivery;

// Initializes a content delivery client
IDeliveryClient client = DeliveryClientBuilder
      .WithProjectId("975bf280-fd91-488c-994c-2f04416e5ee3")
      .Build();

// Gets navigation items and their linked items
// Tip: Generate strongly typed models via https://github.com/Kentico/cloud-generators-net
DeliveryItemResponse<NavigationItem> response =  await client.GetItemAsync<NavigationItem>("root_navigation_item",
    new DepthParameter(5)
    );

var item = response.Item;
const KenticoCloud = require('kentico-cloud-delivery');

// Create strongly typed models according to https://github.com/Kentico/kentico-cloud-js/blob/master/doc/delivery.md#creating-models
class NavigationItem extends KenticoCloud.ContentItem {
    constructor() {
        super();
    }
}

const deliveryClient = new KenticoCloud.DeliveryClient({
    projectId: '24246f25-946d-4ab4-b170-fadef22fe7b2',
    typeResolvers: [
        new KenticoCloud.TypeResolver('navigation_item', () => new NavigationItem)
    ]
});

deliveryClient.item('root_navigation_item')
    .depthParameter(5)
    .getObservable()
    .subscribe(response => console.log(response.item));
import { DeliveryClient, TypeResolver, ContentItem, Fields } from 'kentico-cloud-delivery';

// Create strongly typed models according to https://github.com/Kentico/kentico-cloud-js/blob/master/doc/delivery.md#creating-models
export class NavigationItem extends ContentItem {
    public title: Fields.TextField;
    public url_slug: Fields.UrlSlugField;
    public subitems: NavigationItem[];
}
  
const deliveryClient = new DeliveryClient({
    projectId: '24246f25-946d-4ab4-b170-fadef22fe7b2',
    typeResolvers: [
        new TypeResolver('navigation_item', () => new NavigationItem)
    ]
});

var root: NavigationItem;

deliveryClient.item<NavigationItem>('root_navigation_item')
  .depthParameter(5)
  .getObservable()
  .subscribe(response => {
    root = response.item;
    console.log(root);
  });
import com.kenticocloud.delivery;

DeliveryClient client = new DeliveryClient("24246f25-946d-4ab4-b170-fadef22fe7b2");

List<NameValuePair> params = DeliveryParameterBuilder.params().modularContentDepth(5).build();

// Generate strongly typed models via https://github.com/Kentico/cloud-generators-java
ArticleItem item = client.getItem("root_navigation_item", NavigationItemItem.class, params);
import com.kenticocloud.delivery_core.*;
import com.kenticocloud.delivery_rx.*;

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;

// Prepares an array to hold strongly-typed models
List<TypeResolver<?>> typeResolvers = new ArrayList<>();

// Registers the type resolver for articles
typeResolvers.add(new TypeResolver<>(NavigationItem.TYPE, new Function<Void, NavigationItem>() {
    @Override
    public NavigationItem apply(Void input) {
        return new NavigationItem();
    }
}));

// Prepares the DeliveryService configuration object
String projectId = "24246f25-946d-4ab4-b170-fadef22fe7b2";
IDeliveryConfig config = DeliveryConfig.newConfig(projectId)
    .withTypeResolvers(typeResolvers);

// Initializes a DeliveryService for Java projects
IDeliveryService deliveryService = new DeliveryService(config);

// Gets navigation items and their linked items using a simple request
NavigationItem root = deliveryService.<NavigationItem>item("root_navigation_item")
    .depthParameter(5)
    .get()
    .getItem();

// Gets navigation items and their linked items using RxJava2
deliveryService.<NavigationItem>item("root_navigation_item")
    .depthParameter(5)
    .getObservable()
    .subscribe(new Observer<DeliveryItemResponse<NavigationItem>>() {
        @Override
        public void onSubscribe(Disposable d) {
        }

        @Override
        public void onNext(DeliveryItemResponse<NavigationItem> response) {
            // Gets the item
            NavigationItem item = response.getItem();
        }

        @Override
        public void onError(Throwable e) {
        }

        @Override
        public void onComplete() {
        }
    });
<?php

// Defined by Composer to include required libraries
require __DIR__ . '/vendor/autoload.php';

use KenticoCloud\Delivery\DeliveryClient;

$client = new DeliveryClient('24246f25-946d-4ab4-b170-fadef22fe7b2');

$item = $client->getItem('root_navigation_item', (new QueryParams())
            ->depth(5));
import KenticoCloud

let client = DeliveryClient.init(projectId: "24246f25-946d-4ab4-b170-fadef22fe7b2")

let customQuery = "items/root_navigation_item?depth=5"

client.getItem(modelType: NavigationItem.self, customQuery: customQuery) { (isSuccess, deliveryItem, error) in
     if isSuccess {
        if let root = deliveryItem.item {
            // Use your item here
        }
    } else {
        if let error = error {
            print(error)
        }
    }

5. Generate a menu

Use the retrieved data to construct a nested list of links:

See the Pen KC modeled menu by Juraj Uhlar (@juraju) on CodePen.

Note: Please notice that the content of the linked items is inside the modular_content property of the response (historical reasons).

Voila! You now have a dynamic menu that you can manage entirely from Kentico Cloud. You can find a more complex example of this approach on our blog:

Worked example

Check out Managing Navigation Menus in Kentico Cloud for an in-depth guide to managing your navigation and rendering it in an MVC.NET application.

A note on sitemap.xml

Similarly to generating a dynamic menu for your application, you can use the modeled navigation in Kentico Cloud to generate a sitemap.xml file for your web site.

Summarized

In this tutorial, you have learned about three different ways to manage navigation in your Kentico Cloud projects. You can pick the one that best suits your priorities, such as the speed of initial development (approach A), low maintenance cost (approach B), or flexibility and empowerment of your content contributors (approach C).

Each approach is a trade-off, but you can also combine them together and manage each part of your navigation separately, in the most efficient way possible.

What's next

  • Discuss your own approach to building navigation menus with our developer community on Forums.
  • Visit the development section of our blog for more inspiration and some advanced examples of using Kentico Cloud.
  • Have a look at our sample applications and tools.