Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QuickGrid experimental package #517

Merged
merged 6 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions eng/Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
<ProjectToBuild Include="$(MSBuildThisFileDirectory)..\src\BlazorCustomElements\BlazorCustomElements.sln" />
<ProjectToBuild Include="$(MSBuildThisFileDirectory)..\src\ClientAssets\ClientAssets.sln" />
<ProjectToBuild Include="$(MSBuildThisFileDirectory)..\src\BlazorWebAssemblyCustomInitialization\BlazorWebAssemblyCustomInitialization.sln" />
<ProjectToBuild Include="$(MSBuildThisFileDirectory)..\src\QuickGrid\QuickGrid.sln" />
</ItemGroup>
</Project>
60 changes: 60 additions & 0 deletions src/QuickGrid/QuickGrid.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32408.312
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A49CC7EC-8E67-4459-9644-250168A506B7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.QuickGrid", "src\Microsoft.AspNetCore.Components.QuickGrid\Microsoft.AspNetCore.Components.QuickGrid.csproj", "{04DE6012-E837-48D2-9125-41976C7BA326}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{8CE6BA28-B2D9-43D6-A1ED-1DE3CF5A1372}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickGridSamples.Core", "samples\QuickGridSamples.Core\QuickGridSamples.Core.csproj", "{E388368A-5A7E-4E6B-A0B6-9A56FCE79974}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickGridSamples.WebAssembly", "samples\QuickGridSamples.WebAssembly\QuickGridSamples.WebAssembly.csproj", "{848D1378-0889-47BA-A591-1B1F4EE6F09B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickGridSamples.Server", "samples\QuickGridSamples.Server\QuickGridSamples.Server.csproj", "{D6A9F84D-07D2-4BE5-BE70-50230C9CE280}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter", "src\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter.csproj", "{BDEA56A6-2940-4274-AE79-BA1550171954}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{04DE6012-E837-48D2-9125-41976C7BA326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04DE6012-E837-48D2-9125-41976C7BA326}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04DE6012-E837-48D2-9125-41976C7BA326}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04DE6012-E837-48D2-9125-41976C7BA326}.Release|Any CPU.Build.0 = Release|Any CPU
{E388368A-5A7E-4E6B-A0B6-9A56FCE79974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E388368A-5A7E-4E6B-A0B6-9A56FCE79974}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E388368A-5A7E-4E6B-A0B6-9A56FCE79974}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E388368A-5A7E-4E6B-A0B6-9A56FCE79974}.Release|Any CPU.Build.0 = Release|Any CPU
{848D1378-0889-47BA-A591-1B1F4EE6F09B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{848D1378-0889-47BA-A591-1B1F4EE6F09B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{848D1378-0889-47BA-A591-1B1F4EE6F09B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{848D1378-0889-47BA-A591-1B1F4EE6F09B}.Release|Any CPU.Build.0 = Release|Any CPU
{D6A9F84D-07D2-4BE5-BE70-50230C9CE280}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6A9F84D-07D2-4BE5-BE70-50230C9CE280}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6A9F84D-07D2-4BE5-BE70-50230C9CE280}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6A9F84D-07D2-4BE5-BE70-50230C9CE280}.Release|Any CPU.Build.0 = Release|Any CPU
{BDEA56A6-2940-4274-AE79-BA1550171954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDEA56A6-2940-4274-AE79-BA1550171954}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDEA56A6-2940-4274-AE79-BA1550171954}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDEA56A6-2940-4274-AE79-BA1550171954}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{04DE6012-E837-48D2-9125-41976C7BA326} = {A49CC7EC-8E67-4459-9644-250168A506B7}
{E388368A-5A7E-4E6B-A0B6-9A56FCE79974} = {8CE6BA28-B2D9-43D6-A1ED-1DE3CF5A1372}
{848D1378-0889-47BA-A591-1B1F4EE6F09B} = {8CE6BA28-B2D9-43D6-A1ED-1DE3CF5A1372}
{D6A9F84D-07D2-4BE5-BE70-50230C9CE280} = {8CE6BA28-B2D9-43D6-A1ED-1DE3CF5A1372}
{BDEA56A6-2940-4274-AE79-BA1550171954} = {A49CC7EC-8E67-4459-9644-250168A506B7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B9EFF0B9-F341-4C6C-B543-AD97D127ED98}
EndGlobalSection
EndGlobal
25 changes: 25 additions & 0 deletions src/QuickGrid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# QuickGrid

QuickGrid is a simple and fast data grid for Blazor, with:

* **A clean and comfortable Blazor-native API**, with extensibility for custom filtering, sorting, etc.
* **Strong performance** when rendering large amounts of data in any Blazor hosting environment (Server, WebAssembly, .NET MAUI).

