Level: 200
 

Simple information site in Sitecore – Part 6: Navigation

XSL image

This is the sixth part of the article series in 11 parts, describing how to build a simple information site in Sitecore.


In this article we’ll go through how you can build navigation for your website. We’ll introduce the concept of interface templates and how you can build both a left menu and defining global menu points to be shown in the top of the site. Further we’ll briefly touch HTML cache and how to set that up.

Written by: Jens Mikkelsen
Thu, Jun 17 2010

Articles in this series

 

Part 1: The requirements
Part 2: Installing Sitecore and setting up a Visual Studio solution
Part 3: Architecture
Part 4: The basic layouts
Part 5: Identity and documents
Part 6: Navigation
Part 7: The homepage - content oriented spots
Part 8: The homepage - presentation oriented spots
Part 9: News
Part 10: Search
Part 11: Deploying the solution

 

 

The navigation component

The navigation component is responsible for allowing the user or visitor to navigate on the site. In our simple information site, we are going to have several presentations, which enable this:
 

  • Left navigation: This is the main menu on the site and works as many menus do. Initially it shows the top level in the hierarchy. When the user selects an item in the top level, it will show the next level for that given item; and so forth.

 

  • Global navigation: This is static menu shown in the header of the site. This menu shows the same menu items throughout the site, and it is therefore pretty simple.

 

  • Sitemap: This is a page showing all the menu items in a nice hierarchical structure.

 

 

Defining the templates

There are a couple of content fields needed for every template, which should be shown in a navigation presentation. First of all we want a navigation title field. This is needed as editors often want to name an item something different in the rather limited space available in a menu, then what can be used on the document itself. For instance imagine a document written on LearnSitecore called “Learn Sitecore welcomes a new contributor”. This title is way too long for a navigation component, so it would be nice if I could add the navigation title “New contributor”.


The second field we need to define is a “Show this page in the menu” checkbox. This will enable the editors to exclude pages from the menu structure. For instance imagine that you want to implement a form called “Request an article”. After the user has entered his request, he will be redirected to a page thanking him for the request and confirming that it has been sent. This confirmation page shouldn’t be shown in the navigation, so we should allow the editor to exclude it.


So we have two fields which belong and are needed by the navigation component. We don’t want to declare the fields on every single template, which should be shown in the menu. This would make the solution hard to maintain and extend and further it would create dependency between the different components. Therefore we are going to declare the fields in the navigation component. The presentations only use these fields, so there are no dependencies on other components. In that way we create sort of an interface which every item need to inherit/implement if it is to be shown in the navigation presentations. We call this an interface template – a template which defines what fields are necessary for a component or presentation to work.


So in this case we are going to create a template called navigable. The interface being that if an item inherits from this template, it will be possible to present the item in the navigation presentations. Create a template with the fields Navigation_Title and Navigation_ShowInMenu like this:

 

navigable template

 

The pagetypes that needs it can now inherit from this.

 

 

The left navigation presentation

Now we are going to create the presentation for the left navigation. Again we are going to use XSLT, as this is just a simple information site. So go ahead and create an XSLT rendering called LeftNavigation like this:

 

left nav rendering

 

Add the XSLT presentation to a newly created Visual Studio solution for the navigation component (like shown in the previous articles) and we are now ready to develop.


Often it is easier to develop if you can follow the progress by seeing the presentation, while you are developing it. So start by adding the presentation to the pagetypes you need. In this example we are going to add the presentation to the NormalPage pagetype:

 

Assing left navigation

 

Now that we have added the presentation do a publish and enter a test text in the XSLT and see that it comes out in the right place. You should see something like this:

 

Left nav test text

 

The first thing we need to do in our presentation is to determine the root of the navigation. This is needed as the menu will always have a given root and all children should be shown in a hierarchical matter. As the left menu should start with everything from the home node and down, we need to locate the home node. The easy solution would of cause be to hard code the path to the home node. However this would make the site rather static, as you would not be able to rename any of the items in the path to the node. Further the left navigation wouldn’t support multiple sites in Sitecore as the menu would be the same on different sites. For more info on multiple sites in Sitecore see this article.


Now another possibility would be to look for the current nodes ancestors and find the first node, which is based on the home node template. However this isn’t very flexible either. First of all we would create a dependency on the pagetype component, which we don’t want (it would be a circular reference). Secondly it would only be possible to reset the navigation root, by creating a new home node.


So we need a way to determine what should be the root of the navigation component. Thanks to multiple inheritance in templates, we can use a template for this. What we need to do is create a template which indicates that an item is a root of the navigation – lets call this template _menuRoot. It needs no fields, as it just indicates a property of an item and doesn’t contain any data:

Menu root template

 

