Bryant Likes's Blog

It's all about WebData

Recent Posts

Tags

News


  • Windows Live Alerts
    View Bryant Likes's profile on LinkedIn

    Me

    Get Microsoft Silverlight
    by clicking "Install Microsoft Silverlight" you accept the
    Silverlight license agreement


    The posts on this weblog are provided "as is" with no warranties and confer no rights. The opinions expressed herin are the personal opinions of the individual authors and do not represent the views of Avanade in any way.

Community

Email Notifications

Archives

Creating a UL SiteMap Menu

It's been awhile since I last bloged! I'm still alive and still working on my ASP.Net 2.0 project.

Today I was working on a new menu and I wanted to use the unordered list approach (UL) and I wanted it to be driven by the web.sitemap file. After some googling I came across this post by Danny Chen which uses a repeater control and then goes on to create a template control. Close to what I wanted, but I really didn't need a template, just a simple list with a class declaration on the active menu item. I also found this post by Jeff Lynch which talked about the need for a simple sitemap menu. So I created one. :)

The code is pretty simple. It is made up of two classes and it currently renders all the items in the sitemap starting with the root. This suites my needs perfectly so I'm not adding any other features, but it would be very easy to customize this to add more hierarchies. Below is the code for the SimpleSiteMenu and SimpleMenuItem controls:

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.Collections;
using System.Web.UI;
using System.Web;

namespace DavisTwelve.Web.Controls
{
    
public class SimpleSiteMenu : CompositeDataBoundControl
    {

        
private string _activeCssClass;

        
public string ActiveItemCssClass
        {
            
get { return _activeCssClass; }
            
set { _activeCssClass = value; }
        }

        
private string _itemCssClass;

        
public string ItemCssClass
        {
            
get { return _itemCssClass; }
            
set { _itemCssClass = value; }
        }

        
private int AddChildNodes(SiteMapNode node, SiteMapNode current)
        {
            
int count = 0;

            
foreach (SiteMapNode child in node.ChildNodes)
            {
                count++;
                Controls.Add(
new SimpleMenuItem(child, GetCssClass(child, current)));
                count += AddChildNodes(child, current);
            }

            
return count;
        }

        
private string GetCssClass(SiteMapNode node, SiteMapNode current)
        {
            
if (node == current)
            {
                
return _activeCssClass ?? _itemCssClass;
            }
            
else
            {
                
return _itemCssClass;
            }
        }

        
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
        {
            
int count = 0;
            
SiteMapNode current = null;
            
SiteMapNodeCollection nodes = dataSource as SiteMapNodeCollection;
            
SiteMapDataSource siteMap = this.GetDataSource() as SiteMapDataSource;

            
if (dataBinding && nodes != null)
            {
                
if (siteMap != null)
                {
                    current = siteMap.Provider.CurrentNode;
                }
                
                
foreach (SiteMapNode node in nodes)
                {
                    count++;
                    Controls.Add(
new SimpleMenuItem(node, GetCssClass(node, current)));
                    count += AddChildNodes(node, current);
                }
            }

            
return count;
        }

        
public override void RenderBeginTag(System.Web.UI.HtmlTextWriter writer)
        {
            writer.AddAttribute(
HtmlTextWriterAttribute.Id, this.ClientID);
            writer.RenderBeginTag(
HtmlTextWriterTag.Ul);
        }

        
public override void RenderEndTag(System.Web.UI.HtmlTextWriter writer)
        {
            writer.RenderEndTag();
        }
    }
}

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Web;

namespace DavisTwelve.Web.Controls
{
    
public class SimpleMenuItem : WebControl
    {
        
string _url;
        
string _title;
        
string _cssClass;

        
public SimpleMenuItem(SiteMapNode node, string cssClass)
        {
            _url = node.Url;
            _title = node.Title;
            _cssClass = cssClass;
        }

        
protected override void Render(HtmlTextWriter writer)
        {
            
if (!string.IsNullOrEmpty(_cssClass))
            {
                writer.AddAttribute(
HtmlTextWriterAttribute.Class, _cssClass);
            }
            writer.RenderBeginTag(
HtmlTextWriterTag.Li);
            writer.AddAttribute(
HtmlTextWriterAttribute.Href, _url);
            writer.RenderBeginTag(
HtmlTextWriterTag.A);
            writer.Write(_title);
            writer.RenderEndTag();
// a
            writer.RenderEndTag(); // li
            
        }
    }
}

To use the control just add a SiteMapDataSource to the page and then set the DataSourceID to the data source's id.

BTW - Thanks to Jeff Atwood for the cool copy as RTF macro.

 

Comments

Eva Rossa said:

Looks like it's the solution I have been looking for! I am a newbie to ASP 2.0 - would it possible for you to provide me the complete code to read, understand and try?
# May 4, 2006 7:30 AM

bryantlikes said:

Just put the code above (without the namespace) in some cs files in your app_code folder of your asp.net solution and then add a SimpleSiteMenu control to your page. If you need more direction than that I can create a simple asp.net project that uses it and post it for download.
# May 4, 2006 11:09 AM

