Reputation: 501
I have hit a slightly complicated problem that I am finding it hard to wrap my head around.
I have a class:
public class Location
{
public string Name { get; set; }
public List<Location> ChildLocations { get; set; }
}
It again has reference to Child Location objects. So this is a recursive List and I want to output this list in a nicely formatted HTML table. This is the code:
using System.Collections.Generic;
using System.Web.Mvc;
namespace WebApplication1.Controllers
{
public class Location
{
public string Name { get; set; }
public List<Location> ChildLocations { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
List<Location> locations = new List<Location>() {
new Location() {
Name = "Item 1",
ChildLocations = new List<Location>() {
new Location()
{
Name="Female",
ChildLocations = new List<Location>()
{
new Location() { Name = "Female 1" },
new Location() { Name = "Female 2" }
}
},
new Location()
{
Name="Male",
ChildLocations = new List<Location>()
{
new Location() { Name = "Male 1" },
new Location() { Name = "Male 2" }
}
}
}
},
new Location() {
Name = "Item 2",
ChildLocations = new List<Location>() {
new Location()
{
Name="Female",
ChildLocations = new List<Location>()
{
new Location() { Name = "M1" },
new Location() { Name = "M2" },
new Location() { Name = "M3" }
}
},
new Location()
{
Name="Male",
ChildLocations = new List<Location>()
},
new Location()
{
Name="Unknown",
ChildLocations = new List<Location>()
}
}
}
};
return View(locations);
}
}
}
and I want to make the output look something like this:
<table class="table table-border">
<tr>
<td rowspan="4">
Item 1
</td>
<td rowspan="2">
Female
</td>
<td>
Female 1
</td>
</tr>
<tr>
<td>Female 2</td>
</tr>
<tr>
<td rowspan="2">Male</td>
<td>Male 1</td>
</tr>
<tr>
<td>Male 2</td>
</tr>
<tr>
<td rowspan="5">
Item 2
</td>
<td rowspan="3">Female</td>
<td>M1</td>
</tr>
<tr>
<td>M2</td>
</tr>
<tr>
<td>
M3
</td>
</tr>
<tr>
<td>Male</td>
</tr>
<tr>
<td>Unknown</td>
</tr>
</table>
and if somebody wants to view the HTML I have the jsfiddle for it:
https://jsfiddle.net/4pk0necp/
I want to render this type of table using the data that I have pro grammatically in my cshtml:
@using WebApplication1.Controllers;
@model List<Location>
@{
ViewBag.Title = "Home Page";
}
I understand that a function to calculate the rowspan would be needed so I created this tiny function which correctly returns the depth:
public static int GetDepth(this Location location)
{
int noOfchildren = 0;
bool counted = false;
foreach (Location item in location.ChildLocations)
{
if (item.ChildLocations.Count <= 0)
{
if (!counted)
{
noOfchildren += location.ChildLocations.Where(i => i.ChildLocations.Count <= 0).Count();
counted = true;
}
}
else
noOfchildren += GetDepth(item);
}
return noOfchildren;
}
The give list of Tree about is just a sample and there can be many levels of depth in the tree. Any help is appreciated.
Edit: I tweaked my GetDepth function since we only need leaf level nodes count.
Upvotes: 1
Views: 81
Reputation: 30082
Try using the following class. Only barely tested:
public class TreeDrawer {
private readonly Dictionary<Location, int> _depthMap;
public TreeDrawer() {
_depthMap = new Dictionary<Location, int>();
}
public string Draw(IEnumerable<Location> locations) {
var sb = new StringBuilder("<table>");
bool first = true;
foreach (var l in locations) {
Draw(l, sb, true, first);
first = false;
}
sb.Append("</table>");
return sb.ToString();
}
private void Draw(Location l, StringBuilder sb, bool fromRoot, bool first) {
int depth = GetDepth(l);
bool openedRow = false;
if (fromRoot || !first) {
sb.Append("<tr>");
openedRow = true;
}
sb.Append("<td");
if (depth > 1) {
sb.Append(" rowspan=\"");
sb.Append(depth);
sb.Append("\"");
}
sb.Append(">");
sb.Append(l.Name);
sb.Append("</td>");
bool isFirstChild = true;
if (l.ChildLocations != null) {
foreach (var child in l.ChildLocations) {
Draw(child, sb, false, isFirstChild);
isFirstChild = false;
}
}
if (openedRow) {
sb.Append("</tr>");
}
}
private int GetDepth(Location l) {
if (!_depthMap.ContainsKey(l)) {
_depthMap.Add(l, Math.Max(1, l.ChildLocations?.Sum(GetDepth) ?? 0));
}
return _depthMap[l];
}
}
Upvotes: 2