Now that we have this indicator of the root, we can let all pagetypes, which should reset the menu inherit from this template.


Currently we don’t have a root which should reset the menu, as we don’t have a frontpage template. So we need to create that in the pagetypes component (remember a friendly icon, as shown in the article about helping the editors) and make it inherit from the _menuRoot template:

 

Home node template

 

To be able to test the left navigation go and create some example content in the content tree:

 

Example content tree

 

Good… We are now ready to start developing. :)

 

As mentioned the first thing we need to do is find the item, which should be the root of the left navigation. This means we should iterate over the ancestors of the current item and find the first one, which inherits from the _menuRoot template. We can use XPath for this, which should look something like this:

 

< xsl:variable name = "home" select="ancestor-or-self::item[sc:IsItemOfType('_menuRoot',.)]"></xsl:variable>

 

The sc:IsItemOfType is an XSL helper method provided by the Sitecore API. This method tests whether an item inherits from a template, which is named the string you provide in the first parameter. In this case _menuRoot.

 

 

Now that we have found the root, we can start the real task of rendering the menu. We can divide the needed functionality into separate XSLT methods with different responsibility. What we need is the following:

  1. A method that iterates over the items, that needs to be rendered.
  2. A method that renders an item in the left navigation.


We start with the method that iterates over all the nodes we render, we call this method TraverseChildren, as it will traverse over all the children of a specified item.


We need a parameter to this method/xsl template, as we need to know which items children we need to iterate over. The template with a parameter can created like this:

 

<xsl:template name="TraverseChildren">

  <xsl:param name="item"></xsl:param>

</xsl:template>



Now we need to find all the children, which should be rendered in the left navigation. This is pretty straight forward:

 

<xsl:variable name="RenderableChildren" select="$item/item[sc:fld('Navigation_ShowInMenu',.) = '1']"/>

 

 

Here we use the parameter called “item” and iterate over all the children where the field Navigation_ShowInMenu equals one, which means the checkbox is checked.


The HTML that renders the menu should be using the list tag (ul, li). As XHTML doesn’t allow empty UL tags, we need only to render something out, if the number of children is greater than zero. This can be done like this:

 

<xsl:if test="count($RenderableChildren) > 0">

</xsl:if>   

 

 

Now that this is determined we can start creating the list of navigation items. This is done by using the XSL for-each element like this:

 

<ul>

  <xsl:for-each select="$RenderableChildren">

     ...

  </xsl:for-each>

</ul>

 

 

Now we can call our second XSLT method/template, which we haven’t created to render the item.

 

<xsl:call-template name="RenderItem">

  <xsl:with-param name="Item" select="."></xsl:with-param>

</xsl:call-template>


Now comes the tricky part. The left navigation can contain multiple levels, but the next level should only be rendered if one of the items children or it self is selected. For instance if you take a look at the example content tree we created earlier, the item named testpage12 should only be rendered if TestPage1 or one of its children is selected. If this is the case, we need to call the TraverseChildren method recursively.


The easy way to determine if we need to call the method recursively is to iterate over all the children of current node and see if it is selected. However this doesn’t perform very well as there may be 1000s of children. So we need to turn the picture around and take the currently selected node by the user and see if the node we are currently iterating over is an ancestor of that item. In that way we only look upwards in the tree, which limits the number of nodes we iterate over to the amount of levels in the content hierarchy.

Note: As a rule of thumb you should never iterate over all descendants of a given item 

 

Sitecore parses the current user selected item in the parameter sc_currentitem. This gives us the following line to determine if we should call the method recursively:

 

<xsl:if test="current()/@id = $sc_currentitem/ancestor-or-self::item/@id">

 

Please note that we compare the attribute id and not the item itself. This is absolutely necessary to make the rendering perform. If you don’t do so, XSL will convert the item and all of its children to XML trees and compare them. If the item has thousands of children this tree can be rather large and give some extreme spikes in your memory usage.


Now if this test returns true you should call the method recursively. Adding this to the method and a level parameter to the template for styling purposes your XSLT should look something like this:


 

<xsl:template name="TraverseChildren">

  <xsl:param name="level"></xsl:param>

  <xsl:param name="item"></xsl:param>

 

  <xsl:variable name="RenderableChildren" select="$item/item[sc:fld('Navigation_ShowInMenu',.) = '1']"/>

  <xsl:if test="count($RenderableChildren) > 0">

    <xsl:variable name="LevelCssClass" select="concat('MenuLevel',$level)"></xsl:variable>

    <ul class="{$LevelCssClass}">

      <xsl:for-each select="$RenderableChildren">

        <xsl:variable name="selected">

          <xsl:if test="current()/@id = $sc_currentitem/@id">

            Selected

          </xsl:if>

        </xsl:variable>

        <xsl:call-template name="RenderItem">

          <xsl:with-param name="Item" select="."></xsl:with-param>

          <xsl:with-param name="Selected" select="$selected"></xsl:with-param>

        </xsl:call-template>

 

        <xsl:if test="current()/@id = $sc_currentitem/ancestor-or-self::item/@id">

          <xsl:call-template name="TraverseChildren">

            <xsl:with-param name="level" select="$level+1"/>

            <xsl:with-param name="item" select="."/>

          </xsl:call-template>

        </xsl:if>

      </xsl:for-each>

    </ul>

  </xsl:if>

