Skip to content

Commit 3b9b205

Browse files
authored
Merge pull request TabBlazor#187 from TabBlazor/table-nullable
Fixed Nullable on Table
2 parents 497f87e + f81b89f commit 3b9b205

File tree

9 files changed

+135
-43
lines changed

9 files changed

+135
-43
lines changed

docs/Tabler.Docs.Server/Properties/launchSettings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"Tabler.Docs.Server": {
1212
"commandName": "Project",
1313
"launchBrowser": true,
14-
"launchUrl": "docs/content-viewer",
14+
"launchUrl": "docs/tables",
1515
"environmentVariables": {
1616
"ASPNETCORE_ENVIRONMENT": "Development"
1717
},

docs/Tabler.Docs/Components/Dashboards/Basic.razor

+1-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@
398398
{
399399
await Task.Yield();
400400
await chart?.UpdateSeriesAsync(true);
401-
await orderTypeChart?.RenderAsync();
401+
await orderTypeChart?.UpdateOptionsAsync(true, true, true);
402402
await orderDiscountChart?.UpdateSeriesAsync(true);
403403
await topCustomerChart?.UpdateSeriesAsync(true);
404404
}

docs/Tabler.Docs/Components/Tables/Tables.razor

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</Example>
99
</CodeSnippet>
1010

11-
<CodeSnippet SetBackground ClassName="@typeof(TablesRowDetails).ToString()" Title="Details Template">
11+
<CodeSnippet SetBackground ClassName="@typeof(TablesRowDetails).ToString()" Title="Details Template">
1212
<Description>
1313
The details template makes it easy to show more details for a row by clicking on it
1414
</Description>
@@ -80,7 +80,7 @@
8080
<Example>
8181
<TablesExternalDataSourceWithSortingAndPaging />
8282
</Example>
83-
</CodeSnippet>
83+
</CodeSnippet>
8484

8585
</DocsExample>
8686
</Page>

docs/Tabler.Docs/Components/Tables/TablesBasic.razor

+19-14
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
11
@using Align = TabBlazor.Align
22

33
<Table Item="Order" Items="orders" PageSize="10"
4-
@bind-SelectedItems="selectedOrders" OnItemDeleted="OnItemDelete"
5-
ShowCheckboxes MultiSelect Hover Responsive>
4+
@bind-SelectedItems="selectedOrders" OnItemDeleted="OnItemDelete"
5+
ShowCheckboxes MultiSelect Hover Responsive>
66
<HeaderTemplate>
77
<strong>Orders</strong>
88
</HeaderTemplate>
99

1010
<ChildContent>
11-
<Column Item="Order" Property="e=>e.OrderId" Title="OrderId" Sortable Searchable>
11+
<Column Item="Order" Property="e=>e.OrderId" Title="OrderId" Searchable Sortable>
1212
<EditorTemplate>
1313
<input type="text" class="form-control" @bind-value="@context.OrderId" />
1414
</EditorTemplate>
1515
</Column>
16-
<Column Item="Order" Property="e=>e.Customer.CustomerName" Title="Customer" Sortable Searchable Groupable>
16+
<Column Item="Order" Property="e=>e.Customer.CustomerName" Title="Customer" Searchable Sortable Groupable>
1717
<EditorTemplate>
1818
<input type="text" class="form-control" @bind-value="@context.Customer.CustomerName" />
1919
</EditorTemplate>
2020
</Column>
21-
<Column Item="Order" Property="e=>e.Country" Title="Country" Sortable Searchable Groupable SearchExpression="(e, s) => SearchCountry(e,s)">
21+
<Column Item="Order" Property="e=>e.Country" Title="Country" Searchable Sortable Groupable>
2222
<EditorTemplate>
2323
<input type="text" class="form-control" @bind-value="@context.Country" />
2424
</EditorTemplate>
2525
</Column>
26-
27-
<Column Item="Order" Property="e=>e.NetValue" Title="Value" Align="Align.End" Sortable Searchable>
26+
27+
<Column Item="Order" Property="e=>e.NetValue" Title="Value" Align="Align.End" Searchable Sortable>
2828
<Template>
2929
$@context.NetValue.ToString("N0")
3030
</Template>
3131
</Column>
32-
33-
<Column Item="Order" Property="e=>e.OrderType" Title="Order type" Sortable Searchable Groupable>
32+
33+
<Column Item="Order" Property="e=>e.OrderType" Title="Order type" Sortable Groupable Searchable>
3434
<EditorTemplate>
3535
<Select Items="EnumHelper.GetList<OrderType>()" @bind-SelectedValue="@context.OrderType" TextExpression="e=> e.ToString()" ValueExpression="e=> e" Clearable />
3636
</EditorTemplate>
@@ -46,14 +46,19 @@
4646
}
4747
</ul>
4848