It is not intended to replace more full-featured data grids, such as those from commercial component vendors.

### This is experimental

QuickGrid doesn't ship as a supported part of Blazor. The purpose of this experimental package is:

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

Even though it doesn't have Microsoft's official support, you are welcome to use it in production like other open source libraries from third parties.

## Installation

TODO

## Usage

TODO
10 changes: 10 additions & 0 deletions src/QuickGrid/samples/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<!-- Chain up to the next file (can be copy-pasted to either Directory.Build.props or Directory.Build.targets) -->
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory).., '$(MSBuildThisFileName)$(MSBuildThisFileExtension)'))\$(MSBuildThisFileName)$(MSBuildThisFileExtension)" />

<PropertyGroup>
<IsPackable>false</IsPackable>
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
</PropertyGroup>
</Project>
12 changes: 12 additions & 0 deletions src/QuickGrid/samples/QuickGridSamples.Core/App.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
8 changes: 8 additions & 0 deletions src/QuickGrid/samples/QuickGridSamples.Core/Models/Country.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace QuickGridSamples.Core.Models;

public class Country
{
public string Code { get; set; }
public string Name { get; set; }
public Medals Medals { get; set; }
}
10 changes: 10 additions & 0 deletions src/QuickGrid/samples/QuickGridSamples.Core/Models/IDataService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Components.QuickGrid;

namespace QuickGridSamples.Core.Models;

public interface IDataService
{
Task<GridItemsProviderResult<Country>> GetCountriesAsync(int startIndex, int? count, string sortBy, bool sortAscending, CancellationToken cancellationToken);

IQueryable<Country> Countries { get; }
}
10 changes: 10 additions & 0 deletions src/QuickGrid/samples/QuickGridSamples.Core/Models/Medals.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace QuickGridSamples.Core.Models;

public class Medals
{
public int Gold { get; set; }
public int Silver { get; set; }
public int Bronze { get; set; }

public int Total => Gold + Silver + Bronze;
}
36 changes: 36 additions & 0 deletions src/QuickGrid/samples/QuickGridSamples.Core/Pages/EF.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@page "/entityframework"
@inject IDataService DataService

<div class="grid">
<QuickGrid Items="@FilteredItems" ResizableColumns="true" Pagination="@pagination">
<TemplateColumn Title="Rank" SortBy="@rankSort" Align="Align.Center" IsDefaultSort="SortDirection.Ascending">
<img class="flag" src="flags/@(context.Code).svg" />
</TemplateColumn>
<PropertyColumn Property="@(c => c.Name)" Sortable="true">
<ColumnOptions>
<input type="search" autofocus @bind="nameFilter" @bind:event="oninput" />
</ColumnOptions>
</PropertyColumn>
<PropertyColumn Property="@(c => c.Medals.Gold)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Silver)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Bronze)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Total)" Align="Align.Right" />
</QuickGrid>
<Paginator Value="@pagination" />
</div>

@code {
PaginationState pagination = new PaginationState { ItemsPerPage = 15 };
string nameFilter = string.Empty;

// Note: this pattern is a little dubious because it means we're supplying a different data source
// on every render, so the grid will re-query on every render even if the filter criteria hasn't changed.
// Once we have @bind:after, it would be possible to use that to update an _items field, meaning that
// we'd only cause a re-query when the filter actually changes.
IQueryable<Country> FilteredItems => DataService.Countries.Where(x => x.Name.ToLower().Contains(nameFilter.ToLower()));

GridSort<Country> rankSort = GridSort<Country>
.ByDescending(x => x.Medals.Gold)
.ThenDescending(x => x.Medals.Silver)
.ThenDescending(x => x.Medals.Bronze);
}
23 changes: 23 additions & 0 deletions src/QuickGrid/samples/QuickGridSamples.Core/Pages/EF.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.flag {
width: 2rem;
height: 1.5rem;
}

.grid {
display: inline-flex;
flex-direction: column;
}

.grid ::deep tr {
height: 1.8rem;
}

.grid ::deep th:nth-child(2) {
width: 15rem;
}

.grid ::deep th:nth-child(2) .col-options-button:before {
/* Magnifying glass */
content: '\1F50D';
filter: saturate(0) contrast(10);
}
34 changes: 34 additions & 0 deletions src/QuickGrid/samples/QuickGridSamples.Core/Pages/InMemory.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@page "/"
@inject IDataService DataService

