Goffo.BlazorDataTable
0.3.0
There is a newer version of this package available.
See the version list below for details.
See the version list below for details.
dotnet add package Goffo.BlazorDataTable --version 0.3.0
NuGet\Install-Package Goffo.BlazorDataTable -Version 0.3.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Goffo.BlazorDataTable" Version="0.3.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Goffo.BlazorDataTable --version 0.3.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Goffo.BlazorDataTable, 0.3.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Goffo.BlazorDataTable as a Cake Addin #addin nuget:?package=Goffo.BlazorDataTable&version=0.3.0 // Install Goffo.BlazorDataTable as a Cake Tool #tool nuget:?package=Goffo.BlazorDataTable&version=0.3.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
Blazor DataTable
This is library with Blazor Components that allow the setup of a table with the ability of pagination, sorting and filtering.
Setup
Add to your head tag
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://kit.fontawesome.com/ce3dedc60a.js" crossorigin="anonymous"></script>
Add at the end of your body
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
Add to program.cs
using BlazorDataTable;
builder.Services.AddBlazorDataTable();
Example Razor
@page "/"
@inject IHttpClientFactory _httpClientFactory
@inject ILogger<Index> _logger
@inject NavigationManager _navigationManager
<PageTitle>Index</PageTitle>
@if (_isLoading)
{
<p>Loading...</p>
}
else
{
<div class="my-3">
<button class="btn-primary" @onclick="GoToNewPage">Go To New Page</button>
</div>
<DataTable TItem="PersonModel"
Items="_people"
CssClass="table table-hover table-bordered table-striped table-responsive"
UsePagination="true"
PaginationRecordsPerPageOptions="new List<int> { 5, 10, 20, 100, 500 }"
PaginationRecordsPerPage="_recordsPerPage"
PaginationAddAllRecordsOption="false"
PaginationCurrentPage="_currentPage"
PaginationTotalRecords="GetTotalRecords()"
RememberSortAndFilters="true"
PaginationOnRecordsPerPageChanged="RecordsPerPageChanged"
PaginationOnSelectedPage="PageSelectionChanged"
OnSortChanged="SortChanged">
<DataColumnHeaders>
<DataColumnHeader ColumnHeaderText="ID" ColumnName="Id" DefaultSortDirection="SortDirection.Disabled"></DataColumnHeader>
<DataColumnHeader ColumnHeaderText="First Name" ColumnName="FirstName" DefaultSortDirection="SortDirection.None"></DataColumnHeader>
<DataColumnHeader ColumnHeaderText="Last Name" ColumnName="LastName" DefaultSortDirection="SortDirection.None"></DataColumnHeader>
<DataColumnHeader ColumnHeaderText="Age" ColumnName="Age" DefaultSortDirection="SortDirection.None"></DataColumnHeader>
<DataColumnHeader ColumnHeaderText="Email Address" ColumnName="EmailAddress" DefaultSortDirection="SortDirection.None"></DataColumnHeader>
</DataColumnHeaders>
<Data>
<DataColumns OnFilterValueChanged="FilterValueChanged"
OnClearFilter="FilterCleared"
OnFilterTypeChanged="FilterTypeChanged"
OnRangeMinValueChanged="MinRangeValueChanged"
OnRangeMaxValueChanged="MaxRangeValueChanged"
>
<DataColumnFilters>
<DataColumnFilter SearchIsDisabled=true DisabledSearchIsVisible=true></DataColumnFilter>
<DataColumnFilter ColumnName="FirstName" AllowNullOrEmptySearch=false DefaultFilterType="FilterType.NullOrEmpty"></DataColumnFilter>
<DataColumnFilter ColumnName="LastName"></DataColumnFilter>
<DataColumnFilter ColumnName="Age" UseRangeFilter="true" MinRangeValue="_minRangeAgeValue" MaxRangeValue="_maxRangeAgeValue" MinValue="_minAgeValue" MaxValue="_maxAgeValue"></DataColumnFilter>
<DataColumnFilter ColumnName="EmailAddress"></DataColumnFilter>
</DataColumnFilters>
<DataDisplayColumns>
<TextDataColumn>@context.Id</TextDataColumn>
<TextDataColumn>@context.FirstName</TextDataColumn>
<TextDataColumn>@context.LastName</TextDataColumn>
<TextDataColumn>@context.Age</TextDataColumn>
<TextDataColumn>@context.Email</TextDataColumn>
</DataDisplayColumns>
</DataColumns>
</Data>
</DataTable>
Example Code Behind
#region Properties and Fields
private HttpClient _httpClient = new();
private List<PersonModel> _people = new();
private string _baseAddress = string.Empty;
private string? _sqlWhere;
private string? _sqlOrderBy;
private Dictionary<string, Dictionary<string, SqlFilter>> _filters = new();
private Dictionary<string, SqlSort> _sorts = new();
private IDictionary<string, string?> _queryParameters = new Dictionary<string, string?>();
private string? _apiQueryString = string.Empty;
private int _recordsPerPage = 7; //Defaulted to 10 records per page
private int _currentPage = 1; //Default to start at page 1
private int _totalRecords;
private int _minRangeAgeValue;
private int _maxRangeAgeValue;
private int _minAgeValue;
private int _maxAgeValue;
private bool _isLoading = true;
#endregion
#region Overrides
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_httpClient = _httpClientFactory.CreateClient("LocalClient");
_baseAddress = _httpClient.BaseAddress?.ToString()!;
//Sets the Initial Data
await SetMinRangeAgeValue();
await SetMaxRangeAgeValue();
await SetPeople();
ToggleIsLoading();
StateHasChanged();
}
await base.OnAfterRenderAsync(firstRender);
}
#endregion
#region For DataTable
private int GetTotalRecords()
{
_totalRecords = 0;
if (_people.Any())
{
_totalRecords = _people.First().TotalRecords;
}
return _totalRecords;
}
private async Task RecordsPerPageChanged(int recordsPerPage)
{
//For when the Records per page is changed
if(_recordsPerPage == recordsPerPage)
{
return;
}
_recordsPerPage = recordsPerPage;
_currentPage = 1; //I want to have it reset back to first page
//Update data
await SetPeople();
}
private async Task PageSelectionChanged(int page)
{
//For when the page is changed
if(_currentPage == page)
{
return;
}
_currentPage = page;
//Update data
await SetPeople();
}
private async Task SortChanged(SortTableSearchEventArgs args)
{
//This is where you have your logic for sorting when the Sort Icon gets clicked (Order: None, Ascending, Descending)
if(string.IsNullOrWhiteSpace(args.ColumnName))
{
_logger.LogInformation("{0} was null for {1} method", nameof(args.ColumnName), nameof(SortChanged));
return;
}
//This is just bringing back the sorted current page of data
//It may be different for you
var sortValues = args.SortedColumns;
if (sortValues.ContainsKey(args.ColumnName))
{
sortValues[args.ColumnName] = args.SortDirection;
}
else
{
sortValues.Add(args.ColumnName, args.SortDirection);
}
//Updates the data
SetSqlOrderBy(sortValues);
await SetPeople();
}
private async Task FilterValueChanged(FilterTableSearchEventArgs args)
{
//This is where you handle filtering based on the text that is entered into the column search
if(string.IsNullOrWhiteSpace(args.ColumnName) || args.ColumnName == "Age")
{
return;
}
//This is just bringing back the filtered current page of data
//It may be different for you
try
{
var searchValues = args.ColumnSearchValues;
if (searchValues.ContainsKey(args.ColumnName))
{
searchValues[args.ColumnName].SearchValue = args.SearchValue;
}
else
{
searchValues.Add(args.ColumnName, new ColumnFilter(args.FilterType, args.SearchValue));
}
//Updates the data
SetSqlWhere(searchValues);
await SetPeople();
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
private async Task FilterCleared(ClearTableSearchEventArgs args)
{
//This is where you handle filtering based on the text that is entered into the column search is cleared out
//It may be different for you
//Updates the data
SetSqlWhere(args.ColumnSearchValues);
SetSqlOrderBy(args.SortedColumns);
await SetPeople();
}
private async Task FilterTypeChanged(FilterTypeEventArgs args)
{
//This is where you handle filtering based on the Filter Type Icon being clicked and changed (Order: Starts With, Contains, Ends With)
//It may be different for you
//This is just bringing back the filtered current page of data
//It may be different for you
try
{
var filters = args.ColumnFilters;
if (filters.ContainsKey(args.ColumnName))
{
filters[args.ColumnName].FilterType = args.Filter.FilterType;
}
else
{
filters.Add(args.ColumnName, new ColumnFilter(args.Filter.FilterType, args.Filter.SearchValue));
}
//Updates data
SetSqlWhere(filters);
await SetPeople();
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
private void MinRangeValueChanged(MinRangeEventArgs args)
{
//Here is where you handle when the Min Value changes
_minAgeValue = args.MinRangeValue;
_logger.LogInformation("{0} has been changed to {1}", nameof(_minAgeValue), _minAgeValue);
}
private async Task MaxRangeValueChanged(MaxRangeEventArgs args)
{
//Here is where you handle when the Max Value changes
_maxAgeValue = args.MaxRangeValue;
_logger.LogInformation("{0} has been changed to {1}", nameof(_maxAgeValue), _maxAgeValue);
await Task.CompletedTask;
StateHasChanged();
}
private async Task SetMinRangeAgeValue()
{
_minRangeAgeValue = int.MinValue;
_apiQueryString = @$"{_baseAddress}GetMinAge";
try
{
var output = await _httpClient.GetFromJsonAsync<List<MinAgeModel>>(_apiQueryString);
if (output is not null)
{
if (output.Count == 1)
{
_minRangeAgeValue = output.Single().MinAge;
_logger.LogInformation("{0} has been set to {1}", nameof(_minRangeAgeValue), _minRangeAgeValue);
}
else
{
_logger.LogError("Multiple records were returned for MinAgeValue");
return;
}
}
else
{
_logger.LogError("Output for MinAgeValue was null");
return;
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return;
}
}
private async Task SetMaxRangeAgeValue()
{
_maxRangeAgeValue = 0;
_apiQueryString = @$"{_baseAddress}GetMaxAge";
try
{
var output = await _httpClient.GetFromJsonAsync<List<MaxAgeModel>>(_apiQueryString);
if (output is not null)
{
if (output.Count == 1)
{
_maxRangeAgeValue = output.Single().MaxAge;
_logger.LogInformation("{0} has been set to {1}", nameof(_maxRangeAgeValue), _maxRangeAgeValue);
}
else
{
_logger.LogError("Multiple records were returned for MaxAgeValue");
return;
}
}
else
{
_logger.LogError("Output for MaxAgeValue was null");
return;
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return;
}
}
#endregion
#region Get Data (Not required for DataTable but would be how you are getting your data)
private void SetApiQueryString()
{
_queryParameters.Clear();
_queryParameters.Add("startPage", _currentPage.ToString());
_queryParameters.Add("recordsPerPage", _recordsPerPage.ToString());
if (!string.IsNullOrWhiteSpace(_sqlWhere))
{
_queryParameters.Add("sqlWhere", _sqlWhere);
}
if (!string.IsNullOrWhiteSpace(_sqlOrderBy))
{
_queryParameters.Add("sqlOrderBy", _sqlOrderBy);
}
_apiQueryString = QueryHelpers.AddQueryString(_baseAddress + "GetPeopleDynamic", _queryParameters);
}
private async Task SetPeople()
{
SetApiQueryString();
try
{
var people = await _httpClient.GetFromJsonAsync<List<PersonModel>>(_apiQueryString);
if (people is not null)
{
_people.Clear();
if (people.Any())
{
_people.AddRange(people);
_logger.LogInformation("{0} = {1}", nameof(_people), _people.Count);
_totalRecords = _people.First().TotalRecords;
_logger.LogInformation("{0} = {1}", nameof(_totalRecords), _totalRecords);
}
else
{
_logger.LogInformation("No records returned.");
}
}
else
{
_logger.LogWarning("{0} was null", nameof(people));
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
#endregion
#region Additional Methods (Not required for DataTable)
private void GoToNewPage()
{
_navigationManager.NavigateTo("/NewPage");
}
private void ToggleIsLoading()
{
_isLoading = !_isLoading;
}
private void SetSqlOrderBy(Dictionary<string, SortDirection> sortedColumns)
{
//This is for the api call to get data
_sorts.Clear();
SqlSort? sqlSort = null;
_sqlOrderBy = null;
foreach (var column in sortedColumns)
{
sqlSort = null;
if (column.Value == SortDirection.Ascending)
{
sqlSort = SqlSort.ASC;
}
else if (column.Value == SortDirection.Descending)
{
sqlSort = SqlSort.DESC;
}
if (sqlSort.HasValue)
{
_sorts.Add(column.Key, sqlSort.Value);
}
}
if (_sorts.Count > 0)
{
_sqlOrderBy = SqlBuilder.BuildOrderByClause(_sorts);
}
}
private void SetSqlWhere(Dictionary<string, ColumnFilter> columnSearchValues)
{
//This is for the api call to get the data
_sqlWhere = null;
_filters.Clear();
SqlFilter sqlFilter = SqlFilter.StartsWith;
FilterType columnFilter = FilterType.StartsWith;
foreach (var column in columnSearchValues)
{
columnFilter = column.Value.FilterType;
if (columnFilter == FilterType.StartsWith)
{
sqlFilter = SqlFilter.StartsWith;
}
else if (columnFilter == FilterType.Contains)
{
sqlFilter = SqlFilter.Contains;
}
else if (columnFilter == FilterType.EndsWith)
{
sqlFilter = SqlFilter.EndsWith;
}
else if (columnFilter == FilterType.NullOrEmpty)
{
sqlFilter = SqlFilter.NullOrEmpty;
}
else
{
sqlFilter = SqlFilter.StartsWith;
}
if (!string.IsNullOrEmpty(column.Value.SearchValue))
{
_filters.Add(column.Key, new Dictionary<string, SqlFilter>() { { column.Value.SearchValue!, sqlFilter } });
}
else
{
if(sqlFilter == SqlFilter.NullOrEmpty)
{
_filters.Add(column.Key, new Dictionary<string, SqlFilter>() { { sqlFilter.ToString(), sqlFilter } });
}
}
}
if (_filters.Count > 0)
{
_sqlWhere = SqlBuilder.BuildWhereClause(_filters);
}
}
#endregion
Other Information
DataColumnHeader
- ColumnHeaderText is the Column Header Text to display in the table
- DefaultSortDirection is defaulted to None
- Other options are Hidden, Disabled, Ascending and Descending
- ColumnName comes back in the OnSortChanged Event Args
DataColumnFilter
- ColumName comes back in the Event args for Events in the DataColumns razor component
There are also different DataColumn components that have callback methods to peform your event task
- TextDataColumn - text
- ButtonDataColumn - button
- CheckBoxDataColumn - checkbox
- SelectDataColumn - dropdown list
- DataDisplayColumn - Put anything you want in it (One or more of the above or your own html)
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net6.0
- Microsoft.AspNetCore.Components.Web (>= 6.0.20)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Added option in DataColumnFilter to allow for Searching Null or Empty values. Added an option to display text if no data is found.