Introduction
This is a simple workaround for the ASP.NET 2.0 menu control's parent menu item highlighting.
Background
I have some websites which have relatively big and deep menu structures. If the user scrolls deep with the mouse in ASP.NET menus, after some levels, the look of the menu becomes a little too complex, and maybe the user would be be confused as to which are the parents of the current menuitem?
There is a solution called CSS Friendly Adapters. I tried to use this, but I think it is full of bugs, and its behavior is often very crazy and annoying. There are a lot of differences between the original ASP.NET menu control and the ASP.NET menu with CSS Friendly Adapters, and I did not like that...
Using the Code
First of all, this is not the most beautiful solution, but it works. First, I wanted to do everything on the server side, but I found that the MenuItem
does not inherit from Control
, and because of that, it does not have client side attributes which can be manipulated. Thus, I decided to look what client-side code the ASP.NET menu generates. I noticed that it is a big table, and some of the rows have ID
attributes, which begin with the ClientID
of the ASP.NET menu, and OnMouseOver
and OnMouseOut
event handlers. I played a little with the event handlers, and figured out that if I call them explicitly from JavaScript, I can highlight any of the menu items.
Thus, I just needed to find out how the ASP.NET menu generates client IDs. This is very simple. If you search on the server side for the menu items recursively, you just need to number them with a simple counter, and you will get the generated client side number of the menu items. Here is the code, which searches for the menu items:
void FindMenuItems(MenuItem mi, List<MenuItem> list)
{
foreach (MenuItem child in mi.ChildItems)
{
list.Add(child);
}
foreach (MenuItem child in mi.ChildItems)
{
FindMenuItems(child, list);
}
}
We have the menu items, now we need to iterate through the list, and find all the parents (to the root) for every menu item.
Here is the code which searches the parents of a menu item:
void FindParents(MenuItem mi, List<MenuItem> list)
{
if(mi.Parent != null)
{
if(mi.Parent != mainMenu.Items[0])
{
list.Add(mi.Parent);
FindParents(mi.Parent, list);
}
}
}
Putting It All Together
In the Page_Load
event handler, I call the above methods for every menu item. I find every parent (till the root), and calculate the client side ID of both the current menu item and the parent menu items. I concatenate the parent menu item's client side IDs to one comma separated string. And finally, I bind a client side OnMouseOver
and OnMouseOut
event handler for each menu item. To these event handlers, I pass two parameters: the menu item's client side ID, and the comma separated string, which holds the menu item's parents.
List<MenuItem> list = new List<MenuItem>();
list.Add(mainMenu.Items[0]);
FindMenuItems(mainMenu.Items[0], list);
foreach (MenuItem mi in list)
{
List<MenuItem> parents = new List<MenuItem>();
FindParents(mi, parents);
string parentSnake = String.Empty;
foreach (MenuItem parent in parents)
{
parentSnake += "mainMenun" + list.IndexOf(parent).ToString() + ",";
}
parentSnake.TrimEnd(',');
script += "document.getElementById(
'" + mainMenu.ClientID + "n" +
list.IndexOf(mi).ToString() + "').onmouseover =
function(){HoverParents('" + mainMenu.ClientID +
"n" + list.IndexOf(mi).ToString() + "', '" + parentSnake + "')};";
script += "document.getElementById('" + mainMenu.ClientID + "n" +
list.IndexOf(mi).ToString() +
"').onmouseout = function(){UnhoverParents('" +
mainMenu.ClientID + "n" + list.IndexOf(mi).ToString() + "', '" +
parentSnake + "')};";
}
ScriptManager.RegisterStartupScript(this, typeof(string),
"MenuHoverUnhover", script, true);
The event handlers are written in JavaScript. The OnMouseOver
highlights the actual menu item, just like the original ASP.NET generated code does (with the same method call), after that iterates through the parents comma separated list and highlights them all the same way. The OnMouseOut
does the opposite of that.
function HoverParents(id, parentSnake)
{
Menu_HoverDynamic(document.getElementById(id));
var parents = parentSnake.toString().split(',');
for(i = 0;i < parents.length;i++)
{
if(parents[i] != '')
{
Menu_HoverDynamic(document.getElementById(parents[i]));
}
}
}
function UnhoverParents(id, parentSnake)
{
Menu_Unhover(document.getElementById(id));
var parents = parentSnake.toString().split(',');
for(i = 0;i < parents.length;i++)
{
if(parents[i] != '')
{
Menu_Unhover(document.getElementById(parents[i]));
}
}
}
Once we get the client-side script which binds the event handlers, I use the Script Manager to embed the JavaScript in the page at every postback.
Points of Interest
Relying on client-side generated IDs is not the best method you can choose, but sometimes, it is simple, and works fine.