SharePoint navigation - Make current item highlighting work
Posted
Tuesday, September 18, 2007 3:41 PM
by
Itay Shakury
One thing that bugs me (and a lot of clients of us) is that SharePoint's top menu (AKA Global navigation) highlighting is not working as we expect it to:
If you create a subsite directly underneath the root web, and make it appear on global navigation, It will get highlighted when selected. BUT if you create a subsite somewhere else (say in the Site Directory), and then make it appear on global navigation, it will not get highlighted when you navigate to it.
If you create a publishing page it gets highlighted when visited. BUT if you create a page in a document library it doesn't.
This is an example of the current and the desired situation.
Recently one of our clients asked me to fix this. So in this post we will see how to customize the built-in SharePoint's menu control to enhance the support for highlighting.
* If you are not interested in the how to, you can simply download the final product from here. Deployment instructions provided below.
Starting point:
While developing SharePoint, the product team has decided to use asp.net's menu control for navigation. But, it had a few issues, so they had created a new menu, that inherits from asp.net's menu. The new SharePoint menu class was marked as sealed, and this is bad for any of us who wish to customize it. Luckily, the product team has provided us with the complete source code for the SharePoint menu so we can make modifications to it. More info on this here.
Highlighting logic:
We need to know when to highlight items. I chose to do that by comparing the URL that the menu item redirects to, to the URL of the page we are currently in. if they are identical, this means we are in the site that this menu item refers to, and it should be highlighted.
Code:
We will add our code to the OnMenuItemBound event. this way we have access to each menu item through the event's arguments.
add the following code to the OnMenuItemDataBound event:
if (SPContext.Current.ListItemServerRelativeUrl.ToLower() == MakeServerRelative(e.Item.NavigateUrl.ToLower()))
{
e.Item.Selected = true;
}
This code uses another method called MakeServerRelative. so add the following code to you class:
private string MakeServerRelative(string url)
{
//checks if the path is absolute
if (url.StartsWith("http://"))
{
//remove protocol
url = url.Remove(0, 7);
//remove host
url = url.Remove(0, url.IndexOf('/'));
}
return url;
}
Don't forget to sign the assembly.
That's it! build the project and you are done.
Update (5.11.07): I have got a comment from Nicholas about checking ListItemServerRelativeUrl for null before using it. What can I say.. He's right.
This is his comment:
"Nicholas Hadlee said: Hi, I found a strange situation where this can cause an nullreference exception in some pages. The seachResults.aspx was causing it for me. I added an <code>if (SPContext.Current.ListItemServerRelativeUrl != null )</code> whcih seemed to fix things up. Good job getting this to work anyway."
Deployment:
You could create a MOSS solution for that, but I am too lazy :) so what you need to do here is:
1. Install the assembly to the GAC.
2. Add SafeControl registration to web.config.
Usage:
As the nature of menus, we will want them in our master page:
1. Edit your master page and add the registration similar to this one at the beginning of the page:
<%@ Register Tagprefix="Itaysk" Namespace="Itaysk.SharePoint.Controls" Assembly="EnhancedSPMenu, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ff4fcc0bc7f78f41" %>
(If you use my attached assembly then copy and pate this line. Oterwise the namespace and assembly information may differ from your build)
2. Paste the following markup to some place holder, usualy PlaceHolderHorizontalNav place holder.
*This will create another menu in addition to the old one. You can delete the old one which is in the "PlaceHolderHorizontalNav" place holder
**You can also replace the existing <SharePoint:AspMenu tag with <Itaysk:MossMenu. Don't forget the closing tag - </SharePoint:AspMenu> turns to </Itaysk:MossMenu>
This is the markup for the menu control (and it's data source):
<Itaysk:MossMenu
ID="TopNavigationMenu"
Runat="server"
DataSourceID="topSiteMap"
EnableViewState="false"
AccessKey="<%$Resources:wss,navigation_accesskey%>"
Orientation="Horizontal"
StaticDisplayLevels="2"
MaximumDynamicDisplayLevels="1"
DynamicHorizontalOffset="0"
StaticPopoutImageUrl="/_layouts/images/menudark.gif"
StaticPopoutImageTextFormatString=""
DynamicHoverStyle-BackColor="#CBE3F0"
SkipLinkText=""
StaticSubMenuIndent="0"
CustomSelectionEnabled="true"
CssClass="ms-topNavContainer">
<StaticMenuStyle/>
<StaticMenuItemStyle CssClass="ms-topnav" ItemSpacing="0px"/>
<StaticSelectedStyle CssClass="ms-topnavselected" />
<StaticHoverStyle CssClass="ms-topNavHover" />
<DynamicMenuStyle BackColor="#F2F3F4" BorderColor="#A7B4CE" BorderWidth="1px"/>
<DynamicMenuItemStyle CssClass="ms-topNavFlyOuts"/>
<DynamicHoverStyle CssClass="ms-topNavFlyOutsHover"/>
<DynamicSelectedStyle CssClass="ms-topNavFlyOutsSelected"/>
</Itaysk:MossMenu>
<SharePoint:DelegateControl runat="server" ControlId="TopNavigationDataSource">
<Template_Controls>
<asp:SiteMapDataSource
ShowStartingNode="False"
SiteMapProvider="SPNavigationProvider"
id="topSiteMap"
runat="server"
StartingNodeUrl="sid:1002"/>
</Template_Controls>
</SharePoint:DelegateControl>
Result:
Enjoy!