
Table tree data display
The table supports the display of tree data
Tips
Control whether it is tree data through the
IsTree
parameter- Tree-structure conversion of datasets through
TreeNodeConverter
- Set
TableTreeNode
itsIsExpand
parameter to control whether the current child node is expanded - When clicking the child item expansion arrow, the child item data is obtained through the
OnTreeExpand
callback delegate method - Maintain line state fallback mechanism,
ModelEqualityComparer
CustomKeyAttribute
IEqualityComparer<TItem>
Equals
overloaded method
Step 1: Set IsTree
to true
Step 2: Set Items
or OnQueryAsync
to get component data collection
Step 3: Set TreeNodeConverter
to convert the component data collection into a tree structure
Step 4: Set the OnTreeExpand
callback delegate to expand the response line to get the child item data set
tree data display
Open the tree table by setting IsTree
Demo
Loading...
@inject IStringLocalizer<Foo> LocalizerFoo
@inject ICacheManager CacheManager
<div>
<Table TItem="TreeFoo" IsBordered="true" IsStriped="true" HeaderStyle="@TableHeaderStyle.Light"
Items="@TreeItems" IsTree="true" TreeNodeConverter="@TreeNodeConverter" OnTreeExpand="@OnTreeExpand">
<TableColumns>
<TableColumn @bind-Field="@context.Name" Width="360" />
<TableColumn @bind-Field="@context.DateTime" Width="180" />
<TableColumn @bind-Field="@context.Address" />
<TableColumn @bind-Field="@context.Count" />
</TableColumns>
</Table>
</div>
@code {
[NotNull]
private List<TreeFoo>? TreeItems { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
// 模拟数据从数据库中获得
TreeItems = TreeFoo.GenerateFoos(LocalizerFoo, 3);
// 插入 Id 为 1 的子项
TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 2, 1, 100));
// 插入 Id 为 101 的子项
TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 3, 101, 1010));
}
private static Task<IEnumerable<TableTreeNode<TreeFoo>>> TreeNodeConverter(IEnumerable<TreeFoo> items)
{
// 构造树状数据结构
var ret = BuildTreeNodes(items, 0);
return Task.FromResult(ret);
IEnumerable<TableTreeNode<TreeFoo>> BuildTreeNodes(IEnumerable<TreeFoo> items, int parentId)
{
var ret = new List<TableTreeNode<TreeFoo>>();
ret.AddRange(items.Where(i => i.ParentId == parentId).Select((foo, index) => new TableTreeNode<TreeFoo>(foo)
{
// 此处为示例,假设偶行数据都有子数据
HasChildren = index % 2 == 0,
// 如果子项集合有值 则默认展开此节点
IsExpand = items.Any(i => i.ParentId == foo.Id),
// 获得子项集合
Items = BuildTreeNodes(items.Where(i => i.ParentId == foo.Id), foo.Id)
}));
return ret;
}
}
private Task<IEnumerable<TableTreeNode<TreeFoo>>> OnTreeExpand(TreeFoo foo) => CacheManager.GetOrCreateAsync($"{foo.Id}", async entry =>
{
// 模拟从数据库中查询
await Task.Delay(1000);
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
return TreeFoo.GenerateFoos(LocalizerFoo, 2, foo.Id, foo.Id * 100).Select(i => new TableTreeNode<TreeFoo>(i));
});
/// <summary>
/// Foo 类为Demo测试用,如有需要请自行下载源码查阅
/// Foo class is used for Demo test, please download the source code if necessary
/// https://gitee.com/LongbowEnterprise/BootstrapBlazor/blob/main/src/BootstrapBlazor.Shared/Data/Foo.cs
/// </summary>
private class TreeFoo : Foo
{
public int ParentId { get; set; }
/// <summary>
/// GenerateFoos
/// </summary>
/// <returns></returns>
public static List<TreeFoo> GenerateFoos(IStringLocalizer<Foo> localizer, int count = 80, int parentId = 0, int id = 0) => Enumerable.Range(1, count).Select(i => new TreeFoo()
{
Id = id + i,
ParentId = parentId,
Name = localizer["Foo.Name", $"{id + i:d4}"],
DateTime = System.DateTime.Now.AddDays(i - 1),
Address = localizer["Foo.Address", $"{Random.Next(1000, 2000)}"],
Count = Random.Next(1, 100),
Complete = Random.Next(1, 100) > 50,
Education = Random.Next(1, 100) > 50 ? EnumEducation.Primary : EnumEducation.Middle
}).ToList();
}
}
Hierarchical indentation
Control the indent width of each layer by setting IndentSize
.
Demo
Tips
The default level indent width is
16px
by setting Indent
to change the indent widthIn this example change the indent width to 8px
Loading...
@inject IStringLocalizer<Foo> LocalizerFoo
@inject ICacheManager CacheManager
<div>
<Table TItem="TreeFoo" IsBordered="true" IsStriped="true" HeaderStyle="@TableHeaderStyle.Light" IndentSize="8"
Items="@TreeItems" IsTree="true" TreeNodeConverter="@TreeNodeConverter" OnTreeExpand="@OnTreeExpand">
<TableColumns>
<TableColumn @bind-Field="@context.Name" Width="360" />
<TableColumn @bind-Field="@context.DateTime" Width="180" />
<TableColumn @bind-Field="@context.Address" />
<TableColumn @bind-Field="@context.Count" />
</TableColumns>
</Table>
</div>
@code {
[NotNull]
private List<TreeFoo>? TreeItems { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
// 模拟数据从数据库中获得
TreeItems = TreeFoo.GenerateFoos(LocalizerFoo, 3);
// 插入 Id 为 1 的子项
TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 2, 1, 100));
// 插入 Id 为 101 的子项
TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 3, 101, 1010));
}
private static Task<IEnumerable<TableTreeNode<TreeFoo>>> TreeNodeConverter(IEnumerable<TreeFoo> items)
{
// 构造树状数据结构
var ret = BuildTreeNodes(items, 0);
return Task.FromResult(ret);
IEnumerable<TableTreeNode<TreeFoo>> BuildTreeNodes(IEnumerable<TreeFoo> items, int parentId)
{
var ret = new List<TableTreeNode<TreeFoo>>();
ret.AddRange(items.Where(i => i.ParentId == parentId).Select((foo, index) => new TableTreeNode<TreeFoo>(foo)
{
// 此处为示例,假设偶行数据都有子数据
HasChildren = index % 2 == 0,
// 如果子项集合有值 则默认展开此节点
IsExpand = items.Any(i => i.ParentId == foo.Id),
// 获得子项集合
Items = BuildTreeNodes(items.Where(i => i.ParentId == foo.Id), foo.Id)
}));
return ret;
}
}
private Task<IEnumerable<TableTreeNode<TreeFoo>>> OnTreeExpand(TreeFoo foo) => CacheManager.GetOrCreateAsync($"{foo.Id}", async entry =>
{
// 模拟从数据库中查询
await Task.Delay(1000);
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
return TreeFoo.GenerateFoos(LocalizerFoo, 2, foo.Id, foo.Id * 100).Select(i => new TableTreeNode<TreeFoo>(i));
});
/// <summary>
/// Foo 类为Demo测试用,如有需要请自行下载源码查阅
/// Foo class is used for Demo test, please download the source code if necessary
/// https://gitee.com/LongbowEnterprise/BootstrapBlazor/blob/main/src/BootstrapBlazor.Shared/Data/Foo.cs
/// </summary>
private class TreeFoo : Foo
{
public int ParentId { get; set; }
/// <summary>
/// GenerateFoos
/// </summary>
/// <returns></returns>
public static List<TreeFoo> GenerateFoos(IStringLocalizer<Foo> localizer, int count = 80, int parentId = 0, int id = 0) => Enumerable.Range(1, count).Select(i => new TreeFoo()
{
Id = id + i,
ParentId = parentId,
Name = localizer["Foo.Name", $"{id + i:d4}"],
DateTime = System.DateTime.Now.AddDays(i - 1),
Address = localizer["Foo.Address", $"{Random.Next(1000, 2000)}"],
Count = Random.Next(1, 100),
Complete = Random.Next(1, 100) > 50,
Education = Random.Next(1, 100) > 50 ? EnumEducation.Primary : EnumEducation.Middle
}).ToList();
}
}
Tree data with single table maintenance
Realize simple addition, deletion, modification and checking functions.
Demo
@inject IStringLocalizer<Foo> LocalizerFoo
@inject ICacheManager CacheManager
<div>
<Table TItem="TreeFoo" IsBordered="true" HeaderStyle="@TableHeaderStyle.Light"
ShowToolbar="true" ShowExtendButtons="true" ShowSkeleton="true" IsStriped="true"
OnQueryAsync="@OnQueryAsync" OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync"
IsTree="true" TreeNodeConverter="@TreeNodeConverter" OnTreeExpand="@OnTreeExpand">
<TableColumns>
<TableColumn @bind-Field="@context.Id" />
<TableColumn @bind-Field="@context.Name" Width="360" />
<TableColumn @bind-Field="@context.DateTime" Width="180" />
<TableColumn @bind-Field="@context.Address" />
<TableColumn @bind-Field="@context.Count" />
</TableColumns>
</Table>
</div>
@code {
[NotNull]
private List<TreeFoo>? TreeItems { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
// 模拟数据从数据库中获得
TreeItems = TreeFoo.GenerateFoos(LocalizerFoo, 3);
// 插入 Id 为 1 的子项
TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 2, 1, 100));
// 插入 Id 为 101 的子项
TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 3, 101, 1010));
}
private static Task<IEnumerable<TableTreeNode<TreeFoo>>> TreeNodeConverter(IEnumerable<TreeFoo> items)
{
// 构造树状数据结构
var ret = BuildTreeNodes(items, 0);
return Task.FromResult(ret);
IEnumerable<TableTreeNode<TreeFoo>> BuildTreeNodes(IEnumerable<TreeFoo> items, int parentId)
{
var ret = new List<TableTreeNode<TreeFoo>>();
ret.AddRange(items.Where(i => i.ParentId == parentId).Select((foo, index) => new TableTreeNode<TreeFoo>(foo)
{
// 此处为示例,假设偶行数据都有子数据
HasChildren = index % 2 == 0,
// 如果子项集合有值 则默认展开此节点
IsExpand = items.Any(i => i.ParentId == foo.Id),
// 获得子项集合
Items = BuildTreeNodes(items.Where(i => i.ParentId == foo.Id), foo.Id)
}));
return ret;
}
}
private Task<IEnumerable<TableTreeNode<TreeFoo>>> OnTreeExpand(TreeFoo foo) => CacheManager.GetOrCreateAsync($"{foo.Id}", async entry =>
{
// 模拟从数据库中查询
await Task.Delay(1000);
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
return TreeFoo.GenerateFoos(LocalizerFoo, 2, foo.Id, foo.Id * 100).Select(i => new TableTreeNode<TreeFoo>(i));
});
private static Task<TreeFoo> OnAddAsync() => Task.FromResult(new TreeFoo() { DateTime = DateTime.Now });
private Task<bool> OnSaveAsync(TreeFoo item, ItemChangedType changedType)
{
if (changedType == ItemChangedType.Add)
{
item.Id = TreeItems.Max(i => i.Id) + 1;
TreeItems.Add(item);
}
else
{
var oldItem = TreeItems.FirstOrDefault(i => i.Id == item.Id);
if (oldItem != null)
{
oldItem.Name = item.Name;
oldItem.DateTime = item.DateTime;
oldItem.Address = item.Address;
oldItem.Count = item.Count;
}
}
return Task.FromResult(true);
}
private Task<bool> OnDeleteAsync(IEnumerable<TreeFoo> items)
{
TreeItems.RemoveAll(foo => items.Any(i => i.Id == foo.Id));
return Task.FromResult(true);
}
private Task<QueryData<TreeFoo>> OnQueryAsync(QueryPageOptions _)
{
var items = TreeFoo.GenerateFoos(LocalizerFoo, 4);
// 插入 Id 为 1 的子项
items.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 2, 1, 100));
// 插入 Id 为 101 的子项
items.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 3, 101, 1010));
var data = new QueryData<TreeFoo>()
{
Items = items
};
return Task.FromResult(data);
}
/// <summary>
/// Foo 类为Demo测试用,如有需要请自行下载源码查阅
/// Foo class is used for Demo test, please download the source code if necessary
/// https://gitee.com/LongbowEnterprise/BootstrapBlazor/blob/main/src/BootstrapBlazor.Shared/Data/Foo.cs
/// </summary>
private class TreeFoo : Foo
{
public int ParentId { get; set; }
/// <summary>
/// GenerateFoos
/// </summary>
/// <returns></returns>
public static List<TreeFoo> GenerateFoos(IStringLocalizer<Foo> localizer, int count = 80, int parentId = 0, int id = 0) => Enumerable.Range(1, count).Select(i => new TreeFoo()
{
Id = id + i,
ParentId = parentId,
Name = localizer["Foo.Name", $"{id + i:d4}"],
DateTime = System.DateTime.Now.AddDays(i - 1),
Address = localizer["Foo.Address", $"{Random.Next(1000, 2000)}"],
Count = Random.Next(1, 100),
Complete = Random.Next(1, 100) > 50,
Education = Random.Next(1, 100) > 50 ? EnumEducation.Primary : EnumEducation.Middle
}).ToList();
}
}
icon
Change the little arrow icon that indicates by setting TreeIcon
Demo
Tips
The default level indent width is
16px
by setting Indent
to change the indent widthIn this example change the indent width to 8px
Loading...
@inject IStringLocalizer<Foo> LocalizerFoo
@inject ICacheManager CacheManager
<div>
<Table TItem="TreeFoo" IsBordered="true" IsStriped="true" HeaderStyle="@TableHeaderStyle.Light" TreeIcon="fa-solid fa-circle-chevron-right"
Items="@TreeItems" IsTree="true" TreeNodeConverter="@TreeNodeConverter" OnTreeExpand="@OnTreeExpand">
<TableColumns>
<TableColumn @bind-Field="@context.Name" Width="360" />
<TableColumn @bind-Field="@context.DateTime" Width="180" />
<TableColumn @bind-Field="@context.Address" />
<TableColumn @bind-Field="@context.Count" />
</TableColumns>
</Table>
</div>
@code {
[NotNull]
private List<TreeFoo>? TreeItems { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
// 模拟数据从数据库中获得
TreeItems = TreeFoo.GenerateFoos(LocalizerFoo, 3);
// 插入 Id 为 1 的子项
TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 2, 1, 100));
// 插入 Id 为 101 的子项
TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 3, 101, 1010));
}
private static Task<IEnumerable<TableTreeNode<TreeFoo>>> TreeNodeConverter(IEnumerable<TreeFoo> items)
{
// 构造树状数据结构
var ret = BuildTreeNodes(items, 0);
return Task.FromResult(ret);
IEnumerable<TableTreeNode<TreeFoo>> BuildTreeNodes(IEnumerable<TreeFoo> items, int parentId)
{
var ret = new List<TableTreeNode<TreeFoo>>();
ret.AddRange(items.Where(i => i.ParentId == parentId).Select((foo, index) => new TableTreeNode<TreeFoo>(foo)
{
// 此处为示例,假设偶行数据都有子数据
HasChildren = index % 2 == 0,
// 如果子项集合有值 则默认展开此节点
IsExpand = items.Any(i => i.ParentId == foo.Id),
// 获得子项集合
Items = BuildTreeNodes(items.Where(i => i.ParentId == foo.Id), foo.Id)
}));
return ret;
}
}
private Task<IEnumerable<TableTreeNode<TreeFoo>>> OnTreeExpand(TreeFoo foo) => CacheManager.GetOrCreateAsync($"{foo.Id}", async entry =>
{
// 模拟从数据库中查询
await Task.Delay(1000);
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
return TreeFoo.GenerateFoos(LocalizerFoo, 2, foo.Id, foo.Id * 100).Select(i => new TableTreeNode<TreeFoo>(i));
});
/// <summary>
/// Foo 类为Demo测试用,如有需要请自行下载源码查阅
/// Foo class is used for Demo test, please download the source code if necessary
/// https://gitee.com/LongbowEnterprise/BootstrapBlazor/blob/main/src/BootstrapBlazor.Shared/Data/Foo.cs
/// </summary>
private class TreeFoo : Foo
{
public int ParentId { get; set; }
/// <summary>
/// GenerateFoos
/// </summary>
/// <returns></returns>
public static List<TreeFoo> GenerateFoos(IStringLocalizer<Foo> localizer, int count = 80, int parentId = 0, int id = 0) => Enumerable.Range(1, count).Select(i => new TreeFoo()
{
Id = id + i,
ParentId = parentId,
Name = localizer["Foo.Name", $"{id + i:d4}"],
DateTime = System.DateTime.Now.AddDays(i - 1),
Address = localizer["Foo.Address", $"{Random.Next(1000, 2000)}"],
Count = Random.Next(1, 100),
Complete = Random.Next(1, 100) > 50,
Education = Random.Next(1, 100) > 50 ? EnumEducation.Primary : EnumEducation.Middle
}).ToList();
}
}
B station related video link
交流群