Skip to content

Productize QuickGrid #46573

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

Merged
merged 19 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Generators", "src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.Generators.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "QuickGrid", "QuickGrid", "{C406D9E0-1585-43F9-AA8F-D468AF84A996}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.QuickGrid", "src\Components\QuickGrid\Microsoft.AspNetCore.Components.QuickGrid\src\Microsoft.AspNetCore.Components.QuickGrid.csproj", "{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter", "src\Components\QuickGrid\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter\src\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter.csproj", "{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -10589,6 +10595,38 @@ Global
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x64.Build.0 = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x86.ActiveCfg = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x86.Build.0 = Release|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Debug|arm64.ActiveCfg = Debug|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Debug|arm64.Build.0 = Debug|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Debug|x64.ActiveCfg = Debug|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Debug|x64.Build.0 = Debug|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Debug|x86.ActiveCfg = Debug|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Debug|x86.Build.0 = Debug|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Release|Any CPU.Build.0 = Release|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Release|arm64.ActiveCfg = Release|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Release|arm64.Build.0 = Release|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Release|x64.ActiveCfg = Release|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Release|x64.Build.0 = Release|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Release|x86.ActiveCfg = Release|Any CPU
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2}.Release|x86.Build.0 = Release|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Debug|arm64.ActiveCfg = Debug|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Debug|arm64.Build.0 = Debug|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Debug|x64.ActiveCfg = Debug|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Debug|x64.Build.0 = Debug|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Debug|x86.ActiveCfg = Debug|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Debug|x86.Build.0 = Debug|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Release|Any CPU.Build.0 = Release|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Release|arm64.ActiveCfg = Release|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Release|arm64.Build.0 = Release|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Release|x64.ActiveCfg = Release|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Release|x64.Build.0 = Release|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Release|x86.ActiveCfg = Release|Any CPU
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -11460,6 +11498,9 @@ Global
{10173568-A65E-44E5-8C6F-4AA49D0577A1} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
{97C7D2A4-87E5-4A4A-A170-D736427D5C21} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
{4730F56D-24EF-4BB2-AA75-862E31205F3A} = {225AEDCF-7162-4A86-AC74-06B84660B379}
{C406D9E0-1585-43F9-AA8F-D468AF84A996} = {60D51C98-2CC0-40DF-B338-44154EFEE2FF}
{7757E360-40F5-4C90-9D7F-E6B0E62BA9E2} = {C406D9E0-1585-43F9-AA8F-D468AF84A996}
{F0BF2260-5AE2-4248-81DE-AC5B9FC6A931} = {C406D9E0-1585-43F9-AA8F-D468AF84A996}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
2 changes: 2 additions & 0 deletions eng/ProjectReferences.props
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components" ProjectPath="$(RepoRoot)src\Components\Components\src\Microsoft.AspNetCore.Components.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.CustomElements" ProjectPath="$(RepoRoot)src\Components\CustomElements\src\Microsoft.AspNetCore.Components.CustomElements.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Forms" ProjectPath="$(RepoRoot)src\Components\Forms\src\Microsoft.AspNetCore.Components.Forms.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" ProjectPath="$(RepoRoot)src\Components\QuickGrid\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter\src\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.QuickGrid" ProjectPath="$(RepoRoot)src\Components\QuickGrid\Microsoft.AspNetCore.Components.QuickGrid\src\Microsoft.AspNetCore.Components.QuickGrid.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Server" ProjectPath="$(RepoRoot)src\Components\Server\src\Microsoft.AspNetCore.Components.Server.csproj" />
<ProjectReferenceProvider Include="Microsoft.Authentication.WebAssembly.Msal" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" />
<ProjectReferenceProvider Include="Microsoft.JSInterop.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions eng/TrimmableProjects.props
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
<TrimmableProject Include="Microsoft.AspNetCore.Components" />
<TrimmableProject Include="Microsoft.AspNetCore.Components.CustomElements" />
<TrimmableProject Include="Microsoft.AspNetCore.Components.Forms" />
<TrimmableProject Include="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" />
<TrimmableProject Include="Microsoft.AspNetCore.Components.QuickGrid" />
<TrimmableProject Include="Microsoft.Authentication.WebAssembly.Msal" />
<TrimmableProject Include="Microsoft.JSInterop.WebAssembly" />
<TrimmableProject Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" />
Expand Down
4 changes: 3 additions & 1 deletion src/Components/Components.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"src\\Components\\CustomElements\\src\\Microsoft.AspNetCore.Components.CustomElements.csproj",
"src\\Components\\Forms\\src\\Microsoft.AspNetCore.Components.Forms.csproj",
"src\\Components\\Forms\\test\\Microsoft.AspNetCore.Components.Forms.Tests.csproj",
"src\\Components\\QuickGrid\\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter\\src\\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter.csproj",
"src\\Components\\QuickGrid\\Microsoft.AspNetCore.Components.QuickGrid\\src\\Microsoft.AspNetCore.Components.QuickGrid.csproj",
"src\\Components\\Samples\\BlazorServerApp\\BlazorServerApp.csproj",
"src\\Components\\Server\\src\\Microsoft.AspNetCore.Components.Server.csproj",
"src\\Components\\Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj",
Expand Down Expand Up @@ -142,4 +144,4 @@
"src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter;
using Microsoft.AspNetCore.Components.QuickGrid.Infrastructure;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Provides extension methods to configure <see cref="IAsyncQueryExecutor"/> on a <see cref="IServiceCollection"/>.
/// </summary>
public static class EntityFrameworkAdapterServiceCollectionExtensions
{
/// <summary>
/// Registers an Entity Framework aware implementation of <see cref="IAsyncQueryExecutor"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
public static void AddQuickGridEntityFrameworkAdapter(this IServiceCollection services)
{
services.AddSingleton<IAsyncQueryExecutor, EntityFrameworkAsyncQueryExecutor>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.AspNetCore.Components.QuickGrid.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;

namespace Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter;

internal sealed class EntityFrameworkAsyncQueryExecutor : IAsyncQueryExecutor
{
public bool IsSupported<T>(IQueryable<T> queryable)
=> queryable.Provider is IAsyncQueryProvider;

public Task<int> CountAsync<T>(IQueryable<T> queryable)
=> queryable.CountAsync();

public Task<T[]> ToArrayAsync<T>(IQueryable<T> queryable)
=> queryable.ToArrayAsync();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.EntityFrameworkCore" />
<Reference Include="Microsoft.AspNetCore.Components.QuickGrid" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#nullable enable
Microsoft.Extensions.DependencyInjection.EntityFrameworkAdapterServiceCollectionExtensions
static Microsoft.Extensions.DependencyInjection.EntityFrameworkAdapterServiceCollectionExtensions.AddQuickGridEntityFrameworkAdapter(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> void
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Components.QuickGrid;

/// <summary>
/// Describes alignment for a <see cref="QuickGrid{TGridItem}"/> column.
/// </summary>
public enum Align
{
/// <summary>
/// Justifies the content against the start of the container.
/// </summary>
Left,

/// <summary>
/// Justifies the content at the center of the container.
/// </summary>
Center,

/// <summary>
/// Justifies the content at the end of the container.
/// </summary>
Right,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@using Microsoft.AspNetCore.Components.Rendering
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@namespace Microsoft.AspNetCore.Components.QuickGrid
@typeparam TGridItem
@{
InternalGridContext.Grid.AddColumn(this, IsDefaultSort);
}

@code
{
private void RenderDefaultHeaderContent(RenderTreeBuilder __builder)
{
@if (HeaderTemplate is not null)
{
@HeaderTemplate(this)
}
else
{
@if (ColumnOptions is not null && Align != Align.Right)
{
<button class="col-options-button" @onclick="@(() => Grid.ShowColumnOptions(this))"></button>
}

if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault())
{
<button class="col-title" @onclick="@(() => Grid.SortByColumnAsync(this))">
<div class="col-title-text">@Title</div>
<div class="sort-indicator" aria-hidden="true"></div>
</button>
}
else
{
<div class="col-title">
<div class="col-title-text">@Title</div>
</div>
}

@if (ColumnOptions is not null && Align == Align.Right)
{
<button class="col-options-button" @onclick="@(() => Grid.ShowColumnOptions(this))"></button>
}
}
}

internal void RenderPlaceholderContent(RenderTreeBuilder __builder, PlaceholderContext placeholderContext)
{
// Blank if no placeholder template was supplied, as it's enough to style with CSS by default
if (PlaceholderTemplate is not null)
{
@PlaceholderTemplate(placeholderContext)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Components.QuickGrid.Infrastructure;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web.Virtualization;

namespace Microsoft.AspNetCore.Components.QuickGrid;

/// <summary>
/// An abstract base class for columns in a <see cref="QuickGrid{TGridItem}"/>.
/// </summary>
/// <typeparam name="TGridItem">The type of data represented by each row in the grid.</typeparam>
public abstract partial class ColumnBase<TGridItem>
{
[CascadingParameter] internal InternalGridContext<TGridItem> InternalGridContext { get; set; } = default!;

/// <summary>
/// Title text for the column. This is rendered automatically if <see cref="HeaderTemplate" /> is not used.
/// </summary>
[Parameter] public string? Title { get; set; }

/// <summary>
/// An optional CSS class name. If specified, this is included in the class attribute of table header and body cells
/// for this column.
/// </summary>
[Parameter] public string? Class { get; set; }

/// <summary>
/// If specified, controls the justification of table header and body cells for this column.
/// </summary>
[Parameter] public Align Align { get; set; }

/// <summary>
/// An optional template for this column's header cell. If not specified, the default header template
/// includes the <see cref="Title" /> along with any applicable sort indicators and options buttons.
/// </summary>
[Parameter] public RenderFragment<ColumnBase<TGridItem>>? HeaderTemplate { get; set; }

/// <summary>
/// If specified, indicates that this column has this associated options UI. A button to display this
/// UI will be included in the header cell by default.
///
/// If <see cref="HeaderTemplate" /> is used, it is left up to that template to render any relevant
/// "show options" UI and invoke the grid's <see cref="QuickGrid{TGridItem}.ShowColumnOptions(ColumnBase{TGridItem})" />).
/// </summary>
[Parameter] public RenderFragment? ColumnOptions { get; set; }

/// <summary>
/// Indicates whether the data should be sortable by this column.
///
/// The default value may vary according to the column type (for example, a <see cref="TemplateColumn{TGridItem}" />
/// is sortable by default if any <see cref="TemplateColumn{TGridItem}.SortBy" /> parameter is specified).
/// </summary>
[Parameter] public bool? Sortable { get; set; }

/// <summary>
/// If specified and not null, indicates that this column represents the initial sort order
/// for the grid. The supplied value controls the default sort direction.
/// </summary>
[Parameter] public SortDirection? IsDefaultSort { get; set; }

/// <summary>
/// If specified, virtualized grids will use this template to render cells whose data has not yet been loaded.
/// </summary>
[Parameter] public RenderFragment<PlaceholderContext>? PlaceholderTemplate { get; set; }

/// <summary>
/// Gets a reference to the enclosing <see cref="QuickGrid{TGridItem}" />.
/// </summary>
public QuickGrid<TGridItem> Grid => InternalGridContext.Grid;

/// <summary>
/// Overridden by derived components to provide rendering logic for the column's cells.
/// </summary>
/// <param name="builder">The current <see cref="RenderTreeBuilder" />.</param>
/// <param name="item">The data for the row being rendered.</param>
protected internal abstract void CellContent(RenderTreeBuilder builder, TGridItem item);

/// <summary>
/// Gets or sets a <see cref="RenderFragment" /> that will be rendered for this column's header cell.
/// This allows derived components to change the header output. However, derived components are then
/// responsible for using <see cref="HeaderTemplate" /> within that new output if they want to continue
/// respecting that option.
/// </summary>
protected internal RenderFragment HeaderContent { get; protected set; }

/// <summary>
/// Get a value indicating whether this column should act as sortable if no value was set for the
/// <see cref="ColumnBase{TGridItem}.Sortable" /> parameter. The default behavior is not to be
/// sortable unless <see cref="ColumnBase{TGridItem}.Sortable" /> is true.
///
/// Derived components may override this to implement alternative default sortability rules.
/// </summary>
/// <returns>True if the column should be sortable by default, otherwise false.</returns>
protected virtual bool IsSortableByDefault() => false;

/// <summary>
/// Constructs an instance of <see cref="ColumnBase{TGridItem}" />.
/// </summary>
public ColumnBase()
{
HeaderContent = RenderDefaultHeaderContent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* Contains the title text and sort indicator, and expands to fill as much of the col width as it can */
.col-title {
display: flex; /* So that we can make col-title-text expand as much as possible, and still hide overflow with ellipsis */
min-width: 0px;
flex-grow: 1;
padding: 0;
}

/* If the column is sortable, its title is rendered as a button element for accessibility and to support navigation by tab */
button.col-title {
border: none;
background: none;
position: relative;
cursor: pointer;
}

.col-justify-center .col-title {
justify-content: center;
}

.col-justify-end .col-title {
flex-direction: row-reverse; /* For end-justified cols, the sort indicator should appear before the title text */
}

/* We put the column title text in its own element primarily so that it can use text-overflow: ellipsis */
.col-title-text {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
Loading