Skip to content

Commit

Permalink
DataGrid: add ExpressionCompiler utility (#5872)
Browse files Browse the repository at this point in the history
* DataGrid ExpressionCompiler ongoing

* ExpressionCompiler | Segregate into Sort | Search | Paging | Improve DataGridPage filter example

* Add Expression support for less than or greater than

* ExpressionCompiler | Between Support

* Docs and Release notes

* Add DataGrid ExpressionCompiler support for DataGridReadDataEventArgs<TItem>

* Format fix

* Fix grammar

---------

Co-authored-by: Mladen Macanovic <[email protected]>
  • Loading branch information
David-Moreira and stsrki authored Dec 19, 2024
1 parent 314d97e commit 6082c85
Show file tree
Hide file tree
Showing 11 changed files with 1,057 additions and 241 deletions.
9 changes: 4 additions & 5 deletions Demos/Blazorise.Demo/Pages/Tests/DataGrid/DataGridPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@
RowUpdating="@OnRowUpdating"
RowRemoving="@OnRowRemoving"
UseInternalEditing="false"
@bind-SelectedRow="@selectedEmployee"
@bind-SelectedRows="@selectedEmployees"
@bind-SelectedRow="@selectedEmployee"
@bind-SelectedRows="@selectedEmployees"
NewItemDefaultSetter="@OnEmployeeNewItemDefaultSetter"
DetailRowTrigger="@((e)=> e.Item.Salaries?.Count > 0 && e.Item.Id == selectedEmployee?.Id)"
Striped
Expand All @@ -254,7 +254,7 @@
Responsive
ValidationsSummaryLabel="Following error occurs..."
CustomFilter="@OnCustomFilter"
PageSize="5"
@bind-PageSize="pageSize"
CurrentPage="currentPage"
PageChanged="(e) => currentPage = e.Page"
FilteredDataChanged="@OnFilteredDataChanged"
Expand Down Expand Up @@ -354,8 +354,7 @@
</SortDirectionTemplate>
</DataGridColumn>
<DataGridDateColumn Field="@nameof( Employee.DateOfBirth )" DisplayFormat="{0:dd.MM.yyyy}" Caption="Date Of Birth" Editable />
<DataGridNumericColumn Field="@nameof( Employee.Childrens )" Caption="Childrens" Editable Filterable="false" />

<DataGridNumericColumn Field="@nameof( Employee.Childrens )" Caption="Childrens" Editable Filterable/>
<DataGridSelectColumn TItem="Employee" Field="@nameof( Employee.Gender )" Caption="Gender"
CustomFilter="@OnGenderCustomFilter"
Editable Data="EmployeeData.Genders" ValueField="(x) => ((Gender)x).Code" TextField="(x) => ((Gender)x).Description" />
Expand Down
86 changes: 9 additions & 77 deletions Demos/Blazorise.Demo/Pages/Tests/DataGrid/DataGridPage.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public partial class DataGridPage

private DataGrid<Employee> dataGrid;
public int currentPage { get; set; } = 1;
public int pageSize { get; set; } = 5;

private bool editable = true;
private bool fixedHeader = false;
Expand Down Expand Up @@ -203,73 +204,22 @@ private async Task OnReadData( DataGridReadDataEventArgs<Employee> e )
{
if ( !e.CancellationToken.IsCancellationRequested )
{
List<Employee> response = null;
var query = dataModels.AsQueryable().ApplyDataGridSort( e ).ApplyDataGridSearch( e );

var filteredData = await FilterData( e.Columns );
if ( dataGrid.CustomFilter is not null )
query = query.Where( item => item != null && dataGrid.CustomFilter( item ) );

// this can be call to anything, in this case we're calling a fictional api
if ( e.ReadDataMode is DataGridReadDataMode.Virtualize )
response = filteredData.Skip( e.VirtualizeOffset ).Take( e.VirtualizeCount ).ToList();
else if ( e.ReadDataMode is DataGridReadDataMode.Paging )
response = filteredData.Skip( ( e.Page - 1 ) * e.PageSize ).Take( e.PageSize ).ToList();
else
throw new Exception( "Unhandled ReadDataMode" );
var response = new List<Employee>();
response = query.ApplyDataGridPaging( e ).ToList();

await Task.Delay( random.Next( 100 ) );
if ( !e.CancellationToken.IsCancellationRequested )
{
totalEmployees = filteredData.Count;
employeeList = new List<Employee>( response ); // an actual data for the current page
}
}
}

/// <summary>
/// Simple demo purpose example filter
/// </summary>
/// <param name="dataGridColumns"></param>
/// <returns></returns>
public Task<List<Employee>> FilterData( IEnumerable<DataGridColumnInfo> dataGridColumns )
{
var filteredData = dataModels.ToList();
var sortByColumns = dataGridColumns.Where( x => x.SortDirection != SortDirection.Default );
var firstSort = true;
if ( sortByColumns?.Any() ?? false )
{
IOrderedEnumerable<Employee> sortedCols = null;
foreach ( var sortByColumn in sortByColumns.OrderBy( x => x.SortIndex ) )
if ( !e.CancellationToken.IsCancellationRequested )
{
var valueGetter = FunctionCompiler.CreateValueGetter<Employee>( sortByColumn.SortField );

if ( firstSort )
{
if ( sortByColumn.SortDirection == SortDirection.Ascending )
sortedCols = dataModels.OrderBy( x => valueGetter( x ) );
else
sortedCols = dataModels.OrderByDescending( x => valueGetter( x ) );

firstSort = false;
}
else
{
if ( sortByColumn.SortDirection == SortDirection.Ascending )
sortedCols = sortedCols.ThenBy( x => valueGetter( x ) );
else
sortedCols = sortedCols.ThenByDescending( x => valueGetter( x ) );
}
totalEmployees = query.Count();
employeeList = response;
}
filteredData = sortedCols.ToList();
}

if ( dataGrid.CustomFilter != null )
filteredData = filteredData.Where( item => item != null && dataGrid.CustomFilter( item ) ).ToList();

foreach ( var column in dataGridColumns.Where( x => !string.IsNullOrWhiteSpace( x.SearchValue?.ToString() ) ) )
{
var valueGetter = FunctionCompiler.CreateValueGetter<Employee>( column.Field );
filteredData = filteredData.Where( x => valueGetter( x )?.ToString().IndexOf( column.SearchValue.ToString(), StringComparison.OrdinalIgnoreCase ) >= 0 ).ToList();
}
return Task.FromResult( filteredData );
}

private Task Reset()
Expand Down Expand Up @@ -301,23 +251,5 @@ private void OnSortChanged( DataGridSortChangedEventArgs eventArgs )
Console.WriteLine( $"Sort changed > Field: {eventArgs.ColumnFieldName}{sort}; Direction: {eventArgs.SortDirection};" );
}

