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

[Blazor][Question] List of RenderFragments - building dynamic ui #14886

Closed glararan closed 4 years ago

glararan commented 4 years ago

I would like to recreate something I did long time ago in VueJs.

What is it? Well you have component. That component contains (in VueJs) <slots>.

What's the issue? Simply I dont have idea how to make it properly in Blazor.

Current status Component1:

// some code
<td>
  @if(column.Type != null)
  {
    @item.GetType().GetProperty(column.Field).GetValue(item, null)
  }
  else
  {
    @Actions(item)
  }
</td>
// some code
@code
{
[Parameter]
public RenderFragment<dynamic> Actions { get; set; }
}

Component2:

<Component1>
  <Actions>
    // some html
  </Actions>
</Component1>

What I would like to look it like? Component2:

<Component1>
  <Actions Name="Title"> 
    // some html
  </Actions>
  <Actions Name="">
    // some html
  </Actions>
</Component1>

I didnt find a way on Google how to manage it via attributes? Or how to access them via List<RenderFragment>. Can anyone from development team show me the way?

In VueJs it would be <slot v-bind:name="...."></slot>

javiercn commented 4 years ago

@glararan Thanks for contacting us.

Capturing multiple render fragments in a list is not supported. Each render fragment needs to have its own distinctive name.

@rynowak or @SteveSandersonMS can confirm.

If you provide more details on what you are trying to achieve we might be able to guide you in the right direction.

glararan commented 4 years ago

@javiercn Thanks for reply.

I am trying to create "DataTable" component. Each column has property "Slot". If property is true then code renders fragment which I would like associate with attribute or something.

E.g.

<DataTable>
 <Actions Name="Title"><a href="/@context.Id">@context.Name</a></Actions>
 <Actions Name="">/* buttons edit/delete */><Actions>
</DataTable>
<DataTable>
 <Actions Name="Title"><a href="/@context.Id">@context.Name</a></Actions>
 <Actions Name="Category"><span class="category-name">@context</span></Actions>
 <Actions Name="">/* button delete */><Actions>
</DataTable>

What I am trying to say its that component is reusable like advanced table which allows for "slot" edit inside html. The thing is I don't know in future use how users data structure looks like. Sure I can define [Parameter] public RenderFragment<dynamic> Actions; /* instead actions e.g. Title, Category, Buttons */

but then if I would like to add "slot" for "Random" field then I would have to edit DataTable component.

Do you now understand what I am trying to accomplish?

hfmm99 commented 4 years ago

I think you're looking for something like this:

<Grid Data=@GridData>
    <GridColumns>
        <GridColumn Field=@nameof(Product.ProductId) Title="Id" />
        <GridColumn Field=@nameof(Product.ProductName) Title="Product Name" />
        <GridColumn Field=@nameof(Product.UnitPrice) Title="Unit Price">
            <Template>
                @(String.Format("{0:C2}", (context as Product).UnitPrice))
            </Template>
        </GridColumn>
    </GridColumns>
</Grid >

This is already supported by third party Blazor components, but I'm also, trying to learn how to implement something like this. Please let me know if you find out how to do it. Thanks.

glararan commented 4 years ago

I think you're looking for something like this:

<Grid Data=@GridData>
    <GridColumns>
        <GridColumn Field=@nameof(Product.ProductId) Title="Id" />
        <GridColumn Field=@nameof(Product.ProductName) Title="Product Name" />
        <GridColumn Field=@nameof(Product.UnitPrice) Title="Unit Price">
            <Template>
                @(String.Format("{0:C2}", (context as Product).UnitPrice))
            </Template>
        </GridColumn>
    </GridColumns>
</Grid >

This is already supported by third party Blazor components, but I'm also, trying to learn how to implement something like this. Please let me know if you find out how to do it. Thanks.

Wow, actually you give me an idea how to figure it out with far far better codestyle.

To implement my issue I had to use CascadingValue then solution is pretty simple and smart. Case closed. If u need more info how to implement just say :)

los93sol commented 4 years ago

I think you're looking for something like this:

<Grid Data=@GridData>
    <GridColumns>
        <GridColumn Field=@nameof(Product.ProductId) Title="Id" />
        <GridColumn Field=@nameof(Product.ProductName) Title="Product Name" />
        <GridColumn Field=@nameof(Product.UnitPrice) Title="Unit Price">
            <Template>
                @(String.Format("{0:C2}", (context as Product).UnitPrice))
            </Template>
        </GridColumn>
    </GridColumns>
