
Table Search
Commonly used for single table maintenance, simple addition, deletion, modification, search, sorting, filtering, search and other common functions can be realized through attribute configuration, and very complex can be realized through the advanced usage of Template
business needs functions
Tips
The search function of the Table
component provides two modes, which can be set through SearchMode
. The default is Popup
, which is the popup mode. In the Top mode, the search bar will be built into the top of the table. In this mode, the Refresh button and the Search button are combined into one, you can set the ShowSearchButton=false
close the search button
Set ShowSearch
to display the query component, customize the search UI by setting the SearchTemplate
template
Demo
- Enable no data display function by setting
ShowEmpty="true"
- The
EmptyText
parameter is used to set the text to be displayed when there is no data, the default is to take the built-in text in the resource file EmptyTemplate
parameter is used to customize no data display template
@inject IStringLocalizer<TablesSearchTable> Localizer
@inject IStringLocalizer<Foo> FooLocalizer
<div>
<Table TItem="Foo"
IsPagination="true" PageItemsSource="@PageItemsSource"
IsStriped="true" IsBordered="true"
ShowToolbar="true" ShowSearch="true" IsMultipleSelect="true" ShowExtendButtons="true"
AddModalTitle="@Localizer["AddModelTitle"]" EditModalTitle="@Localizer["EditModelTitle"]"
SearchModel="@SearchModel" ShowEmpty="true" SearchMode="SearchMode.Top"
OnQueryAsync="@OnSearchModelQueryAsync" OnResetSearchAsync="@OnResetSearchAsync"
OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync">
<TableColumns>
<TableColumn @bind-Field="@context.DateTime" Width="180" />
<TableColumn @bind-Field="@context.Name" />
<TableColumn @bind-Field="@context.Address" />
<TableColumn @bind-Field="@context.Education" />
</TableColumns>
<SearchTemplate>
<GroupBox Title="@Localizer["SearchTableGroupBoxText"]">
<div class="row g-3 form-inline">
<div class="col-12 col-sm-6">
<BootstrapInput @bind-Value="@context.Name" PlaceHolder="@Localizer["NamePlaceholder"]" maxlength="50" ShowLabel="true" DisplayText="@FooLocalizer[nameof(context.Name)]" />
</div>
<div class="col-12 col-sm-6">
<BootstrapInput @bind-Value="@context.Address" PlaceHolder="@Localizer["AddressPlaceholder"]" maxlength="500" ShowLabel="true" DisplayText="@FooLocalizer[nameof(context.Address)]" />
</div>
</div>
</GroupBox>
</SearchTemplate>
</Table>
</div>
@code {
/// <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>
[NotNull]
private List<Foo>? Items { get; set; }
private Foo SearchModel { get; set; } = new Foo();
private static IEnumerable<int> PageItemsSource => new int[] { 4, 10, 20 };
private IEnumerable<SelectedItem>? SearchItems { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
Items = Foo.GenerateFoo(FooLocalizer);
SearchItems = new List<SelectedItem>()
{
new SelectedItem { Text = Localizer["SelectedItemText"].Value, Value = "" },
new SelectedItem { Text = Localizer["SelectedItemText1"].Value, Value = Localizer["SelectedItemValue1"].Value },
new SelectedItem { Text = Localizer["SelectedItemText2"].Value, Value = Localizer["SelectedItemValue2"].Value },
};
}
private static Task<Foo> OnAddAsync() => Task.FromResult(new Foo() { DateTime = DateTime.Now });
private Task<bool> OnSaveAsync(Foo item, ItemChangedType changedType)
{
// 增加数据演示代码
if (changedType == ItemChangedType.Add)
{
item.Id = Items.Max(i => i.Id) + 1;
Items.Add(item);
}
else
{
var oldItem = Items.FirstOrDefault(i => i.Id == item.Id);
if (oldItem != null)
{
oldItem.Name = item.Name;
oldItem.Address = item.Address;
oldItem.DateTime = item.DateTime;
oldItem.Count = item.Count;
oldItem.Complete = item.Complete;
oldItem.Education = item.Education;
}
}
return Task.FromResult(true);
}
private Task<bool> OnDeleteAsync(IEnumerable<Foo> items)
{
items.ToList().ForEach(i => Items.Remove(i));
return Task.FromResult(true);
}
private static Task OnResetSearchAsync(Foo item)
{
item.Name = "";
item.Address = "";
return Task.CompletedTask;
}
private Task<QueryData<Foo>> OnSearchModelQueryAsync(QueryPageOptions options)
{
// 自定义了 SearchModel
IEnumerable<Foo> items = Items;
if (!string.IsNullOrEmpty(options.SearchText))
{
items = items.Where(i => (i.Name?.Contains(options.SearchText, StringComparison.OrdinalIgnoreCase) ?? false)
|| (i.Address?.Contains(options.SearchText, StringComparison.OrdinalIgnoreCase) ?? false));
}
else if (!string.IsNullOrEmpty(SearchModel.Name))
{
items = items.Where(i => i.Name == SearchModel.Name);
}
else if (!string.IsNullOrEmpty(SearchModel.Address))
{
items = items.Where(i => i.Address == SearchModel.Address);
}
// 设置记录总数
var total = items.Count();
// 内存分页
items = items.Skip((options.PageIndex - 1) * options.PageItems).Take(options.PageItems).ToList();
return Task.FromResult(new QueryData<Foo>()
{
Items = items,
TotalCount = total,
IsSorted = true,
IsFiltered = true,
IsSearch = true,
IsAdvanceSearch = true
});
}
}
When ShowSearch
is set, if SearchTemplate
is not set when editing template, the component will try to automatically generate search criteria UI
Demo
When the column information is bound, set the Searchable
property and set the search criteria to automatically build the UI. You can set the SearchDialogShowMaximizeButton
to maximize the search pop-up window display Maximize button
Tips
When building a search popup window automatically, since each column is set to Searchable
, the component will pass SearchText
and set Searchable
Each column whose value is true
automatically builds a search lambda expression, which is obtained through the attribute Searchs
of QueryPageOptions
if (options.Searchs.Any())
{
//Logical relationship usage FilterLogic.Or
items = items.Where(options.Searchs.GetFilterFunc<Foo>(FilterLogic.Or));
}
@inject IStringLocalizer<TablesAutoGenerateSearch> Localizer
@inject IStringLocalizer<Foo> FooLocalizer
<div>
<GroupBox Title="@Localizer["AutoGenerateSearchGroupBoxTitle"]" class="mb-2 mt-3">
<div class="row g-3 form-inline">
<div class="col-12 col-sm-3">
<Switch DisplayText="@Localizer["DisplayText1"]" ShowLabel="true" @bind-Value="SearchModeFlag" />
</div>
<div class="col-12 col-sm-3">
<Switch DisplayText="@Localizer["DisplayText2"]" ShowLabel="true" @bind-Value="ShowSearchText" />
</div>
<div class="col-12 col-sm-3">
<Switch DisplayText="@Localizer["DisplayText3"]" ShowLabel="true" @bind-Value="ShowResetButton" />
</div>
<div class="col-12 col-sm-3">
<Switch DisplayText="@Localizer["DisplayText4"]" ShowLabel="true" @bind-Value="ShowSearchButton" IsDisabled="SearchModeFlag" />
</div>
</div>
</GroupBox>
<Table TItem="Foo"
IsPagination="true" PageItemsSource="@PageItemsSource" SearchDialogShowMaximizeButton="true"
IsStriped="true" IsBordered="true" SearchModel="@SearchModel" ShowSearch="true"
SearchMode="SearchModeValue" ShowResetButton="ShowResetButton" ShowSearchButton="ShowSearchButton"
ShowToolbar="true" IsMultipleSelect="true" ShowExtendButtons="true" ShowSearchText="ShowSearchText"
AddModalTitle="@Localizer["AddModelTitle"]" EditModalTitle="@Localizer["EditModelTitle"]"
OnQueryAsync="@OnQueryAsync" OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync">
<TableColumns>
<TableColumn @bind-Field="@context.DateTime" Width="180" Filterable="true" />
<TableColumn @bind-Field="@context.Name" Searchable="true" />
<TableColumn @bind-Field="@context.Address" Searchable="true" />
<TableColumn @bind-Field="@context.Education" />
</TableColumns>
</Table>
</div>
@code {
/// <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>
[NotNull]
private List<Foo>? Items { get; set; }
private Foo SearchModel { get; set; } = new Foo();
private static IEnumerable<int> PageItemsSource => new int[] { 4, 10, 20 };
private bool SearchModeFlag
{
get
{
return SearchModeValue == SearchMode.Popup;
}
set
{
SearchModeValue = value ? SearchMode.Popup : SearchMode.Top;
}
}
private bool ShowResetButton { get; set; } = true;
private bool ShowSearchButton { get; set; } = true;
private bool ShowSearchText { get; set; }
private SearchMode SearchModeValue { get; set; }
private IEnumerable<SelectedItem>? SearchItems { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
Items = Foo.GenerateFoo(FooLocalizer);
SearchItems = new List<SelectedItem>()
{
new SelectedItem { Text = Localizer["SelectedItemText"].Value, Value = "" },
new SelectedItem { Text = Localizer["SelectedItemText1"].Value, Value = Localizer["SelectedItemValue1"].Value },
new SelectedItem { Text = Localizer["SelectedItemText2"].Value, Value = Localizer["SelectedItemValue2"].Value },
};
}
private static Task<Foo> OnAddAsync() => Task.FromResult(new Foo() { DateTime = DateTime.Now });
private Task<bool> OnSaveAsync(Foo item, ItemChangedType changedType)
{
// 增加数据演示代码
if (changedType == ItemChangedType.Add)
{
item.Id = Items.Max(i => i.Id) + 1;
Items.Add(item);
}
else
{
var oldItem = Items.FirstOrDefault(i => i.Id == item.Id);
if (oldItem != null)
{
oldItem.Name = item.Name;
oldItem.Address = item.Address;
oldItem.DateTime = item.DateTime;
oldItem.Count = item.Count;
oldItem.Complete = item.Complete;
oldItem.Education = item.Education;
}
}
return Task.FromResult(true);
}
private Task<bool> OnDeleteAsync(IEnumerable<Foo> items)
{
items.ToList().ForEach(i => Items.Remove(i));
return Task.FromResult(true);
}
private Task<QueryData<Foo>> OnQueryAsync(QueryPageOptions options)
{
IEnumerable<Foo> items = Items;
var isAdvanceSearch = false;
// 处理高级搜索
if (options.AdvanceSearchs.Any())
{
items = items.Where(options.AdvanceSearchs.GetFilterFunc<Foo>());
isAdvanceSearch = true;
}
// 处理 自定义 高级搜索 CustomerSearchModel 过滤条件
if (options.CustomerSearchs.Any())
{
items = items.Where(options.CustomerSearchs.GetFilterFunc<Foo>());
isAdvanceSearch = true;
}
// 处理 Searchable=true 列与 SeachText 模糊搜索
if (options.Searchs.Any())
{
items = items.Where(options.Searchs.GetFilterFunc<Foo>(FilterLogic.Or));
}
// 过滤
var isFiltered = false;
if (options.Filters.Any())
{
items = items.Where(options.Filters.GetFilterFunc<Foo>());
isFiltered = true;
}
// 排序
var isSorted = false;
if (!string.IsNullOrEmpty(options.SortName))
{
var invoker = Foo.GetNameSortFunc();
items = invoker(items, options.SortName, options.SortOrder);
isSorted = true;
}
// 设置记录总数
var total = items.Count();
// 内存分页
items = items.Skip((options.PageIndex - 1) * options.PageItems).Take(options.PageItems).ToList();
return Task.FromResult(new QueryData<Foo>()
{
Items = items,
TotalCount = total,
IsSorted = isSorted,
IsFiltered = isFiltered,
IsSearch = options.CustomerSearchs.Any() || !string.IsNullOrEmpty(options.SearchText),
IsAdvanceSearch = isAdvanceSearch
});
}
}
When SearchTemplate
is set, the component automatically generates the search UI using this template as the rendering UI
Demo
By setting the SearchTemplate
of the name column to customize the edit, use the drop-down box to select the name
Because it is a search condition, in this example, the drop-down box of the name search column is added. Please select... item
The column search template is generated in the search order SearchTemplate -> AutoGenerate first to find out whether the search template is set, and then automatically generated according to the bound field type
Set the search bar to show only Advanced Search by setting ShowSearch="true"
ShowSearchText="false"
Make the search popup draggable by setting SearchDialogDraggable="true"
@inject IStringLocalizer<TablesCustomColSearch> Localizer
@inject IStringLocalizer<Foo> FooLocalizer
<div>
<Table TItem="Foo"
IsPagination="true" PageItemsSource="@PageItemsSource"
IsStriped="true" IsBordered="true" SearchModel="@SearchModel" ShowSearch="true" ShowSearchText="false"
ShowToolbar="true" IsMultipleSelect="true" ShowExtendButtons="true" SearchDialogIsDraggable="true"
AddModalTitle="@Localizer["AddModelTitle"]" EditModalTitle="@Localizer["EditModelTitle"]" SearchDialogShowMaximizeButton="true"
OnQueryAsync="@OnQueryAsync" OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync">
<TableColumns>
<TableColumn @bind-Field="@context.DateTime" Width="180" Filterable="true" />
<TableColumn @bind-Field="@context.Name" Searchable="true">
<SearchTemplate Context="model">
<div class="col-12 col-sm-6">
<Select Items="SearchItems" @bind-Value="@model!.Name" ShowLabel="true" DisplayText="姓名" ></Select>
</div>
</SearchTemplate>
</TableColumn>
<TableColumn @bind-Field="@context.Address" Searchable="true" />
<TableColumn @bind-Field="@context.Education" />
</TableColumns>
</Table>
</div>
@code {
/// <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>
[NotNull]
private List<Foo>? Items { get; set; }
private Foo SearchModel { get; set; } = new Foo();
private static IEnumerable<int> PageItemsSource => new int[] { 4, 10, 20 };
private IEnumerable<SelectedItem>? SearchItems { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
Items = Foo.GenerateFoo(FooLocalizer);
SearchItems = new List<SelectedItem>()
{
new SelectedItem { Text = Localizer["SelectedItemText"].Value, Value = "" },
new SelectedItem { Text = Localizer["SelectedItemText1"].Value, Value = Localizer["SelectedItemValue1"].Value },
new SelectedItem { Text = Localizer["SelectedItemText2"].Value, Value = Localizer["SelectedItemValue2"].Value },
};
}
private static Task<Foo> OnAddAsync() => Task.FromResult(new Foo() { DateTime = DateTime.Now });
private Task<bool> OnSaveAsync(Foo item, ItemChangedType changedType)
{
// 增加数据演示代码
if (changedType == ItemChangedType.Add)
{
item.Id = Items.Max(i => i.Id) + 1;
Items.Add(item);
}
else
{
var oldItem = Items.FirstOrDefault(i => i.Id == item.Id);
if (oldItem != null)
{
oldItem.Name = item.Name;
oldItem.Address = item.Address;
oldItem.DateTime = item.DateTime;
oldItem.Count = item.Count;
oldItem.Complete = item.Complete;
oldItem.Education = item.Education;
}
}
return Task.FromResult(true);
}
private Task<bool> OnDeleteAsync(IEnumerable<Foo> items)
{
items.ToList().ForEach(i => Items.Remove(i));
return Task.FromResult(true);
}
private Task<QueryData<Foo>> OnQueryAsync(QueryPageOptions options)
{
IEnumerable<Foo> items = Items;
var isAdvanceSearch = false;
// 处理高级搜索
if (options.AdvanceSearchs.Any())
{
items = items.Where(options.AdvanceSearchs.GetFilterFunc<Foo>());
isAdvanceSearch = true;
}
// 处理 自定义 高级搜索 CustomerSearchModel 过滤条件
if (options.CustomerSearchs.Any())
{
items = items.Where(options.CustomerSearchs.GetFilterFunc<Foo>());
isAdvanceSearch = true;
}
// 处理 Searchable=true 列与 SeachText 模糊搜索
if (options.Searchs.Any())
{
items = items.Where(options.Searchs.GetFilterFunc<Foo>(FilterLogic.Or));
}
// 过滤
var isFiltered = false;
if (options.Filters.Any())
{
items = items.Where(options.Filters.GetFilterFunc<Foo>());
isFiltered = true;
}
// 排序
var isSorted = false;
if (!string.IsNullOrEmpty(options.SortName))
{
var invoker = Foo.GetNameSortFunc();
items = invoker(items, options.SortName, options.SortOrder);
isSorted = true;
}
// 设置记录总数
var total = items.Count();
// 内存分页
items = items.Skip((options.PageIndex - 1) * options.PageItems).Take(options.PageItems).ToList();
return Task.FromResult(new QueryData<Foo>()
{
Items = items,
TotalCount = total,
IsSorted = isSorted,
IsFiltered = isFiltered,
IsSearch = options.CustomerSearchs.Any() || !string.IsNullOrEmpty(options.SearchText),
IsAdvanceSearch = isAdvanceSearch
});
}
}
Set CustomerSearchModel
and CustomerSearchTemplate
to fully control the search criteria UI
Demo
Due to a certain application scenario, our form entity class TItem
is not ideal as a search model, for example, we have an attribute that is int
or enum
, we need a blank condition item of all or please select... as a search condition. It is very difficult to use the default model at this time , you can use the custom search model CustomerSearchModel
and CustomerSearchTemplate
to customize the control search interface
@inject IStringLocalizer<Foo> FooLocalizer
<div>
<Table TItem="Foo"
IsPagination="true" PageItemsSource="@PageItemsSource" SearchMode="SearchMode.Top"
IsStriped="true" IsBordered="true" CustomerSearchModel="@CustomerSearchModel" ShowSearch="true" ShowSearchText="true"
ShowToolbar="true" IsMultipleSelect="true" ShowExtendButtons="true"
OnQueryAsync="@OnQueryAsync" OnAddAsync="@OnAddAsync" OnSaveAsync="@OnSaveAsync" OnDeleteAsync="@OnDeleteAsync">
<TableColumns>
<TableColumn @bind-Field="@context.DateTime" Width="180" />
<TableColumn @bind-Field="@context.Name" Searchable="true" />
<TableColumn @bind-Field="@context.Address" Searchable="true" />
<TableColumn @bind-Field="@context.Count" />
<TableColumn @bind-Field="@context.Education" />
</TableColumns>
<CustomerSearchTemplate>
@if (context is FooSearchModel model)
{
<FooSearch @bind-Value="@model" />
}
</CustomerSearchTemplate>
</Table>
</div>
@code {
/// <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>
[NotNull]
private List<Foo>? Items { get; set; }
private static IEnumerable<int> PageItemsSource => new int[] { 4, 10, 20 };
private ITableSearchModel CustomerSearchModel { get; set; } = new FooSearchModel();
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
Items = Foo.GenerateFoo(FooLocalizer);
}
private static Task<Foo> OnAddAsync() => Task.FromResult(new Foo() { DateTime = DateTime.Now });
private Task<bool> OnSaveAsync(Foo item, ItemChangedType changedType)
{
// 增加数据演示代码
if (changedType == ItemChangedType.Add)
{
item.Id = Items.Max(i => i.Id) + 1;
Items.Add(item);
}
else
{
var oldItem = Items.FirstOrDefault(i => i.Id == item.Id);
if (oldItem != null)
{
oldItem.Name = item.Name;
oldItem.Address = item.Address;
oldItem.DateTime = item.DateTime;
oldItem.Count = item.Count;
oldItem.Complete = item.Complete;
oldItem.Education = item.Education;
}
}
return Task.FromResult(true);
}
private Task<bool> OnDeleteAsync(IEnumerable<Foo> items)
{
items.ToList().ForEach(i => Items.Remove(i));
return Task.FromResult(true);
}
private Task<QueryData<Foo>> OnQueryAsync(QueryPageOptions options)
{
IEnumerable<Foo> items = Items;
var isAdvanceSearch = false;
// 处理高级搜索
if (options.AdvanceSearchs.Any())
{
items = items.Where(options.AdvanceSearchs.GetFilterFunc<Foo>());
isAdvanceSearch = true;
}
// 处理 自定义 高级搜索 CustomerSearchModel 过滤条件
if (options.CustomerSearchs.Any())
{
items = items.Where(options.CustomerSearchs.GetFilterFunc<Foo>());
isAdvanceSearch = true;
}
// 处理 Searchable=true 列与 SeachText 模糊搜索
if (options.Searchs.Any())
{
items = items.Where(options.Searchs.GetFilterFunc<Foo>(FilterLogic.Or));
}
// 过滤
var isFiltered = false;
if (options.Filters.Any())
{
items = items.Where(options.Filters.GetFilterFunc<Foo>());
isFiltered = true;
}
// 排序
var isSorted = false;
if (!string.IsNullOrEmpty(options.SortName))
{
var invoker = Foo.GetNameSortFunc();
items = invoker(items, options.SortName, options.SortOrder);
isSorted = true;
}
// 设置记录总数
var total = items.Count();
// 内存分页
items = items.Skip((options.PageIndex - 1) * options.PageItems).Take(options.PageItems).ToList();
return Task.FromResult(new QueryData<Foo>()
{
Items = items,
TotalCount = total,
IsSorted = isSorted,
IsFiltered = isFiltered,
IsSearch = options.CustomerSearchs.Any() || !string.IsNullOrEmpty(options.SearchText),
IsAdvanceSearch = isAdvanceSearch
});
}
}
B station related video link
交流群