private string TitleFromGender( string gender )
{
return gender switch
{
"M" => "Mr.",
"F" => "Mrs.",
_ => string.Empty,
};
}

private string TitleToName( string title, object name )
{
if ( string.IsNullOrEmpty( title ) )
return $"{name}";

return $"{title} {name}";
}

#endregion
}
65 changes: 65 additions & 0 deletions Documentation/Blazorise.Docs/Models/Snippets.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8501,6 +8501,71 @@ private async Task OnReadData( DataGridReadDataEventArgs<Employee> e )
}
}";

public const string DataGridLargeDataExpressionCompilerExample = @"@using Blazorise.DataGrid.Extensions;
@using Blazorise.DataGrid.Utils

<DataGrid @ref=dataGridRef
TItem=""Employee""
Data=""@employeeList""
ReadData=""@OnReadData""
TotalItems=""@totalEmployees""
PageSize=""10""
ShowPager
Responsive
Filterable
FilterMode=""DataGridFilterMode.Menu"">
<DataGridCommandColumn />
<DataGridColumn Field=""@nameof(Employee.FirstName)"" Caption=""First Name"" Editable />
<DataGridColumn Field=""@nameof(Employee.LastName)"" Caption=""Last Name"" Editable />
<DataGridSelectColumn TItem=""Employee"" Field=""@nameof( Employee.Gender )"" Caption=""Gender"" Editable Data=""EmployeeData.Genders"" ValueField=""(x) => ((Gender)x).Code"" TextField=""(x) => ((Gender)x).Description"" />
<DataGridNumericColumn Field=""@nameof( Employee.Childrens )"" Caption=""Childrens"" Editable />
<DataGridDateColumn Field=""@nameof( Employee.DateOfBirth )"" DisplayFormat=""{0:dd.MM.yyyy}"" Caption=""Date Of Birth"" Editable />
</DataGrid>

