I have this markup. GD is a list, containing individual RD's. GD=GridDataType, RD=RowDataType.
<GGrid @ref="mg" GD="recs">
<GCol RD="MyAction" Expression="c => c.Action_ID" Title="Action ID" />
<GCol RD="MyAction" Expression="c => c.Action_Date" Title="Action Date" />
</GGrid>
I want to be able to NOT have to declare the datatype on the columns. Rather the code infer it from the parent, and declare it like this...
<GGrid @ref="mg" GD="recs">
<GCol Expression="c => c.Action_ID" Title="Action ID" />
<GCol Expression="c => c.Action_Date" Title="Action Date" />
</GGrid>
Here are my three code sections that make up my page with Markup, GGrid, and also GCol...
Markup...
@page "/Grid/GGridTest"
@layout Partial
@inject IJSRuntime JSRuntime
@inject HttpClient Http
@using System.Linq;
@using gMIS.Model;
<button @onclick="LoadGridData">Load Data</button>
<GGrid @ref="mg" GD="recs">
<GCol RD="MyAction" Expression="c => c.Action_ID" Title="Action ID" />
<GCol RD="MyAction" Expression="c => c.Action_Date" Title="Action Date" />
</GGrid>
@code {
private RazorComponents.Components.GGrid.GGrid<gMIS.Model.Action> mg;
private List<MyAction> recs;
private async void LoadGridData()
{
recs = await Http.GetFromJsonAsync<List<MyAction>>("https://localhost:44302/api/actions");
StateHasChanged();
}
}
GGrid...
@typeparam RD
<CascadingValue IsFixed="true" Value="this">@ChildContent</CascadingValue>
<ggrid>
<table @attributes="@TableAttributes">
<thead>
<tr>
@foreach (var column in columns)
{
@column.HeaderTemplate
}
</tr>
</thead>
<tbody>
@if (GD != null)
{
foreach (var row in GD)
{
<tr class="@RowClass?.Invoke(row, GD.ToList().IndexOf(row))">
@foreach (var column in columns)
{
@column.CellTemplate(row)
}
</tr>
}
}
</tbody>
</table>
</ggrid>
@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> TableAttributes { get; set; }
[Parameter]
public ICollection<RD> GD { get; set; }
// This fragment should contain all the GCol
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public Func<RD, int, string> RowClass { get; set; }
private readonly List<GCol<RD>> columns = new List<GCol<RD>>();
// GCol uses this method to add a column
internal void AddColumn(GCol<RD> column)
{
columns.Add(column);
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// The first render will instantiate the GCol defined in the ChildContent.
// GCol calls AddColumn during its initialization. This means that until
// the first render is completed, the columns collection is empty.
// Calling StateHasChanged() will re-render the component, so the second time it will know the columns
StateHasChanged();
}
}
}
GCol...
@typeparam RD
@using System.Linq.Expressions
@code {
[CascadingParameter]
public GGrid<RD> OwnerGrid { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public Expression<Func<RD, object>> Expression { get; set; }
[Parameter]
public RenderFragment<RD> ChildContent { get; set; }
private Func<RD, object> compiledExpression;
private Expression lastCompiledExpression;
private RenderFragment headerTemplate;
private RenderFragment<RD> cellTemplate;
// Add the column to the parent Grid component.
// OnInitialized is called only once in the component lifecycle
protected override void OnInitialized()
{
OwnerGrid.AddColumn(this);
}
protected override void OnParametersSet()
{
if (lastCompiledExpression != Expression)
{
compiledExpression = Expression?.Compile();
lastCompiledExpression = Expression;
}
}
internal RenderFragment HeaderTemplate
{
get
{
return headerTemplate ??= (builder =>
{
// Use the provided title or infer it from the expression
var title = Title;
if (title == null && Expression != null)
{
title = GetMemberName(Expression);
}
builder.OpenElement(0, "th");
builder.AddContent(1, title);
builder.CloseElement();
});
}
}
internal RenderFragment<RD> CellTemplate
{
get
{
return cellTemplate ??= (rowData => builder =>
{
builder.OpenElement(0, "td");
if (compiledExpression != null)
{
var value = compiledExpression(rowData);
builder.AddContent(1, value?.ToString());
}
else
{
builder.AddContent(2, ChildContent, rowData);
}
builder.CloseElement();
});
}
}
// Get the Member name from an expression.
// (customer => customer.Name) returns "Name"
private static string GetMemberName<T>(Expression<T> expression)
{
return expression.Body switch
{
MemberExpression m => m.Member.Name,
UnaryExpression u when u.Operand is MemberExpression m => m.Member.Name,
_ => throw new NotSupportedException("Expression of type '" + expression.GetType().ToString() + "' is not supported")
};
}
}