Click here to Skip to main content
15,896,278 members
Articles / Web Development / ASP.NET

AMenu - A Simple .NET Vertical Menu

Rate me:
Please Sign up or sign in to vote.
4.88/5 (26 votes)
8 Oct 2009CPOL4 min read 64.1K   3.1K   100  
A CSS based .NET vertical menu control.
// MenuLink.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using System.IO;
using System.Drawing;
using System.Globalization;
using System.Security.Permissions;


namespace mtweb
{

//////////////////////////////////////////////////////////////////////////
// LinkButtonEx

public class LinkButtonEx : LinkButton
{
  // Fields
  private MenuLink ml = null;

  
  // Constructor
  public LinkButtonEx(MenuLink link) : base()
  {
    if (link == null) {
      string err = "LinkButtonEx: MenuLink is null.";
      throw new Exception(err);
    }
    this.ml = link;
  }

  
  ///////////////////////////////////////////////
  // Events

  protected override void OnInit(EventArgs e)
  {
    base.OnInit(e);

    // Parent must be MenuLink
    if (!(this.Parent is MenuLink)) {
      string err = "LinkButtonEx: parent is not MenuLink.";
      throw new Exception(err);
    }
  }

  
  ///////////////////////////////////////////////
  // Rendering

  public override void RenderBeginTag(HtmlTextWriter writer)
  {
    base.RenderBeginTag(writer);
  }


  // Note: don't call base, set attributes manually
  protected override void AddAttributesToRender(HtmlTextWriter writer)
  {
    // The menu
    AMenu menu = ml.GetMenu();
    if (menu == null) {
      string err = "LinkButtonEx: Menu is null.";
      throw new Exception(err);
    }

    // ID
    writer.AddAttribute("id", this.ClientID);
    
    // The HREF script, initally NOP
    string s = "javascript:void(0)";

    // HREF will be one of the following sctipts:
    //
    // [1] If this.PostBackUrl is empty:
    //
    // __doPostBack(eventTarget, eventArgument)
    //
    // [2] If this.PostBackUrl is not empty:
    //
    // WebForm_DoPostBackWithOptions(new
    //   WebForm_PostBackOptions(eventTarget, eventArgument,
    //                           validation, validationGroup,
    //                           actionUrl,
    //                           trackFocus, clientSubmit)
    // )

    if (ml.Enabled && (!string.IsNullOrEmpty(CommandName) ||
                       !string.IsNullOrEmpty(PostBackUrl)))
    {
      string url = AMenu.ToAbsolute(PostBackUrl);
      PostBackOptions opt = new PostBackOptions(this);
      opt.Argument = "";              // eventArgument
      opt.PerformValidation = false;  // validation
      opt.ValidationGroup = "";       // validationGroup
      opt.ActionUrl = url;            // actionUrl
      opt.TrackFocus = false;         // trackFocus
      opt.ClientSubmit = true;        // clientSubmit
      opt.AutoPostBack = false;
      opt.RequiresJavaScriptProtocol = true;

      s = Page.ClientScript.GetPostBackEventReference(opt);
    }

    // HREF
    writer.AddAttribute("href", s);

    // Client side click
    if (!string.IsNullOrEmpty(OnClientClick)) {
      writer.AddAttribute("onclick", OnClientClick);
    }

    // Icon image (as background)
    if (!string.IsNullOrEmpty(ml.IconImage)) {
      string url = "url(" + ml.IconImage + ")";
      writer.AddStyleAttribute("background-image", url);
    }

    // Foreground color
    if (!menu.ForeColor.IsEmpty) {
      string fore_color = ColorTranslator.ToHtml(menu.ForeColor);
      if (!AMenu.IsDefault(fore_color, AMenu.DEF_FORECOLOR)) {
        writer.AddStyleAttribute("color", fore_color);
      }
    }
    
    // Width
    if (!ml.Width_A.IsEmpty) {
      writer.AddStyleAttribute("width", ml.Width_A.ToString());
    }

    // No margin if top-menu has no border
    if (ml.IsTopLink && !menu.Border) {
      writer.AddStyleAttribute("margin", "0");
    }

  }


