Skip to content

Commit

Permalink
Productize QuickGrid (#46573) (#46975)
Browse files Browse the repository at this point in the history
Co-authored-by: Nick Stanton <[email protected]>
  • Loading branch information
SteveSandersonMS and Nick-Stanton authored Mar 3, 2023
1 parent ca8dbd4 commit 5ae8106
Show file tree
Hide file tree
Showing 46 changed files with 2,459 additions and 2 deletions.
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;
using Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter;

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,20 @@
// 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.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,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<Description>Provides an Entity Framework Core adapter for the <a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Components.QuickGrid">Microsoft.AspNetCore.Components.QuickGrid</a> package.</Description>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<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,35 @@
// 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>
Start,

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

/// <summary>
/// Justifies the content at the end of the container.
/// </summary>
End,

/// <summary>
/// Justifies the content against the left of the container.
/// </summary>
Left,

/// <summary>
/// Justifies the content at the right 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, InitialSortDirection, IsDefaultSortColumn);
}

@code
{
private void RenderDefaultHeaderContent(RenderTreeBuilder __builder)
{
@if (HeaderTemplate is not null)
{
@HeaderTemplate(this)
}
else
{
@if (ColumnOptions is not null && (Align != Align.Right || Align != Align.End))
{
<button class="col-options-button" @onclick="@(() => Grid.ShowColumnOptionsAsync(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 || Align == Align.End))
{
<button class="col-options-button" @onclick="@(() => Grid.ShowColumnOptionsAsync(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,115 @@
// 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}.ShowColumnOptionsAsync(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>
/// Specifies sorting rules for a column.
/// </summary>
public abstract GridSort<TGridItem>? SortBy { get; set; }

/// <summary>
/// Indicates which direction to sort in
/// if <see cref="IsDefaultSortColumn"/> is true.
/// </summary>
[Parameter] public SortDirection InitialSortDirection { get; set; } = default;

/// <summary>
/// Indicates whether this column should be sorted by default.
/// </summary>
[Parameter] public bool IsDefaultSortColumn { get; set; } = false;

/// <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

0 comments on commit 5ae8106

Please sign in to comment.