</xsl:template>

 


 

Now all we need to do is implement the RenderItem method/template. This is fairly easy to do.

 

<xsl:template name="RenderItem">

  <xsl:param name="Item"/>

  <xsl:param name="Selected"></xsl:param>

 

  <li class="{$Selected}">

    <sc:link select="$Item">

      <sc:text select="$Item" field="Navigation_Title"/>

    </sc:link>

   

  </li>

</xsl:template>

 

Notice here how I use the sc:link and sc:text XslControls which Sitecore provides through the API. These automatically create a link and pulls out the text from the Navigation_Title field.


The last thing we need to do is call the TraverseChildren with the root node of the menu. This can be done in the Main template of the XSLT:


 

<xsl:template match="*" mode="main">

  <div class="LeftNavigation">

    <xsl:variable name="home" select="ancestor-or-self::item[sc:IsItemOfType('_menuRoot',.)]"></xsl:variable>

 

    <xsl:call-template name="TraverseChildren">

      <xsl:with-param name="level" select="0"/>

      <xsl:with-param name="item" select="$home"/>

    </xsl:call-template>

  </div>

</xsl:template>

 

If you add a little styling to the control (in the design component) you should now see something like this:

 

 

left nav result

 

 

That is it. You have now created the most complex presentation – the left menu.

 

 

The global navigation properties

The next part we need to develop is the global navigation. This is a fairly simple data presentation, so we are going to use an XSLT again. The first thing we need to do is to create a content structure for the global navigation elements. We need to create a folder for the root as the same way we created a root for the left navigation. That means we need to create a root template and a content item in the pagetypes component:

 

Global nav templates

 

Now we have a folder for the global navigation. To allow editors to create pages under this folder we need to setup insert options. This should be done on the standard values on the folder. So create the standard values and allow the editor to insert items of templates NormalPage and create an example content structure in the content tree:

 

global nav structure

 

Now that we have that in place, we can get started on the presentation. So create the rendering as we have done before.

 

Global nav rendering

 

As we did when developing the left navigation the first thing we need to do is to find the root of the menu, no matter what the current item is in Sitecore. Again this should be dynamic so we don’t hardcode paths’ and disallow multiple sites. In the same way as with the leftNavigation we are going to look upwards in the tree to find the root of the site. We are going to define that the global navigation folder should always be found as a direct child of the root of the left menu. We could work with other concepts, but this is a natural and simple way, which fits our needs just fine. So the root is found like this:

 

<xsl:variable name="root" select="ancestor-or-self::item[sc:IsItemOfType('_menuRoot',.)]/item[sc:IsItemOfType('_globalMenuRoot',.)]"/>

 

Now all we need to do is iterate over all the items and render a link to them. This is done like this:

 

<xsl:variable name="children" select="$root/item[sc:fld('Navigation_ShowInMenu',.)='1']"/>

<xsl:if test="count($children) > 0">

  <ul>

    <xsl:for-each select="$children">

      <li>

        <sc:link select=".">

          <sc:text field="Navigation_Title"/>

        </sc:link>

      </li>

    </xsl:for-each>

  </ul>

</xsl:if>

 

Now we just need to apply the rendering to the correct page types:

 

Global Nav assign

 

That’s it. Easy-peasy-lemon-squezzy!

 

 

The sitemap

The only thing we now miss in the navigation component is the sitemap. I am not going to go through all the steps and explain it in detail, as the sitemap is very similar to the left navigation – except that the complete tree is going to be listed. You can download the source code if you want to take a closer look.


After you have created the presentation you need to create a pagetype for the sitemap. You need to define what “interface templates” it should hold. In my case I want it to inherit from navigable as it should be presentable in the global navigation. Further I want to inherit from documentHeader as I want to present a title and abstract field in the top of the page. So I create a template like this:

sitemap base templates

 

Now you just need to set the presentations on the __Standard values. One thing you should know about a sitemap is, that it potentially iterates over a huge amount of items, if the content tree is very big. So in this case it would be a good idea to add HTML caching to the presentation. If you want to know more about Sitecores caching read this article.