<div class="grid">
<QuickGrid Items="@FilteredItems" ResizableColumns="true" Pagination="@pagination">
<TemplateColumn Title="Rank" SortBy="@rankSort" Align="Align.Center" IsDefaultSort="SortDirection.Ascending">
<img class="flag" src="flags/@(context.Code).svg" />
</TemplateColumn>
<PropertyColumn Property="@(c => c.Name)" Sortable="true">
<ColumnOptions>
<input type="search" autofocus @bind="nameFilter" @bind:event="oninput" />
</ColumnOptions>
</PropertyColumn>
<PropertyColumn Property="@(c => c.Medals.Gold)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Silver)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Bronze)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Total)" Sortable="true" Align="Align.Right" />
</QuickGrid>
<Paginator Value="@pagination" />
</div>

@code {
PaginationState pagination = new PaginationState { ItemsPerPage = 15 };
IQueryable<Country> items;
string nameFilter = string.Empty;
GridSort<Country> rankSort = GridSort<Country>.ByDescending(x => x.Medals.Gold).ThenDescending(x => x.Medals.Silver).ThenDescending(x => x.Medals.Bronze);

IQueryable<Country> FilteredItems => items?.Where(x => x.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase));

protected override async Task OnInitializedAsync()
{
items = (await DataService.GetCountriesAsync(0, null, null, true, CancellationToken.None)).Items.AsQueryable();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.flag {
width: 2rem;
height: 1.5rem;
}

.grid {
display: inline-flex;
flex-direction: column;
}

.grid ::deep tr {
height: 1.8rem;
}

.grid ::deep th:nth-child(2) {
width: 15rem;
}

.grid ::deep th:nth-child(2) .col-options-button:before {
/* Magnifying glass */
content: '\1F50D';
filter: saturate(0) contrast(10);
}
28 changes: 28 additions & 0 deletions src/QuickGrid/samples/QuickGridSamples.Core/Pages/Provider.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@page "/provider"
@inject IDataService DataService

<div class="grid">
<QuickGrid ItemsProvider="@itemsProvider" ResizableColumns="true" Pagination="@pagination">
<TemplateColumn Title="Flag"><img class="flag" src="flags/@(context.Code).svg" /></TemplateColumn>
<PropertyColumn Class="country-name" Property="@(c => c.Name)" Sortable="true" IsDefaultSort="SortDirection.Ascending" />
<PropertyColumn Property="@(c => c.Medals.Gold)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Silver)" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Bronze)" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Total)" Align="Align.Right" />
</QuickGrid>
<Paginator Value="@pagination" />
</div>

@code {
PaginationState pagination = new PaginationState { ItemsPerPage = 15 };
GridItemsProvider<Country> itemsProvider;

protected override void OnInitialized()
{
itemsProvider = async req =>
{
var sortBy = req.GetSortByProperties().SingleOrDefault();
return await DataService.GetCountriesAsync(req.StartIndex, req.Count, sortBy.PropertyName, sortBy.Direction == SortDirection.Ascending, req.CancellationToken);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.flag {
width: 2rem;
height: 1.5rem;
}

.grid {
display: inline-flex;
flex-direction: column;
}

.grid ::deep tr {
height: 1.8rem;
}

.grid ::deep thead .country-name {
width: 15rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@page "/virtualized"
@inject IDataService DataService

<div class="grid" tabindex="-1">
<QuickGrid ItemsProvider="@itemsProvider" Virtualize="true" ItemSize="30">
<TemplateColumn Title="Flag"><img class="flag" src="flags/@(context.Code).svg" /></TemplateColumn>
<PropertyColumn Class="country-name" Property="@(c => c.Name)" Sortable="true" IsDefaultSort="SortDirection.Ascending" />
<PropertyColumn Property="@(c => c.Medals.Gold)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Silver)" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Bronze)" Align="Align.Right" />
<PropertyColumn Property="@(c => c.Medals.Total)" Align="Align.Right" />
</QuickGrid>
</div>

@code {
GridItemsProvider<Country> itemsProvider;

protected override void OnInitialized()
{
itemsProvider = async req =>
{
var sortBy = req.GetSortByProperties().SingleOrDefault();
return await DataService.GetCountriesAsync(req.StartIndex, req.Count, sortBy.PropertyName, sortBy.Direction == SortDirection.Ascending, req.CancellationToken);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.flag {
width: 2rem;
height: 1.5rem;
}

.grid {
display: inline-flex;
flex-direction: column;
height: 300px;
overflow-y: scroll;
}

.grid:focus {
/* Items with overflow-scroll need to have a tabindex to work properly with keyboard-based scrolling */
/* This also means they will render a focus outline */
outline-color: rgba(0, 0, 0, 0.3);
outline-offset: 2px;
}

.grid ::deep thead {
/* Header doesn't scroll */
position: sticky;
top: 0;
background-color: white;
z-index: 1;
}

.grid ::deep tr {
height: 30px;
}

.grid ::deep thead .country-name {
width: 15rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Components.QuickGrid\Microsoft.AspNetCore.Components.QuickGrid.csproj" />
</ItemGroup>

</Project>
Loading