49-
@code {
50-
49+
@code {
50+
5151
[Inject] TabBlazor.Services.TablerService TablerService { get; set; }
5252
[Inject] TabBlazor.Services.IModalService ModalService { get; set; }
5353
private static List<Order> orders = SampleData.GetOrders();
54-
private static List<Order> selectedOrders = new List<Order>();
55-
56-
54+
private static List<Order> selectedOrders = new List<Order>();
55+
56+
protected override void OnInitialized()
57+
{
58+
orders.First().Customer = null;
59+
base.OnInitialized();
60+
}
61+
5762
private async Task OnItemDelete(Order order)
5863
{
5964
await ShowDialog($"Order deleted {order.OrderId}");

src/TabBlazor/Components/Tables/Components/Column.razor.cs

+30-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Threading.Tasks;
66
using TabBlazor.Components.Tables;
7+
using TabBlazor;
78
using LinqKit;
89

910
namespace TabBlazor
@@ -36,9 +37,9 @@ public string Title
3637
[Parameter] public Expression<Func<Item, object>> Property { get; set; }
3738
[Parameter] public Expression<Func<Item, string, bool>> SearchExpression { get; set; }
3839
[Parameter] public SortOrder? Sort { get; set; }
39-
[Parameter]public Align Align { get; set; }
40-
[Parameter] public bool Group { get; set; }
41-
40+
[Parameter] public Align Align { get; set; }
41+
[Parameter] public bool Group { get; set; }
42+
4243
public bool SortColumn { get; set; }
4344
public bool GroupBy { get; set; }
4445
public bool SortDescending { get; set; }
@@ -76,7 +77,23 @@ protected override void OnParametersSet()
7677
}
7778

7879
Type = Property?.GetPropertyMemberInfo().GetMemberUnderlyingType();
79-
}
80+
81+
PropertyNullSafe = (Expression<Func<Item, object>>)Property?.PropagateNull();
82+
83+
}
84+
85+
public Expression<Func<Item, object>> PropertyNullSafe { get; private set; }
86+
87+
//private Expression<Func<Item, object>> propertyNullSafe;
88+
//public Expression<Func<Item, object>> PropertyNullSafe
89+
//{
90+
// get
91+
// {
92+
// if (Property == null) { return null; }
93+
// propertyNullSafe ??= (Expression<Func<Item, object>>)Property.PropagateNull();
94+
// return propertyNullSafe;
95+
// }
96+
//}
8097

8198
public Expression<Func<Item, bool>> GetFilter(ITableState<Item> state)
8299
{
@@ -100,8 +117,8 @@ public Expression<Func<Item, bool>> GetFilter(ITableState<Item> state)
100117
private Expression<Func<Item, bool>> NotNull()
101118
{
102119
return Expression.Lambda<Func<Item, bool>>(
103-
Expression.NotEqual(Property.Body, Expression.Constant(null)),
104-
Property.Parameters.ToArray()
120+
Expression.NotEqual(PropertyNullSafe.Body, Expression.Constant(null)),
121+
PropertyNullSafe.Parameters.ToArray()
105122
);
106123
}
107124

@@ -139,12 +156,12 @@ public async Task SortByAsync()
139156
if (SortDescending && Table.ResetSortCycle)
140157
{
141158
sortOnColumn = false;
142-
}
143-
SortDescending = !SortDescending;
159+
}
160+
SortDescending = !SortDescending;
144161
}
145162

