Geeks With Blogs
Max's Blog .NET, Windows Phone, SQL Server and other exciting stuff

First of all, sorry for not posting for a quite long time. I have started working on designing and developing a charity website for blood donation. So all my leisure time was diverted into that. I will ensure that going forward, I post at least one post every week. Lets get into the topic of this week, without delaying any more time.

I have always been using asp:Menu and asp:Treeview for transforming the sitemaps to super cool menu which are auto generated by ASP.NET for us. But I wanted more and thought some jQuery can solve this. So the ultimate deal is to loop through the sitemap and add our own extra bits to it to render it as a menu and use jQuery for adding some animation. Seriously jQuery is powerful, I would have ended up writing whole lots of Javascript code for doing a small animation, which is ridiculously one line in jQuery. I attended Microsoft WebCamps in Sydney and that inspired me to move towards jQuery from Javascript. I had a great time there learning new stuff.

This is how the menu will look like.

Menu


 

By default only the Level 1 will be shown and when you click on it, the next level will open up. Also based on the collapsible state, the arrow image will be reversed automatically. Also the current page’s link will be shown by default. I have tested it and its working fine in IE 6,7,8 and Firefox and Chrome browsers. I initially used ul, li combos but that did not work fine in IE 7 & 6, so had to follow the div route. As such this supports 4 levels of link hierarchy, but can easily be modified to handle any number of levels.

Now the steps. I believe you have your own Sitemap, else you can use the dummy Sitemap I’ve used in this sample. You can get the files here.

1) The basic idea is to render the sitemap links within div tags and put in a class name based on the level it is in like menu1, menu2, etc…

2) For example, the DataToolBox link and its sub links are rendered as follows in the browser using IE8 using this method.

<DIV class="Menu1 linky linkyup" title="DataToolBox controls">&nbsp;DataToolBox</DIV>
<DIV>
    <DIV style="DISPLAY: block" class=Menu2 title="ListView Example"><A href="/ListViewExample.aspx">ListView</A></DIV>
    <DIV style="DISPLAY: block" class=Menu2 title="GridView Example"><A href="/GridViewExample.aspx">GridView</A></DIV>
    <DIV style="DISPLAY: block" class="Menu2 linky linkyup" title="DataToolBox controls">&nbsp;DataToolBox</DIV>
    <DIV>
        <DIV style="DISPLAY: block" class=Menu3 title="ListView Example"><A href="/ListViewExample1.aspx">ListView</A></DIV>
        <DIV style="DISPLAY: block" class=Menu3 title="GridView Example"><A href="/GridViewExample1.aspx">GridView</A></DIV>
        <DIV style="DISPLAY: block" class="Menu3 linky linkyup" title="DataToolBox controls">&nbsp;Testing</DIV>
        <DIV>
            <DIV style="DISPLAY: block" class=Menu4 title="ListView Example"><A href="/TestingExample1.aspx">ListView</A></DIV>
        </DIV>
    </DIV>
</DIV>

3) Now we will create the main method which will, render the menu snippet on to the front end. For this, we create a literal control named menu in the aspx page. We will set the text of this literal control to the html generated by our method.

<asp:Literal runat="server" ID="menu"></asp:Literal>

4) Method for generating the menu code. We encapsulate the menu within a plain div only for levels higher than 1. In some cases, you will have a sitemap entry but would not want to render it, in that case, add a visible attribute to the sitemapnode and set it as false and render only those nodes which does not have a visible attribute or the ones with visible as true. I just then build a string and loop through the siblings for rendering them as well and if that nodes has any children, then I increment the level and call the same method recursively.

    public void getChildren(SiteMapNode n, int Level)
    {
        if (Level > 1)
            menu.Text += "<div>";
        while (true)
        {
            if (n["Visible"] == null)
            {
                // Displays the title of each node.
                if (n.Url.Contains(".aspx"))
                {
                    menu.Text += "<div class=\"Menu" + Level.ToString() + "\" title=\"" + n.Description + "\"";
                    menu.Text += "><a href=\"" + n.Url + "\">" + n.Title + "</a></div>";
                }
                else
                {
                    menu.Text += "<div class=\"Menu" + Level.ToString() + " linky\"";
                    menu.Text += " title=\"" + n.Description + "\">&nbsp;" + n.Title + "</div>";
                    if (n.ChildNodes.Count > 0)
                    {
                        Level++;
                        getChildren(n.ChildNodes[0], Level);
                        Level--;
                    }
                }
            }
            if (n.NextSibling == null)
                break;
            else
                n = n.NextSibling;
        }
        if (Level > 1)
            menu.Text += "</div>";
    }
}

