I have a List that looks like:
public class CategoryViewModel
{
public int Id { get; set; }
public string? Name { get; set; }
public int? ParentCategoryId { get; set; }
}
I'm using the TreeItemData class from the Mudblazor site Mudblazor.
public class TreeItemData
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Text { get; set; }
public string Icon { get; set; }
public bool IsExpanded { get; set; } = true;
public bool HasChild => TreeItems != null && TreeItems.Count > 0;
public HashSet<TreeItemData> TreeItems { get; set; } = new HashSet<TreeItemData>();
public TreeItemData(string text, string icon)
{
Text = text;
Icon = icon;
}
}
I am struggling with how to iterate over my populated List and populate Hashset<TreeItemData> TreeItems
. The Mudblazor Treeview requires a Hashset.
My attempt, BuildTreeItems
is not working:
private void BuildTreeItems(int? ParentId = null)
{
if (Categories != null)
{
foreach (CategoryViewModel item in Categories.Where(i => i.ParentCategoryId == ParentId))
{
if (!Categories.Where(c => c.ParentCategoryId == item.Id).Any())
{// no children
TreeItems.Add(new TreeItemData(item.Name ?? string.Empty, Icons.Custom.FileFormats.FileDocument));
BuildTreeItems(item.ParentCategoryId);
}
else
{// has children
TreeItems.Add(new TreeItemData(item.Name, Icons.Custom.FileFormats.FileDocument)
{
TreeItems = new HashSet<TreeItemData>()
{
}
});
BuildTreeItems(item.Id);
}
}
}
}
I have actually tried various alternatives to this code, and I got close with using Blazor nested components, but the Mudblazor Treeview does not work well with when using nested components. I think the best option for me would be try to get the MudTreeView all in 1 component, and just figure out how to use recursion to convert my List to a Hashset suitable for the Mudblazor Treeview.
I'm not sure what I am missing. I'm trying to mimic the CustomTree example from the website: Mudblazor Treeview
It looks something like this:
protected override void OnInitialized()
{
TreeItems.Add(new TreeItemData(".azure", Icons.Custom.Brands.MicrosoftAzure));
TreeItems.Add(new TreeItemData(".github", Icons.Custom.Brands.GitHub));
TreeItems.Add(new TreeItemData(".vscode", Icons.Custom.Brands.MicrosoftVisualStudio));
TreeItems.Add(new TreeItemData("content", Icons.Custom.FileFormats.FileDocument));
TreeItems.Add(new TreeItemData("src", Icons.Custom.FileFormats.FileCode)
{
TreeItems = new HashSet<TreeItemData>()
{
new TreeItemData("MudBlazor", Icons.Custom.Brands.MudBlazor),
new TreeItemData("MudBlazor.Docs", Icons.Custom.FileFormats.FileDocument)
{
TreeItems = new HashSet<TreeItemData>()
{
new TreeItemData("_Imports.razor", Icons.Material.Filled.AlternateEmail),
new TreeItemData( "compilerconfig.json", Icons.Custom.FileFormats.FileImage),
new TreeItemData( "MudBlazor.Docs.csproj", Icons.Custom.Brands.MicrosoftVisualStudio),
new TreeItemData("NewFilesToBuild.txt" , Icons.Custom.FileFormats.FileDocument),
}
},
new TreeItemData("MudBlazor.Docs.Client", Icons.Material.Filled.Folder),
new TreeItemData("MudBlazor.Docs.Compiler", Icons.Material.Filled.Folder),
new TreeItemData("MudBlazor.Docs.Server", Icons.Material.Filled.Folder),
new TreeItemData("MudBlazor.UnitTests", Icons.Material.Filled.Folder),
new TreeItemData("MudBlazor.UnitTests.Viewer", Icons.Material.Filled.Folder),
new TreeItemData(".editorconfig", Icons.Custom.FileFormats.FileCode),
new TreeItemData("MudBlazor.sln", Icons.Custom.Brands.MicrosoftVisualStudio)
}
});
TreeItems.Add(new TreeItemData("History", Icons.Material.Filled.Folder));
}
I need to do this dynamically with an unknown number of categories and unknown number of levels.
What am I missing? Any help would be appreciated.
Sample Sample Data and what my treeview would look like.
Id | Name | ParentCategoryId
1 | Pictures | NULL
2 | Audio | NULL
3 | Animals | 1
4 | Dogs | 3
5 | Ring Tones | 2
8 | Cats | 3
9 | Photographs | 1
Pictures
Animals
Dogs
Cats
Photographs
Audio
Ring Tones
Showing the Solution
private void BuildTree()
{
if (Categories != null)
{
TreeItems = new HashSet<TreeItemData>();
var subItems = Categories.Where(item => item.ParentCategoryId == null);
foreach (var item in subItems)
{
BuildTreeItem(item, null);
}
}
}
private void BuildTreeItem(CategoryViewModel categoryItem, TreeItemData? data)
{
if (Categories != null)
{
var treeItem = new TreeItemData(categoryItem.Name ?? string.Empty, Icons.Material.Filled.AccountCircle);
var subItems = Categories.Where(item => item.ParentCategoryId == categoryItem.Id);
foreach (var subItem in subItems)
{
BuildTreeItem(subItem, treeItem);
}
if (data is null)
TreeItems.Add(treeItem);
else
data.TreeItems.Add(treeItem);
}
}
Here are two approaches.
RenderFragments
to build out the MudTreeViewItem
component structure.HashSet
.I answered another similar question recently so I've lifted some code from that answer and abreviated your data objects to simplify the code.
A simple readonly data object and a data provider.
public record Taxonomy(int Id, string Name, int? ParentId);
public static class DataProvider
{
private static List<Taxonomy>? _taxonomies;
public static IEnumerable<Taxonomy> Items
{
get
{
if (_taxonomies == null)
Populate();
return _taxonomies?.AsEnumerable() ?? Enumerable.Empty<Taxonomy>();
}
}
private static void Populate()
{
_taxonomies = new List<Taxonomy>();
_taxonomies.Add(new(1, "World", 0));
_taxonomies.Add(new(2, "Europe", 1));
_taxonomies.Add(new(3, "Asia", 1));
_taxonomies.Add(new(4, "Africa", 1));
_taxonomies.Add(new(5, "France", 2));
_taxonomies.Add(new(6, "Spain", 2));
_taxonomies.Add(new(7, "Portugal", 2));
_taxonomies.Add(new(8, "Cordoba", 6));
_taxonomies.Add(new(9, "Algeciras", 6));
}
}
The RenderFragment
version.
@page "/"
@using SO77657548.Data
<PageTitle>Index</PageTitle>
<MudText Typo="Typo.h3" GutterBottom="true">Hello, world!</MudText>
<MudText Class="mb-8">Welcome to your new app, powered by MudBlazor!</MudText>
<div>
@BuildTreeFragment
</div>
@code {
private IEnumerable<Taxonomy> _items = DataProvider.Items;
private RenderFragment BuildTreeFragment => __builder =>
{
var subItems = _items.Where(item => item.ParentId == 0);
<MudTreeView T="string">
@foreach (var item in subItems)
{
@BuildTreeItemFragment(item)
}
</MudTreeView>
};
private RenderFragment<Taxonomy> BuildTreeItemFragment => taxonomy => __builder =>
{
var items = _items.Where(item => item.ParentId == taxonomy.Id);
<MudTreeViewItem Value="@(taxonomy.Name)">
@foreach (var item in items)
{
@BuildTreeItemFragment(item)
}
</MudTreeViewItem>
};
}
The HashSet
version:
@page "/"
@using SO77657548.Data
<PageTitle>Index</PageTitle>
<MudText Typo="Typo.h3" GutterBottom="true">Hello, world!</MudText>
<MudText Class="mb-8">Welcome to your new app, powered by MudBlazor!</MudText>
<MudPaper Width="300px" Elevation="0">
<MudTreeView ServerData="LoadServerData" Items="_treeItems">
<ItemTemplate>
<MudTreeViewItem Value="@context" Icon="@context.Icon" LoadingIconColor="Color.Info" CanExpand="@context.CanExpand" Text="@context.Title" EndText="@context.Number?.ToString()" EndTextTypo="@Typo.caption" />
</ItemTemplate>
</MudTreeView>
</MudPaper>
@code {
private IEnumerable<Taxonomy> _items = DataProvider.Items;
private HashSet<TreeItemData> _treeItems { get; set; } = new HashSet<TreeItemData>();
protected override void OnInitialized()
{
BuildTree();
}
// Not sure what this is supposed to do - I'm not too familiar with MudBlazor
public async Task<HashSet<TreeItemData>> LoadServerData(TreeItemData parentNode)
{
await Task.Yield();
return parentNode.TreeItems;
}
private void BuildTree()
{
_treeItems = new HashSet<TreeItemData>();
var subItems = _items.Where(item => item.ParentId == 0);
foreach (var item in subItems)
{
BuildTreeItem(item, null);
}
}
private void BuildTreeItem(Taxonomy taxonomyItem, TreeItemData? data)
{
var treeItem = new TreeItemData(taxonomyItem.Name, Icons.Material.Filled.AccountCircle);
var subItems = _items.Where(item => item.ParentId == taxonomyItem.Id);
@foreach (var subItem in subItems)
{
BuildTreeItem(subItem, treeItem);
}
if (data is null)
_treeItems.Add(treeItem);
else
data.TreeItems.Add(treeItem);
}
}