146-
Table.Columns.ForEach(x => x.SortColumn = false);
147-
163+
Table.Columns.ForEach(x => x.SortColumn = false);
164+
148165
SortColumn = sortOnColumn;
149166
await Table.Update();
150167
}
@@ -153,8 +170,9 @@ public async Task SortByAsync()
153170
public object GetValue(Item item)
154171
{
155172
try
156-
{
157-
return Property.Compile().Invoke(item);
173+
{
174+
return PropertyNullSafe.Compile().Invoke(item);
175+
// return Property.Compile().Invoke(item);
158176
}
159177
catch (NullReferenceException)
160178
{

src/TabBlazor/Components/Tables/Components/TheGridDataFactory.cs

+7-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public async Task<IEnumerable<TableResult<object, Item>>> GetData(List<IColumn<I
1414
{
1515
var query = items.AsQueryable();
1616
query = AddSearch(columns, state, query);
17+
18+
var jj = query.ToList();
19+
1720
if (addSorting)
1821
{
1922
query = AddSorting(columns, state, query);
@@ -50,7 +53,7 @@ public async Task<IEnumerable<TableResult<object, Item>>> GetData(List<IColumn<I
5053
else
5154
{
5255
columnGroup.GroupBy = true;
53-
foreach (var r in query.GroupBy(columnGroup.Property))
56+
foreach (var r in query.GroupBy(columnGroup.PropertyNullSafe))
5457
{
5558
viewResult.Add(new TableResult<object, Item>(r.Key, r.ToList())
5659
{
@@ -70,13 +73,13 @@ private IQueryable<Item> AddSorting(List<IColumn<Item>> columns, ITableState<Ite
7073
{
7174
if (state.UseNaturalSort)
7275
{
73-
query = NaturalOrderBy(query, sortColumn.Property, sortColumn.SortDescending);
76+
query = NaturalOrderBy(query, sortColumn.PropertyNullSafe, sortColumn.SortDescending);
7477
}
7578
else
7679
{
7780
query = sortColumn.SortDescending
78-
? query.OrderByDescending(sortColumn.Property)
79-
: query.OrderBy(sortColumn.Property);
81+
? query.OrderByDescending(sortColumn.PropertyNullSafe)
82+
: query.OrderBy(sortColumn.PropertyNullSafe);
8083
}
8184
}
8285

src/TabBlazor/Components/Tables/IColumn.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public interface IColumn<Item>
2121
Align Align { get; set; }
2222
Task SortByAsync();
2323
Task GroupByMeAsync();
24-
Type Type { get; }
24+
Type Type { get; }
25+
26+
Expression<Func<Item, object>> PropertyNullSafe { get; }
2527
Expression<Func<Item, object>> Property { get; }
2628
Expression<Func<Item, string, bool>> SearchExpression { get; }
2729
object GetValue(Item item);

src/TabBlazor/Components/Tables/TableFilterService.cs

+1-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
using Microsoft.Extensions.Localization;
2-
using System;
3-
using System.Collections.Generic;
4-
using System.ComponentModel;
5-
using System.Globalization;
6-
using System.Linq;
7-
using System.Reflection;
8-
1+

92
namespace TabBlazor.Components.Tables;
103

114
public class TableFilterService
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+

2+
namespace TabBlazor;
3+
4+
internal static class ExpressionExtensions
5+
{
6+
internal static Expression PropagateNull(this Expression expression)
7+
{
8+
if (expression == null) { return null; }
9+
return new MemberNullPropagationVisitor().Visit(expression);
10+
}
11+
12+
13+
internal static Func<T> PropagateNull<T>(this Expression<Func<T>> expression)
14+
{
15+
if (expression == null) { return null; }
16+
var defaultValue = Expression.Constant(default(T));
17+
var body = expression.Body.PropagateNull();
18+
if (body.Type != typeof(T))
19+
body = Expression.Coalesce(body, defaultValue);
20+
return Expression.Lambda<Func<T>>(body, expression.Parameters)
21+
.Compile();
22+
}
23+
24+
}
25+
26+
27+
//https://stackoverflow.com/questions/30488022/how-to-use-expression-tree-to-safely-access-path-of-nullable-objects
28+
internal class MemberNullPropagationVisitor : ExpressionVisitor
29+
{
30+
protected override Expression VisitMember(MemberExpression node)
31+
{
32+
if (node.Expression == null || !IsNullable(node.Type)) //|| !IsNullable(node.Expression.Type)
33+
return base.VisitMember(node);
34+
35+
var expression = base.Visit(node.Expression);
36+
var nullBaseExpression = Expression.Constant(null, expression.Type);
37+
var test = Expression.Equal(expression, nullBaseExpression);
38+
var memberAccess = Expression.MakeMemberAccess(expression, node.Member);
39+
var nullMemberExpression = Expression.Constant(null, node.Type);
40+
return Expression.Condition(test, nullMemberExpression, node);
41+
}
42+
43+
protected override Expression VisitMethodCall(MethodCallExpression node)
44+
{
45+
if (node.Object == null || !IsNullable(node.Object.Type))
46+
return base.VisitMethodCall(node);
47+
48+
var expression = base.Visit(node.Object);
49+
var nullBaseExpression = Expression.Constant(null, expression.Type);
50+
var test = Expression.Equal(expression, nullBaseExpression);
51+
var memberAccess = Expression.Call(expression, node.Method);
52+
var nullMemberExpression = Expression.Constant(null, MakeNullable(node.Type));
53+
return Expression.Condition(test, nullMemberExpression, node);
54+
}
55+
56+
private static Type MakeNullable(Type type)
57+
{
58+
if (IsNullable(type))
59+
return type;
60+
61+
return typeof(Nullable<>).MakeGenericType(type);
62+
}
63+
64+
private static bool IsNullable(Type type)
65+
{
66+
if (type.IsClass)
67+
return true;
68+
return type.IsGenericType &&
69+
type.GetGenericTypeDefinition() == typeof(Nullable<>);
70+
}
71+
}

0 commit comments

Comments
 (0)