@code {
[Inject] public EmployeeData EmployeeData { get; set; }
private DataGrid<Employee> dataGridRef;
private List<Employee> employeeListSource;
private List<Employee> employeeList;

protected override async Task OnInitializedAsync()
{
employeeListSource = await EmployeeData.GetDataAsync();
await base.OnInitializedAsync();
}

private int totalEmployees;

private async Task OnReadData(DataGridReadDataEventArgs<Employee> e)
{

if (!e.CancellationToken.IsCancellationRequested)
{
var query = employeeListSource.AsQueryable().ApplyDataGridSort(e.Columns).ApplyDataGridSearch(e.Columns);

if (dataGridRef.CustomFilter is not null)
query = query.Where(item => item != null && dataGridRef.CustomFilter(item));

var response = new List<Employee>();

if (e.ReadDataMode is DataGridReadDataMode.Virtualize)
response = query.ApplyDataGridPaging(e.VirtualizeOffset + 1, e.VirtualizeCount).ToList();
else if (e.ReadDataMode is DataGridReadDataMode.Paging)
response = query.ApplyDataGridPaging(e.Page, e.PageSize).ToList();
else
throw new Exception(""Unhandled ReadDataMode"");

await Task.Delay(Random.Shared.Next(100));

if (!e.CancellationToken.IsCancellationRequested)
{
totalEmployees = query.Count();
employeeList = response;
}
}
}
}";

public const string DataGridLoadingEmptyTemplateExample = @"<DataGrid @ref=""datagridRef""
TItem=""Employee""
Data=""@employeeList""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@
<DocsPageSectionSource Code="DataGridLargeDataExample" />
</DocsPageSection>

<DocsPageSection>
<DocsPageSectionHeader Title="Expression Compiler">
<Paragraph>
This utility enables you to compile expressions and use them in conjunction with the DataGrid <Code>ReadData()</Code> to build an <Code>IQueryable</Code> for querying your data.
</Paragraph>
<Paragraph>
The <Code>ExpressionCompiler</Code> can work alongside the <Code>DataGridColumnInfo</Code> collection provided by <Code>ReadData</Code> to create expression-based queries (<Code>IQueryables</Code>) that can be utilized in ORMs like Entity Framework.
</Paragraph>
</DocsPageSectionHeader>
<DocsPageSectionContent FullWidth Outlined>
<DataGridLargeDataExpressionCompilerExample />
</DocsPageSectionContent>
<DocsPageSectionSource Code="DataGridLargeDataExpressionCompilerExample" />
</DocsPageSection>

<DocsPageApi>
<DocsPageApiItem Url="docs/extensions/datagrid/api" Name="<DataGrid />" />
</DocsPageApi>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<div class="blazorise-codeblock">
<div class="html"><pre>
<span class="atSign">&#64;</span>using Blazorise.DataGrid.Extensions;
<span class="atSign">&#64;</span>using Blazorise.DataGrid.Utils

