Recursive menu builder with C #
Trying to create a recursive html menu using C #. I need html structure:
<ul>
<li>Office</li>
<li>Home
<ul>
<li>Beds</li>
<li>Desks</li>
</ul>
</li>
<li>Outdoor
<ul>
<li>Children
<ul>
<li>Playsets</li>
</ul>
</li>
</ul>
</li>
</ul>
The structure can obviously change as it is dynamic. At the moment I am using the HtmlGeneric controls i.e. Ul, li and adding controls, but not sure how to turn this into an efficient recursive function.
source to share
I'm not sure if you are a structure for storing a hierarchy of rows, but let's assume you have a way to get the child rows of each row as needed (for example, you can get "Beds" and "Tables" from "Home").
First, I declare tags as constants:
public const string OPEN_LIST_TAG = "<ul>";
public const string CLOSE_LIST_TAG = "</ul>";
public const string OPEN_LIST_ITEM_TAG = "<li>";
public const string CLOSE_LIST_ITEM_TAG = "</li>";
Then I would create a recursive method using something like a string builder:
/// <summary>
/// Adds another level of HTML list and list items to a string
/// </summary>
/// <param name="str">The string to add</param>
/// <param name="liStrings">The list of strings at this level to add</param>
/// <param name="iTabIndex">The current number of tabs indented from the left</param>
public void GenerateHTML( System.Text.StringBuilder str, List<string> liStrings, int iTabIndex) {
//add tabs to start of string
this.AddTabs(str, iTabIndex);
//append opening list tag
str.AppendLine(OPEN_LIST_TAG);
foreach (string strParent in liStrings) {
//add tabs for list item
this.AddTabs(str, iTabIndex + 1);
//if there are child strings for this string then loop through them recursively
if (this.GetChildStrings(strParent).Count > 0) {
str.AppendLine(OPEN_LIST_ITEM_TAG + strParent);
GenerateHTML(str, this.GetChildStrings(strParent), iTabIndex + 2);
//add tabs for closing list item tag
this.AddTabs(str, iTabIndex + 1);
str.AppendLine(CLOSE_LIST_ITEM_TAG);
}
else {
//append opening and closing list item tags
str.AppendLine(OPEN_LIST_ITEM_TAG + strParent + CLOSE_LIST_ITEM_TAG);
}
}
//add tabs for closing list tag
this.AddTabs(str, iTabIndex);
//append closing list tag
str.AppendLine(CLOSE_LIST_TAG);
}
Separate the tab by adding in a separate method:
/// <summary>
/// Appends a number of tabs to the string builder
/// </summary>
/// <param name="str">The string builder to append to</param>
/// <param name="iTabIndex">The number of tabs to append to</param>
public void AddTabs(System.Text.StringBuilder str, int iTabIndex) {
for (int i = 0; i <= iTabIndex; i++) {
str.Append("\t");
}
}
Then just call GenerateHTML with a new row builder, first row level and tab index set to 0, and it should give you what you want. I have not included the functions to get the child rows as I was not sure what structure you were using to do this - let me know and I can adapt my solution.
Hope this helps, Dane.
source to share
A bit outdated topic, but should have the correct answer nonetheless
In my example, the inbound nodes contain a Children property that contains children.
private HtmlGenericControl RenderMenu(Nodes nodes)
{
if (nodes == null)
return null;
var ul = new HtmlGenericControl("ul");
foreach (Node node in nodes)
{
var li = new HtmlGenericControl("li");
li.InnerText = node.Name;
if(node.Children != null)
{
li.Controls.Add(RenderMenu(node.Children));
}
ul.Controls.Add(li);
}
return ul;
}
source to share
The only thing that recursively refers to a list like this is the srtructure of the data. NGenerics is a good open source library of data structures.
Also I would recommend using HTMLTextWriter Class in this problem.
If you want to take a "roll-your-own" approach and create a server control then something like the class below will work.
public class MenuTree : Control
{
public string MenuText {get; set;}
public List<MenuTree> Children {get; set;}
public override void Render(HTMLTextWriter writer)
{
writer.RenderBeginTag(HtmlTextWriterTag.Ul);
writer.RenderBeginTag(HtmlTextWriterTag.Li);
writer.RenderBeginTag(MenuText);
writer.RenderEndTag();
foreach (MenuTree m in Children)
{
m.Render();
}
writer.RenderEndTag();
}
}
source to share