Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Yet Another Menu Generator

0.00/5 (No votes)
5 Feb 2007 1  
A 2 tier menu generator that can be navigated forwards and/or backwards.
Sample image

Introduction

I have been thinking of writing an article for a long time now. Why? I've been feeling a bit guilty lately since I have used allot of excellent code from here.

My Menu Generator

So, with all the stuff I have been working on lately, I've decided to post something that I created a couple of years ago, a database generated menu for the intranet site at my company. The reason, might as well start small and post something that might be remotely useful for some of you reading this.

Why does my menu generator stand apart from others on the net? Not much, I'm sure it has been done before. But one reason I find it useful is the fact that it can be traversed both forwards and backwards while keeping a fixed 2 line format (as seen above).

One warning I'm going to make before I begin is that my code is probably sloppy and outdated, but who cares, lets pretend it's pseudo-code. :)

Menu Examples

Here are some examples of what the menu looks like when traversing it (attached picture doesn't really show this). Here is my default setting:

Internal | Applications - Sites | Parent
Applications | AMS - UMS - Time Tracker | Home

Clicking on "Home" brings you here:

Links | Production - Development - Documentation - Outside | Parent

Clicking on "Outside" results in the following:

Links | Production - Development - Documentation - Outside | Parent
Outside | HMK Related Links - Microgaming | Home

And finally clicking on "Microgaming" would show this:

Microgaming | Support Desk - Casper Center | Parent
Outside | HMK Related Links - Microgaming | Home

If you follow the steps I did, you can see how the menu is traversed and your previous selection is always shown so you can navigate backwards in case of error.

The Goods

How does it work? Basically the menu items are stored in a simple database table containing the required information. Here is how I created the tables:

USE [Utopia]
GO
/****** Object: Table [dbo].[MENU] Script Date: 02/05/2007 16:16:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[MENU](
[LEVELID] [int] IDENTITY(1,1) NOT NULL,
[LEVELCODE] [int] NOT NULL,
[DESCRIPTION] [varchar](25) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[LEVELWITHIN] [int] NOT NULL,
[URL] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[HINT] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[IMAGEURL] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[CREATIONDATE] [datetime] NOT NULL,
[USERTXN] [varchar](25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
CONSTRAINT [PK_MENU] PRIMARY KEY CLUSTERED 
(
[LEVELID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

for the main table and here is the following type table:

GO
/****** Object: Table [dbo].[MENUTYPE] Script Date: 02/05/2007 16:19:08 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[MENUTYPE](
[LEVELCODE] [int] IDENTITY(1,1) NOT NULL,
[DESCRIPTION] [varchar](25) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[CREATIONDATE] [datetime] NOT NULL,
[USERTXN] [varchar](25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
CONSTRAINT [PK_MENULEVEL] PRIMARY KEY CLUSTERED 
(
[LEVELCODE] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

The most important items here are the level id and the level within which define the navigation order. This would be similar to child and parent nodes in a tree.

Data

Nothing would stop you from reading your items from an XML file or web service by the way. It's all up to you. The presentation of the menu is done via a custom control (ascx file) with a simple literal web control:

litMenu.Text = GenerateMenu(myMenu); 

where the myMenu object is simply:

using System;

namespace SmartNav
{
/// <summary />

/// Summary description for MenuHelper.

/// </summary />

public class MenuHelper
{
protected int _Level;
protected int _Parent;
protected Boolean _Row1;
protected Boolean _Row2; 

/// <summary />

/// Gets or sets the level.

/// </summary />

/// <value />The level.</value />

public int Level
{
get { return _Level; }
set { _Level = value; }
}

/// <summary />

/// Gets or sets the parent.

/// </summary />

/// <value />The parent.</value />

public int Parent
{
get { return _Parent; }
set { _Parent = value; }
}

/// <summary />

/// Gets or sets a value indicating whether this <see cref="MenuHelper" /> is row1.

/// </summary />

/// <value /><c />true</c /> if row1; otherwise, <c />false</c />.</value />

public Boolean Row1
{
get { return _Row1; }
set { _Row1 = value; }
}

/// <summary />

/// Gets or sets a value indicating whether this <see cref="MenuHelper" /> is row2.

/// </summary />

/// <value /><c />true</c /> if row2; otherwise, <c />false</c />.</value />

public Boolean Row2
{
get { return _Row2; }
set { _Row2 = value; }
}
}
}

A small note I should mention is that before the GenerateMenu(MenuHelper inMenu) procedure is called, I have some code (rather ugly but functional, refer to the source code) that uses cookies to save the menu navigation and loads a default location if no cookie is present.

Like I said, it doesn't really matter where the data is stored, here is how I extract the data from the tables:

set ANSI_NULLS OFF
set QUOTED_IDENTIFIER OFF
GO
/* ReturnMenu
Returns menu row complete with menu header and parent id to navigate backwards.
*/

ALTER PROCEDURE [dbo].[ReturnMenu]
@inLoc int
AS
SET NOCOUNT ON;

select 
l.description as "Level",
l.hint as "Level Hint",
m.description as "Name",
m.url as "URL",
m.imageURL as "ImageURL",
m.hint as "Hint",
m.levelid as "ID",
l.levelid as "Top" 
from 
menu m,
menutype mt,
(select description, hint, levelid from menu where levelID = @inLoc) l
where
m.levelcode = mt.levelcode
and m.levelwithin = @inLoc

This returns all the rows in the menu and gets populated into several MenuRow objects. These rows are all part of the menu object which can be found in the SmartNav zip file.

Main Code

So here is the meat of the code, where the menu is actually created:

 /// <summary />

/// Generates the menu.

/// </summary />

/// The in menu.

/// <returns /></returns />

private string GenerateMenu(MenuHelper inMenu)
{
string menuStr; 
MenuRow headerRow = new MenuRow();
MenuRow navigationItem = new MenuRow();
SmartNav menu = new SmartNav();
SqlDataReader dr;
MenuRow menuRow;
ArrayList menuItems = new ArrayList();

menu.WriteStartMenu();

// menu is 2-tier, gotta do this twice

menu.WriteNewLine(); 

#region Fetch First Row from DB
...
#endregion Fetch First Row

menu.WriteHeaderItem(headerRow);
menu.WriteMenuItem(menuItems);

navigationItem.Top = (int)SqlHelper.ExecuteScalar(
System.Configuration.ConfigurationSettings.AppSettings["ConnectionStringUTOPIA"], 
CommandType.StoredProcedure, "ReturnParent", 
new SqlParameter("@inLoc", navigationItem.Top));

menu.WriteNavigationItem(navigationItem, 0, Request["s"]);
menu.WriteEndLine();

menuItems.Clear();

menu.WriteNewLine();

if (inMenu.Parent != 0) //to make sure menu has a second row

{
#region Fetch Second Row from DB
...
#endregion Fetch Second Row

menu.WriteHeaderItem(headerRow);
menu.WriteMenuItem(menuItems);

navigationItem.ID = (int)SqlHelper.ExecuteScalar(
System.Configuration.ConfigurationSettings.AppSettings["ConnectionStringUTOPIA"], 
CommandType.StoredProcedure, "ReturnTop");
menu.WriteNavigationItem(navigationItem, 1, Request["s"]);
}
else //menu does not have a second row

{
menu.WriteHeaderItem();
menu.WriteMenuItem();
menu.WriteNavigationItem();
}

menu.WriteEndLine();
menu.WriteEndMenu();

menuStr = menu.WriteMenu();
menuItems.Clear();
menu.Close();

return menuStr; 
}

And that's pretty much it. Code needs some heavy refactoring but in a nutshell, we can all pretend that it's pseudo-code and that it works.

Things To Consider

There are many modifications that could be made to this menu generator (outside of code re-work/refactoring) that I can think of off the top of my head:

  • C# 3.0's use of LINQ for accessing the data from various different types of sources.
  • using stylesheets or config files to alter the look and feel of the menu.
  • using a config file to make the menu a n-tier menu with a simple variable stating how many tiers to display dynamically.
  • using C# 2.0 generics to have more creative menu items instead of text.
  • using AJAX and eliminating frames (this is how it is implemented now).

Conclusion

I created this 2-tier menu because I wanted a menu that could shrink/grow in a defined space on top of my web application which could be navigated forwards/backwards in a logical way.

All the source code is included either in the article or in the attached file. Any comments will be appreciated.

History

  1. 2/5/2007: First draft.
  2. 2/6/2007: Formatting and "Menu Examples" section.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here