blazor-docs/knowledge-base/grid-dynamic-column-templat...

5.1 KiB

title description type page_title slug position tags ticketid res_type
Dynamic Templates that can be repeated easily How to create dynamic grid column templates for easy reuse and dynamic logic how-to Dynamic Column Templates that can be repeated easily grid-kb-dynamic-columns-templates 1496723 kb

Environment

Product Grid for Blazor,
TreeList for Blazor,
UI for Blazor

Description

When working with templated columns some code is repetitive for each Column that needs to be in a template (we have a lot of templates in each grid) and I want an easier and more reusable way to write templates.

How can I define my template code in C# so it is easier to reuse?

I have dynamic objects with many fields and I want to pass the object and field name to the template so I can extract the information I need.

Solution

While all of this is possible with templates in the markup, some code may get repetitive (especially casting the context) and/or may be harder to debug. You can extract templates to the C# portion of your component by defining the RenderFragment object yourself.

Another option is, of course, to create a child component that receives the context and provides the required casting and rendering on its own. This is a well documented approach in the Blazor framework so this article will focus on the specific way of writing a RenderFragment.

caption Using a RenderFragment in the C# code to create a template by passing a field name to it so you can extract particular information based on that field with Reflection.

@using System.Reflection;

<TelerikGrid Data="@forecasts" Height="550px" Pageable="true">
    <GridColumns>
        <GridColumn Template="@(GetColumnTemplate("Id"))"
            Field="Id" Title="Id" Width="100px" Editable="false" Groupable="false">
        </GridColumn>
        <GridColumn Template="@(GetColumnTemplate("Date"))"
            Field="Date" Width="220px">
        </GridColumn>
        <GridColumn Field="Summary">
        </GridColumn>
    </GridColumns>
</TelerikGrid>

@code {
    List<WeatherForecast> forecasts { get; set; }

    protected override void OnInitialized()
    {
        GetForecasts();
    }

    void GetForecasts()
    {
        forecasts = Enumerable.Range(1, 20).Select(x => new WeatherForecast()
        {
            Id = x,
            Date = DateTime.Now.AddDays(x),
            Summary = "Summary" + x
        }).ToList();
    }

    public class WeatherForecast
    {
        public int Id { get; set; }
        public DateTime Date { get; set; }
        public string Summary { get; set; }
    }

    public RenderFragment<object> GetColumnTemplate(string propName)
    {
        // Define the RenderFragment in your code
        // Its type matches the type of the Grid context - an object
        // The same as if you were defining it in the markup
        
        // The syntax for writing a RenderFragment is rather specific, note the lambda expressions
        
        RenderFragment<object> ColumnTemplate = context => __builder =>
        {
            // in this example we pass the property name from the grid declaration
            // and we use reflection to extract the needed data. You don't have to
            // If you know the field or the type, you can cast and simplify this code as needed
            
            PropertyInfo propertyInfo = context.GetType().GetProperty(propName);

            var propType = propertyInfo.PropertyType;

            var propValue = propertyInfo.GetValue(context);

            if (propType == typeof(int))
            {
                <div style="text-align: right;">
                    @propValue
                </div>
            }
            else
            {
                @propValue
            }
        };

        return ColumnTemplate;
    }
}

Notes

You can combine this approach with a loop over a column descriptor so you can create many columns based on what you seek. Of course, you can declare the template in the markup too.

Generally, casting of the Template context is required because an object is passed to the RenderFragment. Currently, there is no option to pass the typeparam of a parent component down to its children, and that is why we are using an object instead of the TItem that you have bound the Grid to: https://github.com/dotnet/aspnetcore/issues/7268

For example, the [RowTemplate]({%slug grid-templates-row%}) of the Grid uses RenderFragment<TItem> where no additional casting is needed, but the [cell template]({%slug grid-templates-column%}) is a child component of the row so it can only get an object.

To summarize, once the framework provides the ability to pass the typeparam to child components, there will be no need to cast the context to the model and the code will look cleaner. Until then, there is no other way to avoid those lines of casting.