Reputation: 340
I start with a list Cats with this data:
Name______________ Parent__________Description_______________FilesCoded________CodingReferences
"Blue"____________ "1.Colors"______"whatever"________________10________________11
"Red"_____________ "1.Colors"______"this"____________________2_________________3
"LightBlue"_______ "Blue"__________"that"____________________3_________________4
"Square"__________ "3.Forms"_______""________________________3_________________6
"ClearBlue"_______ "LightBlue"_____""________________________0_________________0
I need to create this output:
1.Colors
____Blue
________LightBlue
____________ClearBlue
3.Forms
____Square
I use this code, which does the trick, but I have around 15 to 20 levels on the tree, which makes the code ugly and highly inefficient. Is there a way to make this:
Current Code:
@{
var Cats = AsList(App.Data["Categories"]).OrderBy(o => o.Parent).ThenBy(oo => oo.Name);
// List of objects with: Name, Parent, Description, and other fields.
var FirstLevelTree = Cats.Where(a => Char.IsNumber(a.Name[0])).Select(s => s.Name).Distinct();
}
@functions{
public CategoryInfo setCatInfo (string catName)
{
var Cats = AsList(App.Data["Categories"]);
var returnInfo = new CategoryInfo();
returnInfo.desc = "Error: empty string";
returnInfo.info = "Error: empty string";
var checkCat = Cats.Where(n => n.Name == catName);
if (checkCat.Count() != 1)
{
returnInfo.info = "Error: no such cat in list";
returnInfo.desc = "Error: no such cat in list";
} else {
var thisCat = checkCat.First();
returnInfo.info = thisCat.Name + "somethingelse";
returnInfo.desc = thisCat.Description + "alsosomethingelse";
}
return returnInfo;
}
public class CategoryInfo {
public string info {get;set;}
public string desc {get;set;}
}
}
<div id="myCats">
@foreach(var top in FirstLevelTree)
{
<p>@top</p>
var setlvlOne = Cats.Where(a => a.Parent == top);
foreach(var lvlOne in setlvlOne)
{
var getLvlOneInfo = setCatInfo(lvlOne.Name);
<p style="margin-left: 20px;">@Html.Raw(getLvlOneInfo.info)</p>
if (!String.IsNullOrEmpty(getLvlOneInfo.desc))
{
<p style="margin-left: 30px;">@Html.Raw(getLvlOneInfo.desc)</p>
}
var setlvlTwo = Cats.Where(b => b.Parent == lvlOne.Name);
foreach(var lvlTwo in setlvlTwo)
{
var getLvlTwoInfo = setCatInfo(lvlTwo.Name);
<p style="margin-left: 40px;">@Html.Raw(getLvlTwoInfo.info)</p>
if (!String.IsNullOrEmpty(getLvlTwoInfo.desc))
{
<p style="margin-left: 50px;">@Html.Raw(getLvlTwoInfo.desc)</p>
}
var setlvlThree = Cats.Where(c => c.Parent == lvlTwo.Name);
foreach(var lvlThree in setlvlThree)
{
var getLvlThreeInfo = setCatInfo(lvlThree.Name);
<p style="margin-left: 60px;">@Html.Raw(getLvlThreeInfo.info)</p>
if (!String.IsNullOrEmpty(getLvlThreeInfo.desc))
{
<p style="margin-left: 70px;">@Html.Raw(getLvlThreeInfo.desc)</p>
}
var setlvlFour = Cats.Where(d => d.Parent == lvlThree.Name);
foreach(var lvlFour in setlvlFour)
{
var getLvlFourInfo = setCatInfo(lvlFour.Name);
<p style="margin-left: 80px;">@Html.Raw(getLvlFourInfo.info)</p>
if (!String.IsNullOrEmpty(getLvlFourInfo.desc))
{
<p style="margin-left: 90px;">@Html.Raw(getLvlFourInfo.desc)</p>
}
var setlvlFive = Cats.Where(e => e.Parent == lvlFour.Name);
foreach(var lvlFive in setlvlFive)
{
var getLvlFiveInfo = setCatInfo(lvlFive.Name);
<p style="margin-left: 100px;">@Html.Raw(getLvlFiveInfo.info)</p>
if (!String.IsNullOrEmpty(getLvlFiveInfo.desc))
{
<p style="margin-left: 110px;">@Html.Raw(getLvlFiveInfo.desc)</p>
}
}
}
}
}
}
}
</div>
Upvotes: 0
Views: 347
Reputation: 340
@inherits ToSic.Sxc.Dnn.RazorComponent
@{
var Cats = AsList(App.Data["Categories"]).OrderBy(o => o.Parent).ThenBy(oo => oo.Name);
// List of objects with: Parent, Name, Description, FilesCoded, CodingReferences
var CatsGrouped = Cats.ToLookup(a => a.Parent);
var FirstLevelTree = Cats.Where(a => Char.IsNumber(a.Name[0])).Select(s => s.Name).Distinct();
}
<style>
.setmargins{
padding-left: 2em;
}
</style>
@helper getChildren(string CatName, dynamic CatsGrouped)
{
var setChilds = CatsGrouped[CatName];
<div class="setmargins">
@foreach(var child in setChilds)
{
<div class="toogle">
@child.Name (a: @child.FilesCoded urs: @child.CodingReferences)
<div class="toogletarget setmargins">@(new HtmlString(child.Description))</div>
</div>
@getChildren(child.Name, CatsGrouped)
}
</div>
}
<div id="myCats" @Edit.TagToolbar(Content)>
@foreach(var top in FirstLevelTree)
{
<div>@top</div>
@getChildren(top, CatsGrouped)
}
</div>
<script>
$( ".toogletarget" ).toggle();
$( ".toogle" ).click(function() {
$(this).find('.toogletarget').toggle();
});
</script>
Upvotes: 0
Reputation: 156534
Simpler
Yes. Create a Razor Helper that calls itself recursively. That should allow you to avoid all the duplicative code in your example, and it'll support a basically infinite maximum depth without requiring any more code changes.
Faster
It depends on what's taking all the time.
As you get more levels, presumably you're just generating a lot more HTML. That'll take more time server-side, and it'll also bog down the browser. To avoid that kind of problem, you may need to look at only loading in the parts that the user is actually interested in. For example, you can create a collapsed structure that only loads in data from deeper nodes as users drill down into it.
I'd also pay close attention to what may be happening in the code you haven't provided.
setCatInfo
an expensive operation?Cats
collection? If it's using deferred execution, like an Entity Framework DbSet, you may be doing a separate round-trip each time you iterate over the results of a Cats.Where(...)
call.Either way, executing Cats.Where()
all over the place is giving your page generation an O(n²)
algorithmic complexity. I'd suggest collecting all the categories into a lookup, grouped by their Parent:
var catsByParent = Cats.ToLookup(c => c.Parent);
This is a one-time O(log n)
operation, and ever after that point you should be able to get the categories with a given parent much more quickly.
var thisLevel = catsByParent[parentLevel.Name];
@Html.Raw()
is a big code smell. Most likely you just want @thisLevel.desc
. If @thisLevel.desc
is guaranteed to be safe for injecting directly into your HTML, it should be an IHtmlString
.Upvotes: 3