5) You then need to add a script reference to jQuery library 1.4.2 to one of Microsoft or Google CDN

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js” language="javascript" type="text/javascript"></script>

6) Then I add this jQuery snippet to the end of the aspx page within a <script> tag. Now here, I hide all the menus higher than 1 by default. And add some click functionality to each level menu to toggle open the next level below it, also a few lines to close all the sub menus when clicked on higher level. Like closing the level 3 and level 4 menus when I click on level 1 to close it. Also I’ve used the toggleClass to change the arrow image. Believe me, jQuery is the way forward!! I can’t imagine writing all these in Javascript.

$(document).ready(function () {
    $('div.Menu1.linky').next('div').children('div.Menu2').hide();
    $('div.Menu2.linky').next('div').children('div.Menu3').hide();
    $('div.Menu3.linky').next('div').children('div.Menu4').hide();

    $("div.Menu1.linky").click(function () {
        $(this).next('div').find('div.Menu3').hide();
        $(this).next('div').find('div.Menu4').hide();
        $(this).next('div').children('div.Menu2').toggle();

        $(this).toggleClass('linkyup');
        $(this).next('div').find('div.Menu2').removeClass('linkyup');
        $(this).next('div').find('div.Menu3').removeClass('linkyup');
    });

    $("div.Menu2.linky").click(function () {
        $(this).next('div').find('div.Menu4').hide();
        $(this).next('div').children('div.Menu3').toggle();

        $(this).toggleClass('linkyup');
        $(this).next('div').find('div.Menu4').removeClass('linkyup');
        $(this).next('div').children('div.Menu3').removeClass('linkyup');
    });

    $("div.Menu3.linky").click(function () {
        $(this).next('div').children('div.Menu4').toggle();
        $(this).toggleClass('linkyup');
    });

});

7) Another problem here I had is to maintain the same current page’s link opened during post back. So what I do here is, have a hidden field in the page and then it was my friend Suprotim Agarwal an ASP.NET MVP who came to the rescue. This particular aspect was inspired by an article in DonetCurry by him. Here it is for your reference. Also this one. My many thanks to him. So I store the current page’s url in a hidden field and and find out the a tag with this url and make it parent level menus in open state. It also works when there is some query string that is appended to the url.

So add this to the current page aspx.

<asp:HiddenField Value="" ID="CurrentPageTitle" runat="server" />

8) So now the jQuery which handles the toggling based on the current page’s url that is saved in the above hidden field.

//If current URL is in Level 4.
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu4').parent('div').parent('div').parent('div').children().show();
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu4').parent('div').parent('div').children().show();
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu4').parent('div').children().show();

$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu4').parent('div').parent('div').parent('div').prev('div.Menu1').toggleClass('linkyup');
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu4').parent('div').parent('div').prev('div.Menu2').toggleClass('linkyup');
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu4').parent('div').prev('div.Menu3').toggleClass('linkyup');

//If current URL is in Level 3.
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu3').parent('div').parent('div').parent('div').children().show();
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu3').parent('div').parent('div').children().show();
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu3').parent('div').children().show();

$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu3').parent('div').prev('div.Menu2').toggleClass('linkyup');
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu3').parent('div').parent('div').prev('div.Menu1').toggleClass('linkyup');

//If current URL is in Level 2.
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu2').parent('div').parent('div').children().show();
$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu2').parent('div').children().show();

$("a[href*='<%=CurrentPageTitle.Value%>']").parent('div.Menu2').parent('div').prev('div.Menu1').toggleClass('linkyup');

9) Also add this line to the page load event, which sets the current page url to the hidden field.

CurrentPageTitle.Value = Request.ServerVariables["URL"].Replace("/", \\/);

10) Download the files and try navigating to the TestingExample1.aspx file and you will see that the levels are automatically shown visible for us. Also try putting some query string values.

11) Get the files here.

Please comment below or contact me for any clarifications / issues. I will be very happy to help you out with all what I can.

Cheers!

Posted on Sunday, July 25, 2010 10:31 AM ASP.NET , jQuery | Back to top


Comments on this post: Transform ordinary Sitemap into a Wonderful jQuery powered Multi level vertical collapsible menu

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Max | Powered by: GeeksWithBlogs.net