  public override void RenderEndTag(HtmlTextWriter writer)
  {
    if (ml.IsParent) writer.Write("<!--[if gte IE 7]><!-->");
    base.RenderEndTag(writer);
    if (ml.IsParent) writer.Write("<!--<![endif]-->");
  }

}  // class



//////////////////////////////////////////////////////////////////////////
// MenuLink

[ParseChildren(false)]
[PersistChildren(true)]
[DefaultProperty("Text")]
[DefaultEvent("Click")]
[ToolboxData("<{0}:MenuLink runat=server></{0}:MenuLink>")]
public class MenuLink : WebControl, INamingContainer
{
  // Fields
  private LinkButtonEx m_lb = null;               // our LinkButton
  private ArrayList m_Items = new ArrayList();    // child controls
  private event CommandEventHandler m_ItemClick;  // click handler
  

  // Constructor
  public MenuLink() : base(HtmlTextWriterTag.Li)
  {
  }


  ///////////////////////////////////////////////
  // Events

  protected override void OnInit(EventArgs e)
  {
    base.OnInit(e);

    // Parent must be AMenu or AMenuSub
    if (!(this.Parent is AMenu) && !(this.Parent is AMenuSub))
    {
      string err = "MenuLink: parent is not AMenu or AMenuSub.";
      throw new Exception(err);
    }

    // Create the LinkButton
    m_lb = new LinkButtonEx(this);
    m_lb.ID = "LB";
    m_lb.Text = this.Text;
    m_lb.CommandName = this.CommandName;
    m_lb.CommandArgument = this.CommandArgument;
    m_lb.PostBackUrl = this.PostBackUrl;
    m_lb.OnClientClick = this.OnClientClick;
    this.Controls.Add(m_lb);

    // Initialize:
    // - determine and save the IsParent property
    // - determine and save the IsTopLink property
    // - calculate and save the width of <A> elements
    //
    Initialize();
  }


  ///////////////////////////////////////////////
  // Properties

