This treat is for those that really hate how the ASP.NET Menu works. The menu shows the sub-menu when the user hovers the mouse over the root level menu items. This can get annoying especially if you accidentally pass the mouse over the menu when trying to click something inside the page. This can be really frustrating from a usability point of view especially when developing Web Applications (not just websites).
So, the following javascript will override the default behavior of the menu and show the second level of menu items only when the user clicks on the first (root) level menu items.
[The Code]
Also to call the function, add the following in the overriden OnPrerender method:
The above code takes care of more than just adding click behavior to the menu. It also makes sure that styling remains intact (for example when the mouse is above the menu item it changes color, depending on how you style the menu).
Now as for where to place this javascript, I recommend a common place where it will be used across all pages, also adding them to a js file and referencing it would be fine. For example, if you have a User Control called "Top_Section.ascx", and that control has the menu inside it, then add the javascript to the top of that file before the menu.
I am not going to go into the details of the code since this is basically code taken from Microsoft's javascript for the Menu control and modified to our needs. The reason why this would work (since you will not call these functions explicitly), is that the original versions of these functions are called by the Menu Control itself, and since we wrote our own implementation of those functions (having the same name as the original ones we want to replace) the new implementation will override the default one as long as they appear after. That is how overriding is simulated in javascript. And these functions will definitely be parsed after the default ones because ASP.NET loads its resources first and before any user defined functions do. You can check that by viewing the source of the page. Also to know what I changed you can compre these functions to their originals by requesting the resources generated by ASP.NET (the ones that coontain "ScriptResource.axd").
Hope this helps you out.
So, the following javascript will override the default behavior of the menu and show the second level of menu items only when the user clicks on the first (root) level menu items.
[The Code]
function addClickBehavior(menuTable)
{
var tbody = menuTable.getElementsByTagName("TBODY")[0];
var tr = tbody.getElementsByTagName("TR")[0];
for(var i = 0; i < tr.childNodes.length; i++)
{
var td = tr.childNodes[i];
if(td.tagName && td.tagName.toLowerCase() == 'td')
{
var anchor = td.getElementsByTagName("A")[0];
if(anchor)
{
var onClick = td.onmouseover;
td.onclick =
(function (el, method){
return function(evt){
method.call(el);
if(window.event) {
evt = window.event
}
evt.cancelBubble = true;
};
})(td, onClick);
td.onmouseover =
(function (el){
return function(){
Menu_HoverRoot(el);
};
})(td);
//add cursor style
anchor.style.cursor = "default";
anchor.onclick = function(){return false;};
//td.onmouseout = null;
}
}
}
}
function WebForm_RemoveClassName(element, className) {
var current = element.className;
var oldLength = -1;
if (current) {
while(oldLength != current.length)
{
if (current.substring
(current.length - className.length - 1,
current.length) == ' ' + className) {
element.className =
current.substring
(0, current.length - className.length - 1);
oldLength = current.length;
current = element.className;
continue;
}
if (current == className) {
element.className = "";
oldLength = current.length;
current = element.className;
continue;
}
var index = current.indexOf(' ' + className + ' ');
if (index != -1) {
element.className =
current.substring
(0, index) +
current.substring
(index + className.length + 2, current.length);
oldLength = current.length;
current = element.className;
continue;
}
if (current.substring
(0, className.length) == className + ' ') {
element.className =
current.substring
(className.length + 1, current.length);
}
current = element.className;
oldLength = current.length;
}
}
}
function Menu_HoverRoot(item) {
var node = (item.tagName.toLowerCase() == "td") ?
item:
item.cells[0];
var data = Menu_GetData(item);
if (!data) {
return null;
}
var nodeTable = WebForm_GetElementByTagName(node, "table");
if (data.staticHoverClass) {
//avoids adding the same class twice
nodeTable.hoverClass = data.staticHoverClass;
WebForm_AppendToClassName(nodeTable, data.staticHoverClass);
}
node = nodeTable.rows[0].cells[0].childNodes[0];
if (data.staticHoverHyperLinkClass) {
node.hoverHyperLinkClass = data.staticHoverHyperLinkClass;
WebForm_AppendToClassName
(node, data.staticHoverHyperLinkClass);
}
return node;
}
Also to call the function, add the following in the overriden OnPrerender method:
Page.ClientScript.RegisterStartupScript
(Page.GetType(), "addClickBehavior",
"addClickBehavior(document.getElementById('" +
Menu1.ClientID + "'));", true);
The above code takes care of more than just adding click behavior to the menu. It also makes sure that styling remains intact (for example when the mouse is above the menu item it changes color, depending on how you style the menu).
Now as for where to place this javascript, I recommend a common place where it will be used across all pages, also adding them to a js file and referencing it would be fine. For example, if you have a User Control called "Top_Section.ascx", and that control has the menu inside it, then add the javascript to the top of that file before the menu.
I am not going to go into the details of the code since this is basically code taken from Microsoft's javascript for the Menu control and modified to our needs. The reason why this would work (since you will not call these functions explicitly), is that the original versions of these functions are called by the Menu Control itself, and since we wrote our own implementation of those functions (having the same name as the original ones we want to replace) the new implementation will override the default one as long as they appear after. That is how overriding is simulated in javascript. And these functions will definitely be parsed after the default ones because ASP.NET loads its resources first and before any user defined functions do. You can check that by viewing the source of the page. Also to know what I changed you can compre these functions to their originals by requesting the resources generated by ASP.NET (the ones that coontain "ScriptResource.axd").
Hope this helps you out.
46 comments:
Nice post...
This is a *wonderful* solution to an IRRITATING problem. There is one enhancement that I might suggest. The code above misbehaves when I use it in my application. It doesn't call the Menu_HoverRoot() or WebForm_RemoveClassName() in my local code and after clicking on a root menu, the HoverClassStyle isn't removed when the mouse moves away from the root menu item. The following code is my adaptation of the solution, which works for me a little better. I hope others will benefit like I did!
function addClickBehavior(menuTable) {
var tbody = menuTable.getElementsByTagName("TBODY")[0];
var tr = tbody.getElementsByTagName("TR")[0];
for(var i = 0; i < tr.childNodes.length; i++) {
var td = tr.childNodes[i];
if(td.tagName && td.tagName.toLowerCase() == 'td') {
var anchor = td.getElementsByTagName("A")[0];
if(anchor) {
var onClick = td.onmouseover;
td.onclick =
(function (el, method){
return function(evt){
method.call(el);
if(window.event) {
evt = window.event
}
evt.cancelBubble = true;
RemoveRootHover(el);
};
})(td, onClick);
td.onmouseover =
(function (el){
return function(){
HoverRootMenu(el);
};
})(td);
//add cursor style
anchor.style.cursor = "default";
anchor.onclick = function(){return false;};
td.onmouseout = (
function(el){
return function(){
RemoveRootHover(el)
}
}
)(td);
}
}
}
}
function HoverRootMenu(item) {
var node = (item.tagName.toLowerCase() == "td")?item:item.cells[0];
var data = Menu_GetData(item);
if (!data) {return null;}
var nodeTable = WebForm_GetElementByTagName(node, "table");
if (data.staticHoverClass) {
//avoids adding the same class twice
nodeTable.hoverClass = data.staticHoverClass;
WebForm_AppendToClassName(nodeTable, data.staticHoverClass);
}
node = nodeTable.rows[0].cells[0].childNodes[0];
if (data.staticHoverHyperLinkClass) {
node.hoverHyperLinkClass = data.staticHoverHyperLinkClass;
WebForm_AppendToClassName(node, data.staticHoverHyperLinkClass);
}
return node;
}
function RemoveRootHover(item) {
var node = (item.tagName.toLowerCase() == "td")?item:item.cells[0];
var data = Menu_GetData(item);
if (!data) {return null;}
var nodeTable = WebForm_GetElementByTagName(node, "table");
if (nodeTable.hoverClass) {
WebForm_RemoveClassName(nodeTable, nodeTable.hoverClass);
}
node = nodeTable.rows[0].cells[0].childNodes[0];
if (node.hoverHyperLinkClass) {
WebForm_RemoveClassName(node, node.hoverHyperLinkClass);
}
}
You still need to register the javascript in the page's prerender routine.
Thanks Muhammad. Great solution!
Okay. I tweaked it once more so that the dropdown menu will close if the user doesn't move their mouse over it within three seconds after clicking to drop it down. I also fixed a bug where clicking on a root menu without a dropdown menu wouldn't fire the link.
Enjoy.
function FixMenu(menuTable) {
if (!menuTable) {return};
var tbody = menuTable.getElementsByTagName("TBODY")[0];
var tr = tbody.getElementsByTagName("TR")[0];
for(var i = 0; i < tr.childNodes.length; i++) {
var td = tr.childNodes[i];
if(td.tagName && td.tagName.toLowerCase() == 'td') {
var anchor = td.getElementsByTagName("A")[0];
if(anchor) {
var onClick = td.onmouseover;
td.onclick = (function (e, method){return function(evt){method.call(e); if(window.event) {evt = window.event} evt.cancelBubble = true; RemoveRootHover(e); __disappearAfter = 3000; Menu_Collapse(e)}})(td, onClick);
td.onmouseover = (function (e){return function(){HoverRootMenu(e)}})(td);
anchor.style.cursor = "default";
var submnu = WebForm_GetElementById(td.id + "Items");
if (submnu) {anchor.onclick = function(){return false}}
td.onmouseout = (function(e){return function(){RemoveRootHover(e)}})(td);
}
}
}
}
function HoverRootMenu(item) {
var node = (item.tagName.toLowerCase() == "td")?item:item.cells[0];
var data = Menu_GetData(item);
if (!data) {return null;}
var nodeTable = WebForm_GetElementByTagName(node, "table");
if (data.staticHoverClass) {
nodeTable.hoverClass = data.staticHoverClass;
WebForm_AppendToClassName(nodeTable, data.staticHoverClass);
}
node = nodeTable.rows[0].cells[0].childNodes[0];
if (data.staticHoverHyperLinkClass) {node.hoverHyperLinkClass = data.staticHoverHyperLinkClass; WebForm_AppendToClassName(node, data.staticHoverHyperLinkClass)}
return node;
}
function RemoveRootHover(item) {
var node = (item.tagName.toLowerCase() == "td")?item:item.cells[0];
var data = Menu_GetData(item);
if (!data) {return null;}
var nodeTable = WebForm_GetElementByTagName(node, "table");
if (nodeTable.hoverClass) {WebForm_RemoveClassName(nodeTable, nodeTable.hoverClass)}
node = nodeTable.rows[0].cells[0].childNodes[0];
if (node.hoverHyperLinkClass) {WebForm_RemoveClassName(node, node.hoverHyperLinkClass)}
}
Hello, Muhammad.
I've tried your code and it worked fine in my development environment (VS STUDIO 2005 Development server),but when a copy that same project in my IIS It display a javascript error(Line: 5557 Error: An Object was expected)
The code generated:
Line 5557: addClickBehavior(this.document.getElementById('mnuPrincipal'));var mnuPrincipal_Data = new Object();
mnuPrincipal_Data.disappearAfter = 200;
Please helpme with this issue
I pulled your original code without any of the changes and it is working exactly as I need. This saved me a ton of time since I was about to build an alternate menu control. Thank you for sharing your knowledge Muhammad.
I tried this solution and couldn't get it working it builds correctly but the menu still works only by hovering
Can anyone send-me a project with this working?
Please I would appretiate it very much
adelinojoc@gmail.com
After every PostBack the Menu does not use the new JavaScript anymore and it opens on hover again. I apply the Script on PreRender.
Any ideas?
Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
Dim miScript As New StringBuilder
With miScript
.Append(" var menu = document.getElementById('" & Me.menuObjetos.ClientID + "') ;")
.Append(" addClickBehavior(menu); ")
.Append(" alert('fin'); ")
End With
Me.Page.ClientScript.RegisterStartupScript(Page.GetType(), "addClickBehavior", miScript.ToString, True)
MyBase.OnPreRender(e)
End Sub
I copy the javascript code after the body tag, on my aspx page.
It works for me using Internet Explorer 7.
Hi Guys can u give me aspx page code also because i getting error on line "method.call(el)". Reason method is showing null
How would you do the same with the subitems?
Lets say I have something like:
Item 1
- Subitem 1
-- Actual link 1
-- Actual link 2
- Subitem 2
-- Actual link 3
Item 2
...
The subitems have no link (just like the items). I need to be able to click the subitems without closing the menu. Is there a way to do that?
Thanks! :)
Gabriela,
This "hack" is only meant for 1 level menus, whether it could be done or not, maybe it could but you will have to "hack" at it :)
Thank u.. this is wonderful !
Dear Anonymous
My requirement is some what different.
but desparately needed ur help on this.
I had applied the functionality to the Menu Item.
Everything is working fine.
But currently iam looking for added functionality i.e. Now the Menu is being visible when the mouse is clicked.
But my requirement is it should go collapsable state when we click on it the second time.
Great SOlution thanks...HAs anyone updated the JS so that the menu will also close on the 2nd click?
I have tweaked this code to allow closing of the current open menu with a click. I just changed one method, the other remain the same. It's not pretty but it works.
function addClickBehavior(menuTable)
{
if (!menuTable) {return};
var tbody = menuTable.getElementsByTagName("TBODY")[0];
var tr = tbody.getElementsByTagName("TR")[0];
for(var i = 0; i < tr.childNodes.length; i++) {
var td = tr.childNodes[i];
if(td.tagName && td.tagName.toLowerCase() == 'td') {
var anchor = td.getElementsByTagName("A")[0];
if(anchor)
{
var onClick = td.onmouseover;
td.onclick = ( function (e, method) { return function(evt) { var v1 = e.getAttribute("OpenFlag"); if (!v1) { e.setAttribute("OpenFlag", "1"); method.call(e); if(window.event) { evt = window.event } evt.cancelBubble = true; RemoveRootHover(e); __disappearAfter = 3000; Menu_Collapse(e); } else { e.removeAttribute("OpenFlag"); RemoveRootHover(e); } } } ) (td, onClick);
td.onmouseover = (function (e){return function(){HoverRootMenu(e);}})(td);
anchor.style.cursor = "default";
var submnu = WebForm_GetElementById(td.id + "Items");
if (submnu) {anchor.onclick = function(){return false}}
td.onmouseout = (function(e){return function(){e.removeAttribute("OpenFlag");RemoveRootHover(e);}})(td);
}
}
}
}
code is not working!!!!!!!!!!
Please reply the above code is not working for me!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Thank you very much for the code. It is perfect for one level asp.net menu.
onPrerender when I register the event it generates a javascript error saying object missing...please help
Thanks! Exactly what I was looking for!
but i've to open JQuery Dialog Box from menuItem Click event
hello,
ca anyone help to make the entire menu and submenu open with a click ?
thanks in advace.
Paolo
Hi, someone has an idea about how this could be changed that the hover effect still remains? Would be nice if the behaviour would be the same for "normal" browser users that don't have touch.
Thanks
Martin
Hey! This is my 1st comment here so I just wanted to give a quick shout
out and say I truly enjoy reading your posts. Can
you suggest any other blogs/websites/forums that go
over the same topics? Thanks a ton!
My page; chapter 7 bankruptcy florida
I know this web page gives quality based content and other material, is there any other web site which gives such things in quality?
my homepage ... [网站首页]
Unquestionably believe that which you said. Your favorite
justification appeared to be on the net the easiest thing
to be aware of. I say to you, I certainly get irked while
people think about worries that they plainly do not know about.
You managed to hit the nail upon the top as well as defined out the whole thing without having side effect , people can take a signal.
Will likely be back to get more. Thanks
My weblog ... elektronisk cigaret
always i used to read smaller articles that as well clear their motive, and
that is also happening with this piece of
writing which I am reading at this time.
my web-site elektronisk cigaret
Nice blog right here! Also your website so much up fast!
What host are you using? Can I get your associate link
in your host? I desire my website loaded up as quickly as yours lol
my blog - elektronisk cigaret
I'm impressed, I have to admit. Seldom do I come across a blog that's
both educative and entertaining, and let me tell you, you've hit the nail on the head. The issue is an issue that not enough people are speaking intelligently about. I'm very
happy that I came across this during my hunt for something concerning this.
Also visit my blog post; diet plans for women to lose weight
Superb blog! Do you have any tips for aspiring writers?
I'm planning to start my own site soon but I'm a little lost on everything.
Would you advise starting with a free platform like Wordpress or go for
a paid option? There are so many options out there that I'm completely overwhelmed .. Any recommendations? Kudos!
Feel free to visit my page ... simply click the following web site
I've been surfing online greater than 3 hours nowadays, yet I never discovered any attention-grabbing article like yours. It's pretty price enough
for me. In my opinion, if all website owners and bloggers made
just right content as you probably did, the web will likely be a lot
more helpful than ever before.
Here is my page :: pregnancy and powerade
Heya i am for the first time here. I came across this board and I find It really useful
& it helped me out a lot. I hope to give something back and help others like you helped me.
Feel free to surf to my blog post; eating lean cuisine while pregnant
Fastidious answer back in return of this query with solid arguments and
explaining everything concerning that.
My page - are ore ida gluten free
I've read some just right stuff here. Definitely worth bookmarking for revisiting. I surprise how a lot attempt you place to create the sort of fantastic informative site.
Feel free to visit my webpage: haverford quaker
My relatives every time say that I am killing my time here at net, except I know
I am getting experience daily by reading thes good content.
Feel free to visit my website ... nabisco Printable coupons december 2010
You could certainly see your enthusiasm in the article you write.
The world hopes for more passionate writers such as you who aren't afraid to mention how they believe. Always follow your heart.
my blog - lysol coupons january 2013
Hello to every single one, it's actually a nice for me to visit this site, it contains valuable Information.
Also visit my site dial soap coupons printable liquid hand soap
I know this site provides quality based posts and extra material,
is there any other web page which offers such information in quality?
Review my web-site healthy waist to height ratio
Hi i am kavin, its my first time to commenting anyplace, when i read this paragraph i thought i could also create comment due to this sensible article.
Here is my web-site jello brand pudding coupons
Hello there! I know this is kind of off topic but I was wondering which blog platform are you using for this
site? I'm getting tired of Wordpress because I've had issues with hackers and I'm looking at options for another platform. I would be awesome if you could point me in the direction of a good platform.
Here is my website - tag formula 1 watch
Has anyone altered this code to work when the ASP.NET Menu is RenderingMode="List"
What does tag carrera for you? Are you able to recognize the distinction in between costume
tag carrera 1887 and more useful pieces? These suggestions must
assist you find out more regarding the planet
of tag heuer carrera calibre s. This article will give
you some great tricks for all of your tag carrera calibre 16 needs.
My web-site tag heuer carrera automatic
Hello! I know this is somewhat off-topic however I had to ask.
Does managing a well-established blog such as yours require a lot of work?
I am brand new to writing a blog however I do write in my journal everyday.
I'd like to start a blog so I can easily share my own experience and views online. Please let me know if you have any kind of ideas or tips for brand new aspiring bloggers. Thankyou!
My web-site :: mia moore takes the als rocket 7152010
hey there and thanκ you for уouг
information – I've certainly picked up anything new from right here. I did however expertise some technical issues using this website, since I experienced to reload the web site a lot of times previous to I could get it to load correctly. I had been wondering if your web host is OK? Not that I'm comрlaining, but ѕluggish lοadіng inѕtances times
will sometimes affect уour placemеnt in google аnd could damаge your
quality scorе if ads and marketіng with
Adwoгdѕ. Well I am adding this RSS tо mу emaіl anԁ could look out for much morе οf yοur reѕpective
fascinating content. Ensuге that you uρdаte this agaіn
soon.
Feel free to surf tο my homеρage:
diamondlinks review
Post a Comment