dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.19k stars 9.93k forks source link

[Help][Blazor] how to manage array of child parameters ? #17092

Closed julienGrd closed 4 years ago

julienGrd commented 4 years ago

Hello guys, I try to implement un grid component but i meet a problem with the logic of blazor components.

I actually manage my grid like this

<MyGrid>
<GridHeader>
<th scope="col">Nom</th>
 <th scope="col">Statut</th>
</GridHeader>
<GridBody Context="child">
<td>@child.NomPrenom</td>
<td>@child.LibStatut</td>
</GridBody>
</MyGrid>
and on the Mygrid component like this
[Parameter]
        public RenderFragment GridHeader { get; set; }
[Parameter]
        public RenderFragment<TableItem> GridBody { get; set; }
[Parameter]
        public IEnumerable<TableItem> Items { get; set; }

<thead>
                    <tr>
                        @GridHeader
                    </tr>
                </thead>
@foreach (var element in Items)
{
    <tr
        @GridRow(element)
    </tr>
}

I want to change this for a column based system, like this

<MyGrid>
<GridColumns Context="child">
<GridColumn Header="Nom">
@child.NomPrenom
</GridColumn>
<GridColumn Header="Statut">
@child.LibStatut
</GridColumn>
</GridColumns>
</MyGrid>

and on the grid component, iterate on the columns to first render the header and then iterate on the columns and item to render the table body.

I surprisely don't find example like this on internet and i don't find the good way to achieve that.

It sem its it possible because i find some example like this here : https://blazor.syncfusion.com/demos/Grid/DefaultFunctionalities, but unfortunately i don't have access to source code..

Do you have idea on how i can achieve that ?

Thanks for your help !

javiercn commented 4 years ago

Thanks for contacting us @julienGrd

It looks like this is a question about how to use ASP.NET Core Blazor. While we do our best to look through all the issues filed here, to get a faster response we suggest posting your questions to StackOverflow using the asp.net-core-blazor tag.

mrpmorris commented 4 years ago

The trick is to set the grid as a cascading value, and have your headers register themselves by calling a method on the grid.

I give an example of doing this that show how to create a tab control.

https://blazor-university.com/templating-components-with-renderfragements/creating-a-tabcontrol/

julienGrd commented 4 years ago

thanks @mrpmorris i begin to see the tricks !

but in my case, i have difficulty to transpose your example in my context.

My main problem is my template need to be render for each row, so i will add too many times the components is her parents. On the other way, with data empty, i don't have way to show the header...

my templates are generic too, it make the thing more tricky

so i change the logic to render the header on a classic way and then render the body manually by iterate on my items

I finally arrived to a quite good solution, i have only one problem left

this is my actual code

//on the caller
<Grid Items="this.Model.ListEnfantsSearch">

                <Columns >
                    <GeckosGridColumn Header="Nom" T="@(Is.GeckosSL.EntityModel.Enfant)">
                        <Body Context="child" >
                            @child.NomPrenom
                        </Body>
                    </GeckosGridColumn>
                </Columns>

the grid
<table class="table table-sm table-borderless @(TableClass ?? "")" id="@this.Id">
        <thead class="@(StyleHeader ? "styled" : "no-styled")">

            <tr>
                <CascadingValue Value="this">
                    @(Columns)
                </CascadingValue>
            </tr>
        </thead>
        <tbody class="group-element @(Disabled ? "disabled": "")">
            @if (Items != null)
            {

                @foreach (var item in this.Items)
                {
                    <tr>
                        @foreach (var c in this.InternalColumns)
                        {
                            <td scope="col">@(c.Body(item))</td>
                        }
                    </tr>

                }
            }

        </tbody>
    </table>
[Parameter]
        public RenderFragment Columns { get; set; }
public List<GeckosGridColumnViewModel<TableItem>> InternalColumns = new List<GeckosGridColumnViewModel<TableItem>>();
        internal void AddColumn(GeckosGridColumnViewModel<TableItem> column)
        {
            InternalColumns.Add(column);
            this.LaunchStateHasChanged();
        }

and finally the Column component
<th>@this.Header</th>
[CascadingParameter]
        private BaseGridComponent<T> Parent { get; set; }

        [Parameter]
        public RenderFragment<T> Body { get; set; }

        [Parameter]
        public string Header { get; set; }

        protected override void OnInitialized()
        {
            if (Parent == null)
                throw new ArgumentNullException(nameof(Parent));
            Parent.AddColumn(this);
            base.OnInitialized();

        }

As you can see, i need to specify the type manually on the column (because i use the Context to specify a generic body), i would prefer the column retrieve the type of the grid container, i don't know how to make that, so if you have idea i take it !

thanks for your help !

mrpmorris commented 4 years ago

I don't think that is currently possible. It would be good if Blazor would allow us to specify a [Parameter] property type that is an Array of components like this - so we could then enforce generic types on the embedded children.

public class Grid<T> : ComponentBase
{
  [ViewChildren]
  public GridColumn<T>[] Columns { get; set; }
}

public class GridColumn<T> : ComponentBase
{
}
<Grid Data=People>
  <Columns>
    <Column>@context.Name</Column>
  </Columns>
</Grid>
julienGrd commented 4 years ago

yeah i try different solution but impossible to make better than declare directly the type (i try some things like T="@(this.Model.ListEnfantsSearch.GetType().GetGenericArguments()[0])" but i ended with error in razor.g.cs generated file.

So for the moement i will keep my solution, it's too bad because im really close to a perfect solution.

It can be nice if blazor team make something like you propose.

i think the post can stay opened until a better solution arrived.

Thanks again for your help @mrpmorris , i will never ended this without you

mrpmorris commented 4 years ago

@julienGrd perhaps you could create a new ticket as a feature request, requesting some way to define strongly-typed child content.

julienGrd commented 4 years ago

you are right, it's done (https://github.com/aspnet/AspNetCore/issues/17194), i close this one !