<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">DataGrid</span> <span class="htmlAttributeName"><span class="atSign">&#64;</span>ref</span><span class="htmlOperator">=</span><span class="htmlAttributeValue">dataGridRef</span>
<span class="htmlAttributeName">TItem</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">Employee</span><span class="quot">&quot;</span>
<span class="htmlAttributeName">Data</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="sharpVariable"><span class="atSign">&#64;</span>employeeList</span><span class="quot">&quot;</span>
<span class="htmlAttributeName">ReadData</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="sharpVariable"><span class="atSign">&#64;</span>OnReadData</span><span class="quot">&quot;</span>
<span class="htmlAttributeName">TotalItems</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="sharpVariable"><span class="atSign">&#64;</span>totalEmployees</span><span class="quot">&quot;</span>
<span class="htmlAttributeName">PageSize</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">10</span><span class="quot">&quot;</span>
<span class="htmlAttributeName">ShowPager</span>
<span class="htmlAttributeName">Responsive</span>
<span class="htmlAttributeName">Filterable</span>
<span class="htmlAttributeName">FilterMode</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="enum">DataGridFilterMode</span><span class="enumValue">.Menu</span><span class="quot">&quot;</span><span class="htmlTagDelimiter">&gt;</span>
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">DataGridCommandColumn</span> <span class="htmlTagDelimiter">/&gt;</span>
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">DataGridColumn</span> <span class="htmlAttributeName">Field</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue"><span class="atSign">&#64;</span>nameof(Employee.FirstName)</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Caption</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">First Name</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Editable</span> <span class="htmlTagDelimiter">/&gt;</span>
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">DataGridColumn</span> <span class="htmlAttributeName">Field</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue"><span class="atSign">&#64;</span>nameof(Employee.LastName)</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Caption</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">Last Name</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Editable</span> <span class="htmlTagDelimiter">/&gt;</span>
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">DataGridSelectColumn</span> <span class="htmlAttributeName">TItem</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">Employee</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Field</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue"><span class="atSign">&#64;</span>nameof( Employee.Gender )</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Caption</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">Gender</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Editable</span> <span class="htmlAttributeName">Data</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="enum">EmployeeData</span><span class="enumValue">.Genders</span><span class="quot">&quot;</span> <span class="htmlAttributeName">ValueField</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">(x) =&gt; ((Gender)x).Code</span><span class="quot">&quot;</span> <span class="htmlAttributeName">TextField</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">(x) =&gt; ((Gender)x).Description</span><span class="quot">&quot;</span> <span class="htmlTagDelimiter">/&gt;</span>
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">DataGridNumericColumn</span> <span class="htmlAttributeName">Field</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue"><span class="atSign">&#64;</span>nameof( Employee.Childrens )</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Caption</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">Childrens</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Editable</span> <span class="htmlTagDelimiter">/&gt;</span>
<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">DataGridDateColumn</span> <span class="htmlAttributeName">Field</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue"><span class="atSign">&#64;</span>nameof( Employee.DateOfBirth )</span><span class="quot">&quot;</span> <span class="htmlAttributeName">DisplayFormat</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">{0:dd.MM.yyyy}</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Caption</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">Date Of Birth</span><span class="quot">&quot;</span> <span class="htmlAttributeName">Editable</span> <span class="htmlTagDelimiter">/&gt;</span>
<span class="htmlTagDelimiter">&lt;/</span><span class="htmlElementName">DataGrid</span><span class="htmlTagDelimiter">&gt;</span>
</pre></div>
<div class="csharp"><pre>
<span class="atSign">&#64;</span>code {
[Inject] <span class="keyword">public</span> EmployeeData EmployeeData { <span class="keyword">get</span>; <span class="keyword">set</span>; }
<span class="keyword">private</span> DataGrid&lt;Employee&gt; dataGridRef;
<span class="keyword">private</span> List&lt;Employee&gt; employeeListSource;
<span class="keyword">private</span> List&lt;Employee&gt; employeeList;

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">async</span> Task OnInitializedAsync()
{
employeeListSource = <span class="keyword">await</span> EmployeeData.GetDataAsync();
<span class="keyword">await</span> <span class="keyword">base</span>.OnInitializedAsync();
}

<span class="keyword">private</span> <span class="keyword">int</span> totalEmployees;

<span class="keyword">private</span> <span class="keyword">async</span> Task OnReadData(DataGridReadDataEventArgs&lt;Employee&gt; e)
{

<span class="keyword">if</span> (!e.CancellationToken.IsCancellationRequested)
{
<span class="keyword">var</span> query = employeeListSource.AsQueryable().ApplyDataGridSort(e.Columns).ApplyDataGridSearch(e.Columns);

<span class="keyword">if</span> (dataGridRef.CustomFilter <span class="keyword">is</span> not <span class="keyword">null</span>)
query = query.Where(item =&gt; item != <span class="keyword">null</span> &amp;&amp; dataGridRef.CustomFilter(item));

<span class="keyword">var</span> response = <span class="keyword">new</span> List&lt;Employee&gt;();

<span class="keyword">if</span> (e.ReadDataMode <span class="keyword">is</span> DataGridReadDataMode.Virtualize)
response = query.ApplyDataGridPaging(e.VirtualizeOffset + <span class="number">1</span>, e.VirtualizeCount).ToList();
<span class="keyword">else</span> <span class="keyword">if</span> (e.ReadDataMode <span class="keyword">is</span> DataGridReadDataMode.Paging)
response = query.ApplyDataGridPaging(e.Page, e.PageSize).ToList();
<span class="keyword">else</span>
<span class="keyword">throw</span> <span class="keyword">new</span> Exception(<span class="string">&quot;Unhandled ReadDataMode&quot;</span>);

<span class="keyword">await</span> Task.Delay(Random.Shared.Next(<span class="number">100</span>));

<span class="keyword">if</span> (!e.CancellationToken.IsCancellationRequested)
{
totalEmployees = query.Count();
employeeList = response;
}
}
}
}
</pre></div>
</div>
Loading

0 comments on commit 6082c85

Please sign in to comment.