MOSS Navigation Deep-Dive - Part 1

Hi everyone, I'm Chris Richard. Today I want to take some time to walk you through some features of navigation in MOSS 2007 from an implementation perspective, and show you some things you can do to customize your Web site's navigation.

First, let's explore some of the navigation-related features in MOSS 2007.  I'll start off by provisioning a new site collection based on the Publishing Portal template. I've created a few sub-sites and pages in my site collection, with a hierarchy like so:

Root Site (NavigationExample)
Web Site - 1
            Web Page - 1.P1
            Web Site - 2
                        Web Site - 2.1
                                    Web Site - 2.1.1
                                    Web Page - 2.1.P1
                                    Web Page - 2.1.P2
                        Web Site - 2.2
                        Web Page - 2.P1
            Web Site - 3
                        Web Site - 3.1

Let's take a look at what gets setup automatically for you in terms of navigation. When I visit the Web Site- 2 subsiteI see the following:

Menu Control

Let's first talk about the horizontal or top navigation menu.  This menu is declared on the master page that this site is using (I've pulled out the markup below):

 
<SharePoint:AspMenu ID="GlobalNav" Runat="server"
    DataSourceID="GlobalNavDataSource"
    Orientation="Horizontal"
    StaticDisplayLevels="1"
    MaximumDynamicDisplayLevels="1" />

