Razor Data Grid Cascading of a List of DataTypes down to Sub Razor Components

29 Views Asked by At

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")
        };
    }
}
0

There are 0 best solutions below