  // Hide inherited:

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override Unit Height
  {
    get { return Unit.Empty; }
    set {}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override Unit Width
  {
    get { return Unit.Empty; }
    set {}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override Color ForeColor
  {
    get { return Color.Empty; }
    set {}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override Color BackColor
  {
    get { return Color.Empty; }
    set {}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override Color BorderColor
  {
    get { return Color.Empty; }
    set {}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override Unit BorderWidth
  {
    get { return Unit.Empty; }
    set {}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override BorderStyle BorderStyle
  {
    get { return BorderStyle.NotSet;}
    set {}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override FontInfo Font
  {
    get { return null;}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override String SkinID
  {
    get { return string.Empty; }
    set {}
  }

  [Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public override Boolean EnableTheming
  {
    get { return false; }
    set {}
  }


  // MenuLink:

  // Child controls
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ArrayList Items
  {
    get { return m_Items; }
    set { m_Items = value;}
  }


  // Text
  [Category("Appearance")]
  [DefaultValue("")]
  [Bindable(true)]
  public string Text
  {
    get {
      String s = (String)ViewState["Text"];
      return (s == null) ? String.Empty : s;
    }
    set {
      ViewState["Text"] = value;
    }
  }


  // IconImage
  [Category("Appearance")]
  [UrlProperty()]
  [DefaultValue(AMenu.DEF_ICONIMAGE)]
  [Bindable(true)]
  public virtual string IconImage
  {
    get {
      String s = (String)ViewState["IconImage"];
      if (s == null) s = AMenu.DEF_ICONIMAGE;
      return AMenu.ToAbsolute(s);
    }
    set {
      ViewState["IconImage"] = value;
    }
  }


  // CommandName
  [Category("Behavior")]
  [DefaultValue("")]
  [Bindable(true)]
  public virtual string CommandName
  {
    get {
      String s = (String)ViewState["CommandName"];
      return (s == null) ? String.Empty : s;
    }
    set {
      ViewState["CommandName"] = value;
    }
  }


  // CommandArgument
  [Category("Behavior")]
  [DefaultValue("")]
  [Bindable(true)]
  public virtual string CommandArgument
  {
    get {
      String s = (String)ViewState["CommandArgument"];
      return (s == null) ? String.Empty : s;
    }
    set {
      ViewState["CommandArgument"] = value;
    }
  }


  // PostBackUrl
  [Category("Behavior")]
  [DefaultValue("")]
  [Bindable(true)]
  public virtual string PostBackUrl
  {
    get {
      String s = (String)ViewState["PostBackUrl"];
      return (s == null) ? String.Empty : s;
    }
    set {
      ViewState["PostBackUrl"] = value;
    }
  }


  // ToolTip
  [Category("Behavior")]
  [DefaultValue("")]
  [Bindable(true)]
  public override string ToolTip
  {
    get {
      String s = (String)ViewState["LinkToolTip"];
      return (s == null) ? String.Empty : s;
    }
    set {
      ViewState["LinkToolTip"] = value;
    }
  }


  // IsParent
  [ReadOnly(true)]
  [Browsable(false)]
  [DefaultValue(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public virtual bool IsParent
  {
    get {
      object o = ViewState["IsParent"];
      return (o == null) ? false : (bool)o;
    }
    set {
      ViewState["IsParent"] = value;
    }
  }


  // IsTopLink
  [ReadOnly(true)]
  [Browsable(false)]
  [DefaultValue(false)]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public virtual bool IsTopLink
  {
    get {
      object o = ViewState["IsTopLink"];
      return (o == null) ? false : (bool)o;
    }
    set {
      ViewState["IsTopLink"] = value;
    }
  }


  // Width_A
  [ReadOnly(true)]
  [Browsable(false)]
  [DefaultValue(typeof(Unit),"0")]
  [TypeConverter(typeof(UnitConverter))]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public virtual Unit Width_A
  {
    get {
      object o = ViewState["Width_A"];
      if (o != null && ((Unit)o) != Unit.Empty) return (Unit)o;
      return Unit.Empty;
    }
    set {
      ViewState["Width_A"] = value;
    }
  }


  // Click handler (server)
  [Category("Action")]
  public event CommandEventHandler Click
  {
    add { m_ItemClick += value; }
    remove { m_ItemClick -= value; }
  }


  // Click handler (client)
  [Category("Behavior")]
  public string OnClientClick
  {
    get {
      String s = (String)ViewState["OnClientClick"];
      return (s == null) ? String.Empty : s;
    }
    set {
      ViewState["OnClientClick"] = value;
    }
  }



  ///////////////////////////////////////////////
  // Rendering

  public override void RenderBeginTag(HtmlTextWriter writer)
  {
    base.RenderBeginTag(writer);
  }


  // Note: don't call base, set attributes manually
  protected override void AddAttributesToRender(HtmlTextWriter writer)
  {
    // The menu
    AMenu menu = GetMenu();
    if (menu == null) {
      string err = "MenuLink: Menu is null.";
      throw new Exception(err);
    }

    // ID
    writer.AddAttribute("id", this.ClientID);

    // ToolTip
    if (!string.IsNullOrEmpty(ToolTip)) {
      writer.AddAttribute("title", ToolTip);
    }

    // Border (left/right) color
    if (!menu.BorderColor.IsEmpty) {
      string borderc = ColorTranslator.ToHtml(menu.BorderColor);
      if (!AMenu.IsDefault(borderc, AMenu.DEF_BORDERCOLOR)) {
        writer.AddStyleAttribute("border-color", borderc);
      }
    }

    // Remove top-menu borders (left/right), if needed
    if (IsTopLink && !menu.Border) {
      writer.AddStyleAttribute("border", "none");
    }

    // Color codes
    string fc="", bc="", fh="", bh="";
    if (!menu.ForeColor.IsEmpty) {
      fc = "'" + ColorTranslator.ToHtml(menu.ForeColor) + "'";
    }
    if (!menu.BackColor.IsEmpty) {
      bc = "'" + ColorTranslator.ToHtml(menu.BackColor) + "'";
    }
    if (!menu.ForeHover.IsEmpty) {
      fh = "'" + ColorTranslator.ToHtml(menu.ForeHover) + "'";
    }
    if (!menu.BackHover.IsEmpty) {
      bh = "'" + ColorTranslator.ToHtml(menu.BackHover) + "'";
    }

    // Our LinkButton
    string id = "'" + m_lb.ClientID + "'";

    // Hover colors (set on the link element)
    if (this.Enabled)
    {
      // True if hover color has been set
      bool bHoverSet = false;

      // Set hover colors
      string s = "";
      if (!string.IsNullOrEmpty(fh) && !string.IsNullOrEmpty(fc)) {
        if (!AMenu.IsDefault(fh, AMenu.DEF_FOREHOVER)) {
          s += "getElementById(" + id + ").style.color=" + fh + ";";
        }
      }
      if (!string.IsNullOrEmpty(bh) && !string.IsNullOrEmpty(bc)) {
        if (!AMenu.IsDefault(bh, AMenu.DEF_BACKHOVER)) {
          s += "getElementById(" + id + ").style.backgroundColor=" + bh + ";";
        }
      }
      if (!string.IsNullOrEmpty(s)) {
        writer.AddAttribute("onmouseover", s);
        bHoverSet = true;
      }

      // Reset hover colors
      if (bHoverSet)
      {
        s = "";
        if (!string.IsNullOrEmpty(fc)) {
          s += "getElementById(" + id + ").style.color=" + fc + ";";
        }
        if (!string.IsNullOrEmpty(bc)) {
          s += "getElementById(" + id + ").style.backgroundColor=" + bc + ";";
        }
        if (!string.IsNullOrEmpty(s)) {
          writer.AddAttribute("onmouseout", s);
        }
      }
    }

    // Disabled - override CSS hover colors
    else
    {
      string s = "";
      if (!string.IsNullOrEmpty(fc)) {
        s += "getElementById(" + id + ").style.color=" + fc + ";";
      }
      if (!string.IsNullOrEmpty(bc)) {
        s += "getElementById(" + id + ").style.backgroundColor=" + bc + ";";
      }
      if (!string.IsNullOrEmpty(s)) {
        writer.AddAttribute("onmouseover", s);
      }
    }

  }


  public override void RenderEndTag(HtmlTextWriter writer)
  {
    base.RenderEndTag(writer);
  }


  ///////////////////////////////////////////////
  // Methods

  [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
  protected override void AddParsedSubObject(Object obj)
  {
    if (!Enabled) return;
    m_Items.Add(obj);
  }


  protected override void CreateChildControls()
  {    
    foreach (Control c in m_Items) {
      this.Controls.Add(c);
    }
  }


  // Return TRUE if event should not be bubbled
  protected override bool OnBubbleEvent(object sender, EventArgs e)
  {
    // If we are disabled, just ignore
    if (!Enabled) return true;

    // If not a command arg, or not from LinkButtonEx, pass on
    if (!(e is CommandEventArgs) || !(sender is LinkButtonEx))
    {
      return false;
    }

    // Call the handler if we got one
    if (m_ItemClick != null) {
      m_ItemClick(this, (CommandEventArgs)e);
      return true;
    }

    // Bubble to AMenu using 'this' as sender
    RaiseBubbleEvent(this, e);
    return true;
  }


  ///////////////////////////////////////////////
  // Utility

  //
  public AMenu GetMenu()
  {
    Control c = this;
    while (c != null)
    {
      c = c.Parent;
      if (c is AMenu) return (AMenu)c;
    }
    return null;  // error
  }


  //
  public AMenuSub GetSubMenu()
  {
    if (!(Parent is AMenuSub)) return null;
    return (AMenuSub)Parent;
  }


  //
  protected void Initialize()
  {
    // Determine if we are Parent
    foreach (Control c in m_Items) {
      if (c is AMenuSub) {IsParent = true; break;}
    }

    // Determine if we are a top-menu link
    IsTopLink = (Parent is AMenu);

    // Calculate and save layout
    SaveLayout();
  }

  
  // Calculate and save the width of <A> elements
  protected void SaveLayout()
  {
    // Local variables
    double width_li = 0;   // width of the LI element
    double width_a = 0;    // width of the A element
    double margin = 0;     // left and right margins
    double padding = 0;    // left padding (indent)
    double font_size = 0;  // font size

    // Get parent top-menu
    AMenu top = GetMenu();
    if (top == null) return;  // error

    // Current font size. Only PX accepted
    if (!top.FontSize.IsEmpty && top.FontSize.Type == UnitType.Pixel) {
      font_size = top.FontSize.Value;
    }
    else {
      font_size = (Unit.Parse(AMenu.DEF_FONTSIZE)).Value;
    }

    // [1] If we are a top-menu link:

    if (this.IsTopLink)
    {
      // If 'Width' is missing, just return
      if (top.Width.IsEmpty) return;

      // We only accept PX and EM. No euros here, pal.
      if (top.Width.Type != UnitType.Pixel &&
          top.Width.Type != UnitType.Em)
      {
        return;
      }

      // Width, margin and padding
      width_li = top.Width.Value;
      margin = top.Margin.Value;
      padding = top.Indent.Value;

      // Remove margin if top menu has no border
      if (!top.Border) margin = 0;

      // Convert margin/padding to EM, if needed
      if (top.Width.Type == UnitType.Em) {
        if (font_size == 0) font_size = 12;  // def
        margin = margin / font_size;
        padding = padding / font_size;
      }

      // Calculate width_a
      width_a = width_li - ((2 * margin) + padding);
      if (width_a <= 0) width_a = width_li;

      // Convert to Units and save
      if (top.Width.Type == UnitType.Em) {
        this.Width_A = Unit.Parse(width_a.ToString() + "em");
      }
      else {
        this.Width_A = Unit.Parse(width_a.ToString() + "px");
      }

      return;
    }

    // [2] We are a sub-menu link:

    // Get parent sub-menu
    AMenuSub sub = GetSubMenu();
    if (sub == null) return;  // error

    // If new 'Width' is not set, just return
    if (sub.Width.IsEmpty) return;

    // We only accept PX and EM
    if (sub.Width.Type != UnitType.Pixel &&
        sub.Width.Type != UnitType.Em)
    {
      return;
    }

    // Width, margin and padding
    width_li = sub.Width.Value;
    margin = sub.Margin.Value;
    padding = sub.Indent.Value;

    // Convert margin/padding to EM, if needed
    if (sub.Width.Type == UnitType.Em) {
      if (font_size == 0) font_size = 12;  // def
      margin = margin / font_size;
      padding = padding / font_size;
    }

    // Calculate width_a
    width_a = width_li - ((2 * margin) + padding);
    if (width_a <= 0) width_a = width_li;

    // Convert to Units and save
    if (sub.Width.Type == UnitType.Em) {
      this.Width_A = Unit.Parse(width_a.ToString() + "em");
    }
    else {
      this.Width_A = Unit.Parse(width_a.ToString() + "px");
    }

  }


}  // class
}  // namespace

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Europe Europe
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions