Blazor QuickGrid Dynamic Columns

See original GitHub issue

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

I want do add my columns dynamically, as they are used in multiple places across the project, including these columns are optional.

The current QuickGrid implementation is pretty much solid for lots of scenarios, but usually whoever is using any grids, they might be using many of them.

Currently suggested solution (https://aspnet.github.io/quickgridsamples/columns)

<QuickGrid Items="@Data.People">
    <PropertyColumn Title="ID" Property="@(c => c.PersonId)" Sortable="true" />
    @if (showName)
    {
        <TemplateColumn Title="Name">
            <strong>@context.LastName</strong>, @context.FirstName
        </TemplateColumn>
    }
    @if (showBirthDate)
    {
        <PropertyColumn Title="Birth date" Property="@(c => c.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
    }
</QuickGrid>

Describe the solution you’d like

Some options are described below but I believe someone can come up even with better solutions.

Option 1 - Expose Columns property, which could be used to (re)assign columns

<QuickGrid Items="@Data.People" Columns="@MyColumns"></QuickGrid>

Option 2 - I am messing/prototyping around currently, is allowing a component inside QuickGrid, which would wrap my different types of columns implementation.

The example below defines an empty column; and QuickGridColumns component defines additional columns, which are generated dynamically through custom columns and existing QuickGrid colums.

<QuickGrid Items="@_corporateUsers.AsQueryable()">
    <EmptyColumn Title="#" TGridItem="CorporateUserDto" Property="new()" />
    @* This is not working *@
    <QuickGridColumns TGridItem="CorporateUserDto" ColumnManager="_columnManager" />
    @* I have to use this instead everywhere 😭😭😭😭😭😭😭😭😭 *@    
    @foreach (var col in _columnManager.Get())
    {
        @if (col.ColumnType == typeof(TickColumn<TGridItem>))
        {
            <TickColumn TGridItem="TGridItem"
            Property="@col.Property"
            Sortable="true"
            Title="@col.Title"
            Align="@col.Align" />
        }
        else if (col.ColumnType == typeof(ImageColumn<TGridItem>))
        {
            <ImageColumn TGridItem="TGridItem"
            Property="@col.Property" />
        }
        else
        {
            <PropertyColumn Title="@col.Title"
                Property="@col.Property"
                Sortable="@col.Sortable"
                Align="@col.Align" />
        }
    }
</QuickGrid>

QuickGridColumns.razor

@* This solution is not working for some reason, inside the QuickGrid, it doesn't render currently so I have to repeat the foreach loop in every grid 😭🤦‍♂️ *@
@typeparam TGridItem
@if (ColumnManager is not null)
{
    @foreach (var col in ColumnManager.Get())
    {
        @if (col.ColumnType == typeof(TickColumn<TGridItem>))
        {
            <TickColumn TGridItem="TGridItem"
            Property="@col.Property"
            Sortable="true"
            Title="@col.Title"
            Align="@col.Align" />
        }
        else if (col.ColumnType == typeof(ImageColumn<TGridItem>))
        {
            <ImageColumn TGridItem="TGridItem"
            Property="@col.Property" />
        }
        else
        {
            <PropertyColumn Title="@col.Title"
                Property="@col.Property"
                Sortable="@col.Sortable"
                Align="@col.Align" />
        }
    }
}
@code {
    [Parameter] public ColumnManager<TGridItem>? ColumnManager { get; set; }

    protected override void OnParametersSet()
    {
        StateHasChanged();
    }
}

ColumnManager

Here you can see how easily I can create new columns, including adding predefined, strongly typed columns e.g AddConsultantName()

public class ColumnManager<TGridItem>
{
    public readonly List<ColumnTemplate<TGridItem>> Columns = new();

    /// <summary>
    /// Returns visible columns
    /// </summary>
    /// <returns></returns>
    public IEnumerable<ColumnTemplate<TGridItem>> Get() => Columns.Where(w => w.Visible);

    public void Add(ColumnTemplate<TGridItem>? column = default)
    {
        if (column == null) return;

        if (string.IsNullOrWhiteSpace(column.Title))
        {
            column.Title = GetPropertyName(column.Property) ?? "Title n/a";
        }

        Columns.Add(column);

        column.Id = Columns.Count;
    }

    public void AddSimple(Expression<Func<TGridItem, object?>> expression)
    {
        Add(new() { Property = expression });
    }

    public void AddTickColumn(Expression<Func<TGridItem, object?>> expression, string? title = null, Align align = Align.Center)
    {
        Add(new() { Property = expression, ColumnType = typeof(TickColumn<TGridItem>), Title = title, Align = align });
    }

    public void AddConsultantName() => Add(new ColumnTemplate<TGridItem>
    {
        Title = "Consultant Name",
        FullTitle = "Consultant Name",
        Property = s => s == null ? string.Empty : ((ICorporateUserDto)s).ConsultantName
    });

    public void AddConsultantId() => Add(new ColumnTemplate<TGridItem>
    {
        Title = "Consult.Id",
        FullTitle = "Consultant Id",
        Property = s => s == null ? string.Empty : ((ICorporateUserDto)s).ConsultantId
    });

    public void AddDateAdded() => Add(new ColumnTemplate<TGridItem>
    {
        Title = "Date Added",
        FullTitle = "Date Added",
        Property = s => s == null ? default : ((IDateAdded)s).DateAdded
    });

    public void AddCreatedOn(string? format = null) => Add(new ColumnTemplate<TGridItem>
    {
        Title = "Date Added",
        FullTitle = "Date Added",
        //Format = "dd/MM/yyyy",
        Property = s => s == null ? default : ((ICreatedOn)s).CreatedOn
    });

    private static string? GetPropertyName(Expression<Func<TGridItem, object?>>? expression)
    {
        if (expression is null) return null;

        MemberExpression? memberExpression;

        if (expression.Body is UnaryExpression unaryExpression)
        {
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = expression.Body as MemberExpression;
        }

        if (memberExpression == null)
        {
            throw new ArgumentException($"Expression '{expression}' refers to a method, not a property.");
        }

        if (!(memberExpression.Member is PropertyInfo propertyInfo))
        {
            throw new ArgumentException($"Expression '{expression}' refers to a field, not a property.");
        }

        return propertyInfo.Name;
    }
}

ColumnTemplate

I wish I could use/inherit from existing PropertyColumn but currently this QuickGrid PropertyColumn should be used only from editor?

public class ColumnTemplate<TGridItem>
{
    // We need id so we could list all columns e.g. as checkbox and select which one is visible
    public int Id { get; set; }
    public string ColumnId => $"column-{Id}";
    public bool Visible { get; set; } = true;
    public bool Sortable { get; set; } = true;
    public string? Title { get; set; } = string.Empty;
    public string? FullTitle
    {
        get => string.IsNullOrWhiteSpace(_fullTitle) ? Title : _fullTitle;
        set => _fullTitle = value;
    }
    public Align Align { get; set; }
    public string? Format { get; set; }
    public Expression<Func<TGridItem, object?>>? Property { get; set; }

    public Type ColumnType { get; set; } = typeof(PropertyColumn<TGridItem, object?>);

    private string? _fullTitle;
}

Final result

You can probably see how easy it is to add columns, have availability to choose dynamically which columns should be added/updated, including an option to create own logic to filter and sort columns.

<QuickGrid Items="@_corporateUsers.AsQueryable()">
    <QuickGridColumns TGridItem="CorporateUserDto" ColumnManager="_columnManager" />
</QuickGrid>
@code {
    private ColumnManager<CorporateUserDto> _columnManager = new();
    protected override async Task OnInitializedAsync()
    {
        _columnManager.Add(new() { Property = p => p.Id, Title = "User Id", Align = Align.Center });
        _columnManager.AddSimple(p => p.Name);
        _columnManager.AddTickColumn(p => p.IsEnabled, "Enabled");
        _columnManager.AddDateAdded();
        _columnManager.AddConsultantName();
    }
}

Additional context

No response

Issue Analytics

  • State:open
  • Created 6 months ago
  • Reactions:1
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
VaclavEliascommented, Mar 19, 2023

Working prototype here https://github.com/VaclavElias/QuickGridToolkit in case anyone would like to test it. I will keep adding some additional functionality. I hope this will help you to decide with the future and upcoming QuickGrid updates.

image
1reaction
SteveSandersonMScommented, Mar 16, 2023

@VaclavElias I don’t know if you’ll think this meets your needs well enough, but as a workaround you could use a RenderFragment method to make your logic reusable. For example:

@code {
    private RenderFragment Columns() => @<text>
        <PropertyColumn TGridItem="Person" TProp="int" Property="@(p => p.PersonId)" Sortable="true" />
        @if (showName)
        {
            <PropertyColumn TGridItem="Person" TProp="string" Property="@(p => p.Name)" Sortable="true" />
        }
        <PropertyColumn TGridItem="Person" TProp="DateOnly" Property="@(p => p.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
    </text>;
}

With that, you can render a grid like:

<QuickGrid Items="@people">
    @Columns()
</QuickGrid>

You could even make the method public static if you want to share it across components.

Full example at https://gist.github.com/SteveSandersonMS/e487f2159868484d63c89dc8ea219e64

One drawback of this approach is you don’t get generic type inference because the columns aren’t actually enclosed in the QuickGrid, so you have to specify TGridItem/TProp explicitly. But it looks like you were already doing that so maybe it’s not such a drawback in your case.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Columns - QuickGrid
Columns. QuickGrid has two built-in column types, PropertyColumn and TemplateColumn . You can also create your own column types by subclassing ColumnBase ....
Read more >
Looping over QuickGrid-columns instead of hardcoding ...
The Items I am trying to loop through is a IQueryable<RowDTO> with the RowDTO just holding information on each row of data. The...
Read more >
ASP.NET Core Blazor QuickGrid component
The QuickGrid component is a Razor component for quickly and efficiently displaying data in tabular form.
Read more >
Easy Grids with QuickGrid - Filtering, Paging and Sorting!
Get my Blazor course with a discount: https://felipe-gavilan.azurewebsites.net/api/Redireccion?curso=programming-in- blazor -eng Get my Entity ...
Read more >
QuickGrid for Blazor - Get started
The sample seems to have column resizing, but it doesn't actually resize the columns? Upvote 1
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found