You can apply caching to the presentation like this:

sitemap cache

 

Here you set that the outputted HTML should be cached. You can set when the cache should be varied. This means that Sitecore can cache different versions of the outputted HTML. For instance you might want to have different caches for different query strings if your presentation varies based on them (like paging). You can read more about setting up caching in the SDN Presentation Component Reference.


Now that you have set that up, you have finished the navigation component. Congratulations!

 

You can download the source and a Sitecore package here.

 

 

Please rate this article


1 rates / 5 avg.

  • About the author:

    Jens Mikkelsen

    Jens Mikkelsen is a partner at Inmento Solutions a Sitecore consulting firm. He works as a Sitecore specialist and consulting helping clients architect and build quality Sitecore solutions using the newest modules and tools. 

    Further he has been deeply envolved in various complex solutions and has built up a strong knowledge of Sitecore architecture and best practices. He has especially focused on and is specialized in debugging and analyzing Sitecore solutions.

     

    Jens is very interested in the technical mechanisms in the new marketing products such as Sitecore DMS and Sitecore ECM.

    My Sitecore Freelance CV

13 responses to "Simple information site in Sitecore – Part 6: Navigation"

Grab all these cool articles in this series, and you have a small booklet.

I will wait to comment until you have completed the series, though there almost nothing I can chip in with to such an experienced developer as you Jens.

Keep up the good work! :)
Posted: Friday, June 18, 2010 8:01 AM
Thanks for article

I'm trying now to implement described navigation but using sublayouts instead of renderings. Can you share some your tips and hints for this matter?

P.S. I'm new to Sitecore and have lack in ASP.NET experience :)
Posted: Friday, June 18, 2010 12:37 PM
Hi Dmytro,

The concepts for implementing the presentations in ASP.NET are the same. However recursion (which you need for multilevel menus) can be a bit tricky. For the left menu I would create a normal usercontrol, which renders a single level. Then I would do the same checks (conditions) to see if the next level of the menu should be rendered, and then add the user control recursively.

Hope thats makes sense.

Cheers
Jens
Posted: Monday, June 21, 2010 9:43 PM
Thankyou very much for this article. A fantastic resource for someone new to Sitecore.
I am looking forward to studying your "News" section.
Posted: Monday, July 19, 2010 12:46 AM
This is great! I'm learning alot from your guide! I only have one question what do i need to do if i want to show all items bij default? In other words:

- Mainitem
-subitem 1
-subitem 2
-subitem
-subitem 3

With you script I first need to click subitem 2 to show subitem.

Kind regards
Posted: Friday, August 13, 2010 5:11 PM
Hi PixM,

You just need to remove the test, to see if the current item is selected. This one:

<xsl:if test="current()/@id = $sc_currentitem/ancestor-or-self::item/@id">

Hope it helps!
Posted: Sunday, August 15, 2010 5:51 PM
Hi Jens,

Thank u for the quick response. I tried your solution but this removes the subitem maybe my example isn't clear enough. See beter one below

- Products
- Markets
- Market Subpage
- Market Subpage 2
- Industry

This is menu I want to show all items open withj your menu I need to click Markets to see Market Subpage and Market Subpage 2. When I Remove the test the 2 subitems are removed from the menu even when I click the mainitem Markets.

Kind Regards,
Posted: Monday, August 16, 2010 9:14 AM
Nevermind:-) I wasn't fully awake it now works! Thanx again Jens.
Posted: Monday, August 16, 2010 2:43 PM
What I find hard is trying to style the css. I dont want an indented navigation list. I want a list that will show current page highlighted with siblings in a different colour ect. We got it working to a degree in explorer but firefox wont display it correctly at all. We gave up and went back to a display current item with children style menu.
Posted: Sunday, November 07, 2010 9:11 AM
Hi Jens,
I've finished all tutorials and eagerly awaiting more..
Thank you very much, keep them up...
Luka
Posted: Tuesday, December 21, 2010 3:26 PM
Hi Jens,
I've finished all tutorials and eagerly awaiting more..
Thank you very much, keep them up...
Luka
Posted: Wednesday, December 29, 2010 3:17 PM
The next part in the series are now available. I hope you enjoy it!

Cheers
Jens
Posted: Wednesday, December 29, 2010 5:23 PM
Hi Jens,

Another great tutorial and I am learning a great deal thank you. I am just slightly confused regarding the global navigation. What is this for? I understand the left nav and sitemap. Have I missed something? Would this be like a top level navigation across the site? Why does this have it's own content item and what are the child nodes underneath it for?

Tahnks
Posted: Saturday, June 04, 2011 5:40 PM

Leave a reply


Notify me of follow-up comments via email.
 
 
#nbsp;