Eva Rossa said:

Hi Bryan

Yes, I do need more direction. If you could create a simple project that uses the control, that would be great! It'll help me understand more how that's being properly used.
# May 5, 2006 1:47 AM

Jonah said:

Bryan! I could use a simple project as well... any chance you could set one up?

Cheers!

. jonah

# September 24, 2006 11:48 PM

anonymous said:

This works well, and I've extended the class to output a nested ul/li structure to match the sitemap hierarchy. The one problem with this is that it doesn't render at all on a postback, since in that case the 'dataBinding' parameter to CreateChildControls() is false, so no child controls are created. Any suggestions on how to make this function properly on a postback? It seems something needs to get saved to Viewstate, but I can't figure out exactly what. Thank you.

# January 29, 2007 6:21 AM

Neil D said:

I had this problem too - the problem as you say presents itself in this method:

protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)

The first hit on the page results in CreateChildControls being called with the correct data source reference in the IEnumerable argument, with DataBinding = true. When you post back, the method gets called again, this time with an object array full of nulls, (one for each child control) and databinding = false.

The solution is I think due to the fact that it's your responsibility to manage the ViewState for the child controls, since the behaviour in the base class does not work correctly in this particular case.

When you add the EnableViewState = "false" attribute in the control declaration in the aspx page, the behaviour on postback changes. This time the control is data bound on postback as well as the first page hit, the reason being (I guess) that because viewstate is turned off, the page building mechanism no longer tries to use viewstate to rebuild the child controls collection.

If you need to store something to identify the currently selected item (e.g. a hashcode, primary key etc), you can still do this in ViewState, typically by wrapping the viewstate item in a get/set accessor and exposing it as a property of the control - just check that you're reading and writing this at the correct stage in the page lifecycle.

# January 31, 2007 2:59 AM

Microsoft SharePoint Products and Technologies Team Blog said:

Dominion Digital , a Virginia-based Microsoft Gold Partner, recently launched the Performance Food Group’s

# May 25, 2007 10:03 PM

Erik Dahlstrand said:

Excellent post! Thanks a bunch. I would really appreciate if you could give me some hints about how to customize the code to support hierarchies. I'm just going to show the first level of menu items. But when visiting a child page (deeper in the hierarchy) I would like a class declaration on the parent menu item to appear. Is this possible? Any ideas on how to implement?

# July 4, 2007 7:59 AM

tapic said:

how do we add this custom control you created to a page?

thanks

# March 24, 2008 12:37 PM

Scott Williams said:

Here is a version that works with postback.

protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)

       {

           int count = 0;

           SiteMapNode current = null;

           SiteMapDataSource siteMap = this.GetDataSource() as SiteMapDataSource;

           if (siteMap != null)

           {

               current = siteMap.Provider.CurrentNode;

               count++;

               Controls.Add(new ULMenuItem(siteMap.Provider.RootNode, GetCssClass(siteMap.Provider.RootNode, current)));

               count += AddChildNodes(siteMap.Provider.RootNode, current);

           }

           return count;

}

# April 16, 2008 10:42 PM

Jusin Case said:

Hi Bryant,

Just wanted to let you know that the apostrophe "s" in "Likes's" is unnecessary. A simple apostrophe will do; such as Likes'.

This rule of the English language also applies to other nouns and pronouns such as Jones. The pronoun, Jones', implies that something belongs to Jones much the way that this blog belongs to you.

Thought you might be interested.

Justin Case

# June 4, 2008 11:41 AM

ASP.NET Good Looking CSS Dropdown Menu | Harnet.nl said:

Pingback from  ASP.NET Good Looking CSS Dropdown Menu | Harnet.nl

# October 23, 2008 1:17 PM

christian said:

Hi Bryan,

great code example .. exactly what i am looking for, but as others before me have asked, i have to ask to;

how do i add this control to a aspx page?

// Christian

# January 8, 2009 4:29 AM

Jamieson Rhyne said:

Do you have the code which you used to extend the control to include nested ULs?  Can you e-mail it to jrhyne@tibbank.com?

# January 20, 2009 12:56 PM

toni said:

hi guys

i have the same problem, cose i´m asp.net noob.

how do i add this control to a aspx page?

i have 2 classes / menuitem + menu...

have to create other sitemenu.ascx file? or what?

thx in advance

# April 5, 2009 5:56 AM

MagicCleanerU said:

Good Site! <a href=http://magiccleanera.info>Magic Cleaner 4 You</a>

# June 24, 2009 4:29 PM

Glen M said:

To use this code, create the two .cs files in your App_code folder, and paste in the code. Then, in your aspx page, at the top, enter:

<%@ Register  Namespace="DavisTwelve.Web.Controls"  TagPrefix="uc5" %>

Then in the body of the page, you can put

<uc5:SimpleSiteMenu ID="mnSimple" runat="server" DataSourceID="SiteMapDataSource1" />

(Assuming your SiteMapaDataSource has an ID of SiteMapDataSource1)

# June 24, 2009 7:51 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)