The vertical or left navigation menu is declared in a similar way, but with slightly different properties.  Note that I removed some of the less interesting properties (at least for this discussion) like styling so as to focus on the more interesting ones. Also, these properties are identical to those available on the ASP.NET 2.0 Menu control (https://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.menu.aspx).

  • DataSourceID is probably the most important property here because it specifies the control which will actually provide the hierarchical data for this menu. In this case it points to a control with ID "GlobalNavDataSource"; we'll take a look at this control shortly.
  • Orientation here is "Horizontal" and I think this is fairly self-explanatory. The other option being "Vertical" which the left menu is set to.
  • StaticDisplayLevels refers to the number of levels of the hierarchy to show in the menu at once. As "1" is specified here, only one level of the hierarchy - the level directly beneath the root - is shown.
  • MaximumDynamicDisplayLevels is similar to StaticDisplayLevels but determines the number of levels to show in dynamic fly-outs. "1" is specified here as well which is why the items beneath Web Site 2 are displayed in a fly-out when the mouse moves over the static item representing Web Site 2.

PortalSiteMapDataSource

Now on to the data source which I found by looking for a declaration with ID="GlobalNavDataSource":

 
<PublishingNavigation:PortalSiteMapDataSource ID="GlobalNavDataSource"
    Runat="server"
    SiteMapProvider="CombinedNavSiteMapProvider"
    ShowStartingNode="false"
    StartFromCurrentNode="true"
    StartingNodeOffset="0"
    TrimNonCurrentTypes="Heading"
    TreatStartingNodeAsCurrent="true" />

The PortalSiteMapDataSource is a MOSS-specific data source which knows how to retrieve data from a PortalSiteMapProvider object and expose this data according to the ASP.NET hierarchical data source interface. As such, it specifies the name of the provider it wishes to use to retrieve data via the SiteMapProvider property. We'll look more closely at these providers shortly.

  • ShowStartingNode affects whether or not the starting node is actually returned by the data source. By specifying "false" here, the menu only receives the items beneath the starting node and does not include this node (in this case the root node). Compare the above screenshot to the one below which was captured after switching ShowStartingNode to "true":

 

As can be seen, the root node, "NavigationExample" is now included in the menu. Also, since I didn't adjust the StaticDisplayLevels, none of the items beneath NavigationExample are included (to see them, simply change this property to "2" to see both NavigationExample and the Web Site - 1, Web Site - 2, etc. items). Whether or not you want to include the starting node is completely a matter of preference. On this site the root Web site is always shown above the horizontal navigation menu, next to the logo (see above), so it doesn't make much sense to include another link in the menu.

  • StartFromCurrentNode affects where the data source starts but things get a little bit interesting here. This property is set to "true" and the current node (i.e. the node representing the item currently being visited) is Web Site - 2. It seems to follow that the data source should start with Web Site - 2 and display the items beneath. However, we've noticed that the menu starts at the root Web site, NavigationExample. So what gives? This is where MOSS navigation starts handling things a little bit differently. By specifying StartFromCurrentNode="true", this is essentially a flag that tells the PortalSiteMapDataSource to apply its own concepts to determine where it should be starting. I'm going to wait until I get to the PortalSiteMapProvider before explaining how this process works, but for now suffice it to say that you should always specify StartFromCurrentNode="true" when using the PortalSiteMapDataSource.
  • TrimNonCurrentTypes (and other similar properties below) allows context- and type-based trimming of nodes. By specifying "Heading" here, the data source is being told that it should remove any nodes of type Heading that are not directly beneath the current node. Multiple types may be specified as a comma-delimited list ("Heading, Page, ..."). Allowable types are Area (this indicates Web or Web site), Page, Heading and AuthoredLink.
  • TrimNonAncestorTypes (not used in above declaration) will trim out any types specified that are not directly beneath the current site or one of its ancestors.
  • TrimNonAncestorDescendantTypes (not used in above declaration) will trim out any nodes of the specified types that are not beneath the current site or one of its ancestor or descendant sites.
  • Finally, TreatStartingNodeAsCurrent is directly related to the previous properties, in that it affects which node is treated as the current node for trimming purposes. As noted before, current node by default refers to the node representing the item currently being visited (Web Site - 2 in our continuing example). By setting this property to "true" the data source's starting node is treated as the context or trimming node (NavigationExample in this case). We'll revisit these trimming-related properties later once we've made it a bit further.

PortalSiteMapProvider

The PortalSiteMapProvider object mentioned above is the true source of the hierarchical data and it provides this to the data source. The PortalSiteMapProvider retrieves nodes from WSS's SPNavigation store which provides the ability to create static links and groupings. These items are then merged with the site collection structure (i.e. dynamic items representing Web sites and Web pages) and then security trimming is applied so that users only see items for which they have permission to navigate.

Named providers are declared in the application's web.config file in order to make them widely accessible. The declarations of the two most important PortalSiteMapProviders are shown below (slightly modified for clarity):

 
<add name="CombinedNavSiteMapProvider" description="CMS provider for Combined navigation"
    type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider"
    NavigationType="Combined" EncodeOutput="true" />
<add name="CurrentNavSiteMapProvider" description="CMS provider for Current navigation"
    type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider"
    NavigationType="Current" EncodeOutput="true" />
 

Note that the name of the first provider, "CombinedNavSiteMapProvider", matches the value specified for the SiteMapProvider property of the data source we examined earlier. From this it becomes clear that the horizontal menu is ultimately supplied by this provider object.

Let's talk about these provider properties in more detail:

  • Instances of PortalSiteMapProvider are separated into three types, based on their NavigationType property:
    • Global providers retrieve SPNavigationNode links from the TopNavigationBar SPNavigation collection and respect the inheritance settings specific to global navigation.
    • Current providers retrieve links from the QuickLaunch SPNavigation collection and respect the inheritance settings specific to current navigation.
    • Combined providers act exactly like Global ones when the Web site in question is set not to inherit its global navigation and exactly like Current ones when the site is set to inherit.
    • By default, a Combined provider is hooked up to the horizontal or top menu we've been looking at whereas a Current provider supplies the vertical or left navigation menu. Use of Global providers is not recommended and I'll try to touch on the reasons for this at the end of the post.
  • The EncodeOutput property would probably be more aptly named EncodeTitle, as the only effect of enabling this option is the automatic HTML encoding of the Title property of any nodes returned by the provider. This is useful as ASP.NET's menu control does not automatically HTML encode this property when rendering. ASP.NET's SiteMapPath control, however, does encode this property which is why it's hooked up to the "CurrentNavSiteMapProviderNoEncode", which is declared identically to the "CurrentNavSiteMapProvider", except without the EncodeOutput="true" specification.

Other properties available on the provider but not shown in the above declarations include:

  • DynamicChildLimit, which is an integer property that specifies the maximum "dynamic" children for each Web site (dynamic children include subsites and pages). This value defaults to 50 so if the number of subsites and pages for a particular Web site is greater than 50 objects will be left out unless you adjust this setting. This limit can be increased but keep in mind the usability of a navigation hierarchy that has so many children at each pivot.
  • RequireUniqueKeysForNodes, which is a Boolean property that controls whether or not the nodes returned from the provider should all have unique values for their Key properties. Yes, I know the term "Key" itself implies uniqueness, but in order to get menu highlighting on authored links and headings working just right we needed to make this property default to false. This won't cause any problems when attaching (through a data source) to an ASP.NET menu control, but for most other display controls make sure the provider declaration includes RequireUniqueKeysForNodes="true".

There are also a group of properties that control the provider's inclusion of various node types:

  • IncludeSubSites and IncludePages, properties which may be set to Always (ignore the per-Web site setting, always include this type), PerWeb (respect the per-Web site setting, this is the default and we'll see how to change the per-Web setting later), and Never (ignore the per-Web site setting, never include this type).
  • IncludeHeadings and IncludeAuthoredLinks, Boolean properties which default to true and control the inclusion of these types.

I think I'll break here for now. Next time we'll look at the Navigation Settings page which you can use to easily configuration navigation on a per-Web site basis. I'll also make sure to tie up all the loose ends I've unraveled in the course of this post.

--Chris Richard, ECM Developer