</Grid >

This is already supported by third party Blazor components, but I'm also, trying to learn how to implement something like this. Please let me know if you find out how to do it. Thanks.

Wow, actually you give me an idea how to figure it out with far far better codestyle.

To implement my issue I had to use CascadingValue then solution is pretty simple and smart. Case closed. If u need more info how to implement just say :)

I've also been struggling with this a bit and came up with a few things that do work, but none that produce the markup experience I was trying to accomplish. I'd love to see the solution you came up with to solve this.

glararan commented 4 years ago

I think you're looking for something like this:

<Grid Data=@GridData>
    <GridColumns>
        <GridColumn Field=@nameof(Product.ProductId) Title="Id" />
        <GridColumn Field=@nameof(Product.ProductName) Title="Product Name" />
        <GridColumn Field=@nameof(Product.UnitPrice) Title="Unit Price">
            <Template>
                @(String.Format("{0:C2}", (context as Product).UnitPrice))
            </Template>
        </GridColumn>
    </GridColumns>
</Grid >

This is already supported by third party Blazor components, but I'm also, trying to learn how to implement something like this. Please let me know if you find out how to do it. Thanks.

Wow, actually you give me an idea how to figure it out with far far better codestyle. To implement my issue I had to use CascadingValue then solution is pretty simple and smart. Case closed. If u need more info how to implement just say :)

I've also been struggling with this a bit and came up with a few things that do work, but none that produce the markup experience I was trying to accomplish. I'd love to see the solution you came up with to solve this.

@los93sol DataTableColumn

@implements IDisposable

<th>
    <span>@Name</span>
</th>

@code
{
    [Parameter]
    public string Name { get; set; }

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

    [Parameter]
    public Type Type { get; set; }

    [Parameter]
    public RenderFragment<dynamic> Template { get; set; }

    [CascadingParameter]
    DataTable DataTable { get; set; }

    protected override void OnInitialized()
    {
        DataTable.AddColumn(this);
    }

    public void Dispose()
    {
        DataTable.RemoveColumn(this);
    }
}

DataTable

<div class="col-12">
            <CascadingValue Value="this">
                        <table class="@Class" id="@Id">
                            <thead>
                                <tr>
                                    @ChildContent
                                </tr>
                            </thead>

                            <tbody>
                                @foreach (dynamic item in FilteredData.Skip((Page - 1) * PerPage).Take(PerPage))
                                {
                                <tr>
                                    @foreach (DataTableColumn column in Columns)
                                    {
                                    <td class="align-middle" width="@(column.Type == null ? "1%" : "")">
                                        @if (column.Template is null)
                                        {
                                        @item.GetType().GetProperty(column.Field).GetValue(item, null)
                                        }
                                        else
                                        {
                                        @column.Template(item)
                                        }
                                    </td>
                                    }
                                </tr>
                                }
                            </tbody>
                        </table>
            </CascadingValue>
        </div>

@code
{
    [Parameter]
    public string Class { get; set; }

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

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public List<dynamic> Data
    {
        get => data;
        set
        {
            data = value;
        }
    }

    [Parameter]
    public int PerPage { get; set; } = 25;

    [Parameter]
    public int Page { get; set; } = 1;

    [Parameter]
    public RenderFragment<dynamic> Actions { get; set; }

    List<DataTableColumn> Columns { get; set; } = new List<DataTableColumn>();

    List<dynamic> FilteredData { get; set; }

    List<dynamic> data;

    public void AddColumn(DataTableColumn column)
    {
        Columns.Add(column);
    }

    public void RemoveColumn(DataTableColumn column)
    {
        Columns.Remove(column);
    }
}

Usage:

        <DataTable Class="table" Data="Data">
            <DataTableColumn Name="A" Field="User" Type="typeof(string)"></DataTableColumn>
            <DataTableColumn Name="B" Field="CreatedAt" Type="typeof(DateTime)"></DataTableColumn>
            <DataTableColumn>
                <Template Context="item">
                    <a href="/@item.Id" class="btn">text</a>
                </Template>
            </DataTableColumn>
        </DataTable>

PS: edit DataTable output content

los93sol commented 4 years ago

@glararan Thank you! That was extremely helpful!

hfmm99 commented 4 years ago

I have another working example here: https://github.com/hfmm99/HCM/tree/master/HCM/Client/Components/Grid

I will add a Format property soon, instead of a type property, but that Column Template is a great option.