-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
Background and Motivation
The QuickGrid package aims to provide a simple and performant data grid component for Blazor. QuickGrid provides some basic functionality such as sorting, paging, styling and virtualization but aims to provide extensibility to developers that want to build on it.
Even through productization, the main goals of QuickGrid are:
- To provide a reference architecture for a performant data grid component, which others may build on or copy.
- To provide a convenient and reliable standalone free data grid component to Blazor developers who don't require advanced grid features.
QuickGrid can show data from three main sources:
- In-memory IQueryable
- EF Core IQueryable
- Remote data
Proposed API
The following propose new API in 2 new namespaces:
namespace Microsoft.AspNetCore.Components.QuickGrid;
public partial class QuickGrid<TGridItem> : IAsyncDisposable
{
public QuickGrid();
public IQueryable<TGridItem>? Items { get; set; }
public GridItemsProvider<TGridItem>? ItemsProvider { get; set; }
public string? Class { get; set; }
public string? Theme { get; set; }
public RenderFragment? ChildContent { get; set; }
public bool Virtualize { get; set; }
public float ItemSize { get; set; }
public bool ResizableColumns { get; set; }
public Func<TGridItem, object> ItemKey { get; set; }
public PaginationState? Pagination { get; set; }
protected override Task OnParametersSetAsync();
protected override async Task OnAfterRenderAsync(bool firstRender);
public Task SortByColumnAsync(ColumnBase<TGridItem> column, SortDirection direction);
public void ShowColumnOptions(ColumnBase<TGridItem> column);
public async Task RefreshDataAsync();
public async ValueTask DisposeAsync();
}
public enum Align
{
Left,
Center,
Right,
}
public abstract partial class ColumnBase<TGridItem>
{
public ColumnBase();
public string? Title { get; set; }
public string? Class { get; set; }
public Align Align { get; set; }
public RenderFragment<ColumnBase<TGridItem>>? HeaderTemplate { get; set; }
public RenderFragment? ColumnOptions { get; set; }
public bool? Sortable { get; set; }
public SortDirection? IsDefaultSort { get; set; }
public RenderFragment<PlaceholderContext>? PlaceholderTemplate { get; set; }
public QuickGrid<TGridItem> Grid;
protected internal abstract void CellContent(RenderTreeBuilder builder, TGridItem item);
protected internal RenderFragment HeaderContent { get; protected set; }
protected virtual bool IsSortableByDefault();
protected override void BuildRenderTree(RenderTreeBuilder builder);
}
public delegate ValueTask<GridItemsProviderResult<TGridItem>> GridItemsProvider<TGridItem>(
GridItemsProviderRequest<TGridItem> request);
public readonly struct GridItemsProviderRequest<TGridItem>
{
public int StartIndex { get; }
public int? Count { get; }
public ColumnBase<TGridItem>? SortByColumn { get; }
public bool SortByAscending { get; }
public CancellationToken CancellationToken { get; }
public IQueryable<TGridItem> ApplySorting(IQueryable<TGridItem> source);
public IReadOnlyCollection<(string PropertyName, SortDirection Direction)> GetSortByProperties();
}
public struct GridItemsProviderResult<TGridItem>
{
public GridItemsProviderResult(ICollection<TGridItem> items, int totalItemCount);
public ICollection<TGridItem> Items { get; set; }
public int TotalItemCount { get; set; }
}
public static class GridItemsProviderResult
{
public static GridItemsProviderResult<TGridItem> From<TGridItem>(ICollection<TGridItem> items, int totalItemCount);
}
public sealed class GridSort<TGridItem>
{
public static GridSort<TGridItem> ByAscending<U>(Expression<Func<TGridItem, U>> expression);
public static GridSort<TGridItem> ByDescending<U>(Expression<Func<TGridItem, U>> expression);
public static GridSort<TGridItem> ThenAscending<U>(Expression<Func<TGridItem, U>> expression);
public static GridSort<TGridItem> ThenDescending<U>(Expression<Func<TGridItem, U>> expression);
}
public interface ISortBuilderColumn<TGridItem>
{
public GridSort<TGridItem>? SortBuilder { get; }
}
public class PaginationState
{
public int ItemsPerPage { get; set; }
public int CurrentPageIndex { get; }
public int? TotalItemCount { get; }
public int? LastPageIndex;
public event EventHandler<int?>? TotalItemCountChanged;
public override int GetHashCode();
public Task SetCurrentPageIndexAsync(int pageIndex);
}
public partial class Paginator : IDisposable
{
public Paginator();
public PaginationState Value { get; set; }
public RenderFragment? SummaryTemplate { get; set; }
public void Dispose();
protected override void OnParametersSet();
protected override void BuildRenderTree(RenderTreeBuilder builder);
}
public class PropertyColumn<TGridItem, TProp> : ColumnBase<TGridItem>, ISortBuilderColumn<TGridItem>
{
public Expression<Func<TGridItem, TProp>> Property { get; set; }
public string? Format { get; set; }
GridSort<TGridItem>? ISortBuilderColumn<TGridItem>.SortBuilder;
protected override void OnParametersSet();
protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item);
}
public enum SortDirection
{
Ascending,
Descending,
Auto,
}
public class TemplateColumn<TGridItem> : ColumnBase<TGridItem>, ISortBuilderColumn<TGridItem>
{
public RenderFragment<TGridItem> ChildContent { get; set; }
public GridSort<TGridItem>? SortBy { get; set; }
GridSort<TGridItem>? ISortBuilderColumn<TGridItem>.SortBuilder;
protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item);
protected override bool IsSortableByDefault();
}
namespace Microsoft.AspNetCore.Components.QuickGrid.Infrastructure;
public sealed class ColumnsCollectedNotifier<TGridItem> : IComponent
{
public void Attach(RenderHandle renderHandle);
public Task SetParametersAsync(ParameterView parameters);
}
public sealed class Defer : ComponentBase
{
public RenderFragment? ChildContent { get; set; }
}
public static class EventHandlers
{
}
public interface IAsyncQueryExecutor
{
bool IsSupported<T>(IQueryable<T> queryable);
Task<int> CountAsync<T>(IQueryable<T> queryable);
Task<T[]> ToArrayAsync<T>(IQueryable<T> queryable);
}
New API in an existing namespace:
namespace Microsoft.Extensions.DependencyInjection;
+public static class EntityFrameworkAdapterServiceCollectionExtensions
+{
+ public static void AddQuickGridEntityFrameworkAdapter(this IServiceCollection services);
+}
Usage Examples
For a complete set of interactive examples, please see https://aspnet.github.io/quickgridsamples/sample.
Risks
This was originally an experimental package, and the only changes since the first draft of this proposal were to make it compliant with the aspnetcore repo's analyzer/build configuration. This issue will be marked with the appropriate label when ready, but in the meantime feel free to review the attached PR if you are able.
The only other risk I can think of is the cost of additional E2E tests on our CI. The testing story for this package should be designed with that consideration.