Aspnetcore: [Blazor][Question] List of RenderFragments - building dynamic ui

Created on 10 Oct 2019  路  8Comments  路  Source: dotnet/aspnetcore

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>

area-blazor question

Most helpful comment

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

All 8 comments

@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.

@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?

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.

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 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.

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

@glararan Thank you! That was extremely helpful!

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

KerolosMalak picture KerolosMalak  路  269Comments

natemcmaster picture natemcmaster  路  213Comments

pekkah picture pekkah  路  200Comments

glennc picture glennc  路  117Comments

rmarinho picture rmarinho  路  78Comments