Blazor QuickGrid Dynamic Columns
See original GitHub issueIs 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:
- Created 6 months ago
- Reactions:1
- Comments:6 (2 by maintainers)
Top Related StackOverflow Question
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.
@VaclavElias I don’t know if you’ll think this meets your needs well enough, but as a workaround you could use a
RenderFragmentmethod to make your logic reusable. For example:With that, you can render a grid like:
You could even make the method
public staticif 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 specifyTGridItem/TPropexplicitly. But it looks like you were already doing that so maybe it’s not such a drawback in your case.