In Constellation, Declared Navigation is intended to be used to solve problems associated with generating site-wide headers and footers, which always have some sort of menu system within them. These menu systems have business requirements that tend to differ from menus that derive their shape from nodes on the Content Tree:
- Some Items that would normally appear in dynamically generated navigation should not appear in “primary navigation”.
- We want links in “primary navigation” to link to external resources that are not represented in the Content Tree.
- We want to organize links in “primary navigation” in a way that is different from the Content Tree, to allow for users that approach our content with a cross-cutting mindset.
- We want to be able to re-organize “primary navigation” at will without changing the underlying Content Tree structure.
- We want to be able to change the link text of “primary navigation” menu options without changing the title or URL of the target Item.
The above are perhaps the most commonly occurring business requirements for site-wide page header navigation. Others include inserting advertisements alongside certain menu options, deciding which menu options expand vs which do not, how many columns a given menu option should have, and other more design-oriented aspects.
Constellation.Feature.Navigation supports everything in this list by separating the problem of maintaining menu links from the problem of presenting them.
Core Declared Navigation Concepts
For Declared Navigation, we are going to build a tree of “links”. These link trees are then passed to renderings to create any menu system you can imagine. Keep in mind that you can use multiple link trees to produce one semantic navigation menu system by having multiple renderings that point at individual branches in Sitecore.
This is the “root” object of any link tree. It can be further split into Link Groups, or simply hold a collection of Navigation Links, or both.
Think of this as a subdivision of your navigation menu. Each Link Group would then be responsible for its own Navigation Links.
This Item represents the exact Menu Option. It has a Link field that allows you to specify the URL, target, title, and inner text of the Menu Option. It also has a flag that allows you to specify that the name of the Navigation Link should be used in place of the Item that the Navigation Link connects to (if any).
This is the API your Rendering Controllers will use to generate the Models for your navigation menus. It requires a Datasource Item and the Context Item to generate a Navigation Menu model. Typically the Datasource Item is the Datasource of the Rendering. The Datasource must be a Navigation Menu Item in the Content Tree. The Context Item is provided to allow for context highlighting in the menu system being rendered.
Consider the following “Primary Navigation” which is rendered in the heading of every page of the example site:
Within Sitecore, we’ll create a Navigation Menu tree as follows:
When we build our Primary Navigation rendering, we’ll assign the /Example Site/Navigation/Primary Item as its datasource. The View will iterate through the Navigation Menu’s Links to generate the navbar presented above.
While the above is a simple list of links, navigation is seldom so simple. Consider the relatively minor change below:
The Utility drop-down has been added to our Primary Navigation for the site. There are two approaches for handling this change. Let’s explore the most obvious first:
In this case, our View must iterate through the Navigation Links, then dive into the “Utility” Link Group and iterate through it’s Navigation Links.
While this is possible, it’s not ideal:
Navigation Links and Link Groups are intrinsically different objects, they are intentionally not considered polymorphic. While Navigation Links represent HTML anchor elements, Link Groups can mean anything from div or list elements, usually with associated tooling for user interaction. They tend to be one-off, or “hard wired” into the HTML as a set number. This makes generic looping very complex.
Caching and Performance Impact
For our example, consider the caching needs if the original primary navigation items needed to display context highlighting. Caching would need to be done based on the ID of the Context Item, not the Navigation Menu’s Datasource. While entirely possible, this means that although the “Utility” navigation would not display context highlighting, it would have to be calculated along with the original primary set for every new page visited. It’s a small performance hit, but it pays to consider scalability in your approach.
A Better Approach
Many Sitecore developers will get tripped up by looking at wireframes, functional specs, or HTML and assume they have to live within the semantic borders of what’s visible. Above, this means creating complex logic for no purpose other than to ensure that their Rendering is a perfect match for the “primary navigation” concept. Now let’s step outside the box.
Instead of trying to “add” utility navigation to primary navigation, let’s just make another navigation menu:
In this approach, we create a discrete Navigation Menu Item for Utility. This means we will need to create a discrete Rendering for the Utility menu, but doing so addresses both of the problems in the “obvious” solution above:
- Both views consist of a simple loop to render the menu contents.
- Each view can have independent caching constraints.
Knowing when to “break” a navigation menu into discrete components can save developers hours of difficult programming followed by multiple trips to the debugging queue. Here are some guidelines for keeping your navigation simple and fast:
Don’t mix & match Navigation Links and Link Groups. If you have a scenario where some links in a navigation menu expand, and some don’t, treat them as discrete Navigation challenges.
Got a Mega Menu in your future? These typically have multiple “slides” each with multiple columns. Don’t try to do this as one Navigation Menu. Treat each “slide” as its own menu, and use Link Groups for the columns. Keep in mind Rule I above.
Watch out for Context Highlighting. Try to isolate these into their own Rendering for performance reasons. Calculating Context Highlighting can be expensive on big sites.
Don’t do Footers all at once. Footers are almost universally divided into discrete columns, groups, or combinations of columns and groups. Chances are you can build a reusable Rendering that handles an individual group (with title) as a Navigation Menu, and then create the footer by adding multiples of this rendering to your Footer, with discrete Datasources for each Rendering instance. This is far less painful than looking for hard-wired column breakpoints, CSS identifiers, and fencepost errors.