From d9f71dc3082a840d7588673312c0259c8cb39920 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 21 Jan 2025 09:40:54 +0100 Subject: [PATCH] - Add AutoItemsPerPage parameter and functionality (#3220) - Reorganize DataGrid documentation page and examples --- ...crosoft.FluentUI.AspNetCore.Components.xml | 31 ++ .../Shared/Pages/DataGrid/DataGridPage.razor | 290 ++---------------- .../Examples/DataGridAutoItemsPerPage.razor | 32 ++ .../Examples/DataGridGetStarted.razor | 2 +- .../Examples/DataGridMultiSelect.razor | 2 +- .../Examples/DataGridTemplateColumns2.razor | 20 +- .../DataGrid/Pages/DataGridAutoFitPage.razor | 31 ++ .../Pages/DataGridAutoItemsPerPagePage.razor | 88 ++++++ .../DataGridColumnHeaderGenerationPage.razor | 13 + .../Pages/DataGridCustomComparerPage.razor | 22 ++ .../Pages/DataGridCustomPagingPage.razor | 13 + .../Pages/DataGridDynamicColumnsPage.razor | 16 + .../Pages/DataGridGetStartedPage.razor | 10 + .../DataGrid/Pages/DataGridManualPage.razor | 5 + .../Pages/DataGridMultiSelectPage.razor | 58 ++++ .../Pages/DataGridMultilineTextPage.razor | 9 + ...ridNotVirtualizedLoadingAndEmptyPage.razor | 11 + .../DataGrid/Pages/DataGridRankSortPage.razor | 11 + .../Pages/DataGridRemoteDataPage.razor | 35 +++ .../Pages/DataGridTableScrollbarsPage.razor | 16 + .../Pages/DataGridTemplateColumns2Page.razor | 12 + .../Pages/DataGridTemplateColumnsPage.razor | 12 + .../DataGrid/Pages/DataGridTypicalPage.razor | 22 ++ .../Pages/DataGridVirtualizePage.razor | 26 ++ .../Demo/Shared/Shared/DemoNavProvider.cs | 105 ++++++- examples/Demo/Shared/wwwroot/css/site.css | 2 +- .../DataGrid/FluentDataGrid.razor.cs | 49 ++- .../DataGrid/FluentDataGrid.razor.js | 30 ++ .../Components/Pagination/PaginationState.cs | 33 +- 29 files changed, 720 insertions(+), 286 deletions(-) create mode 100644 examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoItemsPerPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridAutoFitPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridAutoItemsPerPagePage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridColumnHeaderGenerationPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridCustomComparerPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridCustomPagingPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridDynamicColumnsPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridGetStartedPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridManualPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridMultiSelectPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridMultilineTextPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridRankSortPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridRemoteDataPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTableScrollbarsPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTemplateColumns2Page.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTemplateColumnsPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTypicalPage.razor create mode 100644 examples/Demo/Shared/Pages/DataGrid/Pages/DataGridVirtualizePage.razor diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index 4b0582b344..ab9acfbf2a 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -2017,6 +2017,11 @@ Sets to automatically fit the columns to the available width as best it can. + + + Automatically fit the number of items per page to the available height. + + Gets or sets the size of each row in the grid based on the enum. @@ -2150,6 +2155,15 @@ + + + Updates the s ItemPerPage parameter. + Guards the CurrentPageIndex from getting greater than the LastPageIndex + + + The maixmum number of rows that fits the available space + + Resizes the column width by a discrete amount. @@ -7869,6 +7883,7 @@ Gets or sets the number of items on each page. + To set it and update any associated , call @@ -7903,6 +7918,22 @@ The new, zero-based page index. A representing the completion of the operation. + + + Sets the items per page and notifies any associated + to fetch and render updated data. + + The new number of items per page. + A representing the completion of the operation. + + + + Sets the total number of items nd makes sure the current page index stays valid. + + The total number of items + If true, the total item count will be updated even if it is the same as the current value. + + Gets or sets the id of the component the popover is positioned relative to. diff --git a/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor b/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor index c3661f8803..7b16060805 100644 --- a/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor +++ b/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor @@ -1,4 +1,4 @@ -@page "/DataGrid" +@page "/datagrid-overview" @using FluentUI.Demo.Shared.Pages.DataGrid.Examples; @using Microsoft.FluentUI.AspNetCore.Components; @@ -122,275 +122,31 @@

Examples

- - - - The example and code below show what you need to add to one of your Blazor page components to render a very simple grid (with sortable columns) - - - - - -

The same example, adding a SelectColumn, to allow multi-select rows.

-

To utilize the SelectColumn feature in the Fluent DataGrid, there are two approaches available:

- -

- Automatic Management via SelectedItems -

    -
  • Provide a list of data via the Items property.
  • -
  • Let the grid handle selected rows entirely through the SelectedItems property.
  • -
-

-

- Manual Management via Property and OnSelect: -

    -
  • Control how selected lines are saved manually.
  • -
  • Utilize the Property, OnSelect, and SelectAll attributes.
  • -
- This method offers more flexibility but requires additional configuration, making it particularly useful when - using Virtualize or directly managing a custom IsSelected property. -

- -
- By default the Fluent Design System recommends to only use the checkbox to indicate selected rows. - It is possible to change this behavior by using a CSS style like this to set a background on selected rows: - -
-.fluent-data-grid-row:has([row-selected]) > td {
-    background-color: var(--neutral-fill-stealth-hover)
-}
-
-
- -
-
-

- Using this SelectColumn, you can customize the checkboxes by using ChildContent to define the contents of the selection for each row of the grid; - or SelectAllTemplate to customize the header of this column. - If you don't want the user to be able to interact (click and change) on the SelectAll header, you can set the SelectAllDisabled="true" attribute. -

- - Example: - - <SelectAllTemplate> - @@(context.AllSelected == true ? "✅" : context.AllSelected == null ? "➖" : "⬜") - </SelectAllTemplate> - <ChildContent> - @@(SelectedItems.Contains(context) ? "✅" : " ") @@* Using SelectedItems *@@ - @@(context.Selected ? "✅" : " ") @@* Using Property and OnSelect *@@ - </ChildContent> - + See the following pages for examples and additional documentation on the situations that ech example shows.

+ + Auto fit + Auto items per page + Custom comparer + Custom paging + Custom sorting + Dynamic columns + Getting started + Header generation + Intemittent loading + Manual grid + Multi line text in cells + Single/Multi select + Remote data + Table with scrollbars + Template columns + More template columns + Typical grid usage + Virtualized grid + + - - -

- Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing. -

-

- All columns, except 'Bronze', have a Tooltip parameter value of true.
- When using this for a TemplateColumn (like 'Rank' here), you need to also supply a value for the TooltipText parameter. No value given equals no tooltip shown.
- When using this for a PropertyColumn, a value for the TooltipText is not required. By default, the value given for Property - will be re-used for the tooltip. If you do supply a value for TooltipText its outcome will be used instead. -
-
- TooltipText is a lambda function that takes the current item as input and returns the text to show in the tooltip (and aria-label). - Look at the Razor tab to see how this is done and how it can be customized. -

-
-
- - - -

- Here is an example that demonstrates the rank sort. -

-
-
- - - - -

- If you're using Blazor WebAssembly, it's very common to fetch data from a JSON API on a server. If you want to - fetch only the data that's needed for the current page/viewport and apply any sorting or filtering rules on the - server, you can use the ItemsProvider parameter. -

-

- You can also use ItemsProvider with Blazor Server if it needs to query an external endpoint, or in any - other case where your requirements aren't covered by an IQueryable. -

-

- To do this, supply a callback matching the GridItemsProvider<TGridItem> delegate type, where TGridItem - is the type of data displayed in the grid. Your callback will be given a parameter of type GridItemsProviderRequest<TGridItem> - which specifies the start index, maximum row count, and sort order of data to return. As well as returning the matching items, you need - to return a totalItemCount so that paging or virtualization can work. -

-

- Here is an example of connecting a grid to the public OpenFDA Food Enforcement database. -

-

- This grid is using a 'sticky' header (i.e. the header is always visible when scrolling). The buttons in the last column disappear under the header when scrolling. - In this example they don't really do anything more than writing a message in the console log' -

-

- The second column has a custom Style parameter set and applied to it. The 4th column has its Tooltip - parameter set to true. This will show the full content of the cell when hovering over it. See the 'Razor' tab for how these - parameters have been applied. -

-
-
- - - - -

- It can be expensive both to fetch and to render large numbers of items. If the amount of data you're - displaying might be large, you should use either paging or virtualization. -

-

- Virtualization provides the appearance of continuous scrolling through an arbitrarily-large data set, - while only needing to fetch and render the rows that are currently in the scroll viewport. This can provide - excellent performance even when the data set is vast. FluentDataGrid's virtualization feature is built on Blazor's - built-in Virtualize component, so it shares the - same capabilities, requirements, and limitations. -

-

- Enabling virtualization is just a matter of passing Virtualize="true". For it to work - properly and reliably, every row rendered must have the same known height. - This is handled by the - FluentDataGrid code - . -

-
-
- - - -

- Use the options below the grid to toggle the content of this non virtualized grid on and off and to simulate a loading state -

-
-
- - - -

- TemplateColumn uses arbitrary Razor fragments to supply contents for its cells. It can't - infer the column's title or sort order automatically. -

-
-
- - - -

- You can make columns appear conditionally using normal Razor logic such as @@if. Example: -

-

- Also, in this example the column's Width parameter is being set instead of specifying all widths for all - columns in the GridTemplateColumn parameter. -

-
-
- - - -

- Here a custom comparer is being used to sort counties by the length of their name. The code has examples for both - PropertyColumn and TemplateColumn implementations (see the Razor tab).
- For this example the code for the comparer is placed in the DataGridCustomComparer.razor.cs file but it - could of course be placed in its own file. -

-

- For the paginator, this example also shows how to use the SummaryTemplate and PaginationTextTemplate parameters. -

-

- This example also shows using an OnRowFocus event callback to detect which row the cursor is over. By setting ShowHover - to true, the current row will be highlighted. By default the system will use the designated hover color for this but you can specify an alternative - color by setting the --datagrid-hover-color CSS variable. See the Razor tab for how this is done in this example. -

-
-
- - - -

- You can customize the appearance of Paginator by supplying a SummaryTemplate. - If you want further customization, you can create your own alternative UI that works with - PaginationState. Example: -

-
-
- - - - - -

- TemplateColumn uses arbitrary Razor fragments to supply contents for its cells. It can't - infer the column's title or sort order automatically. -

-
-
- - - - - The DataGrid can generate column headers by using the System.ComponentModel.DataAnnotations.DisplayAttribute on properties - shown in the grid. -
- See the 'Razor' tab on how these attributes have been applied to the class properties. -
-
- - - - Set the grid parameter MultiLine to true when you have cells in your data that will take up more than a single line. - - - - - -

- Example of using an outside div and the Style parameter to achieve a table like DataGrid with infinite horizontal scrollbars to display all content on all devices. -

-

- If you use this in combination with a sticky header, the style of the header will be lost for the columns that are rendered out of the view initially. - You can fix this by adding the following Style to your data grid: Style="min-width: max-content;" -

- -
-
- - - -

- The example and code below show what you need to add to one of your Blazor page components to implement auto-fit. -

-

- The AutoFit parameter is used to automatically adjust the column widths to fit the content. It only runs on - the first render and does not update when the content changes. -

-

- The column widths are calculated with the process below: -

    -
  • - Loop through the columns and find the biggest width of each cell of the column -
  • -
  • - Build the GridTemplateColumns string using the fr unit -
  • -
-

-

- This does not work - when Virtualization is turned on. The GridTemplateColumns parameter is ignored - when AutoFit is set to true. This is untested in MAUI. -

-
-

Documentation

diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoItemsPerPage.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoItemsPerPage.razor new file mode 100644 index 0000000000..d00892e6f8 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoItemsPerPage.razor @@ -0,0 +1,32 @@ +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +@inject DataSource Data +@inject IJSRuntime JSRuntime + +
+ + + + + + + +
+ + + +@code { + + DataGridRowSize rowSize = DataGridRowSize.Small; + IQueryable? items; + PaginationState pagination = new PaginationState { ItemsPerPage = 10 }; + + protected override async Task OnInitializedAsync() => + items = (await Data.GetCountriesAsync()).AsQueryable(); + +} + diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridGetStarted.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridGetStarted.razor index bc304d35a0..c279887831 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridGetStarted.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridGetStarted.razor @@ -16,4 +16,4 @@ new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)), new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)), }.AsQueryable(); -} \ No newline at end of file +} diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultiSelect.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultiSelect.razor index 76cca78a4a..086e12d61d 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultiSelect.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultiSelect.razor @@ -28,7 +28,7 @@ -
+
SelectedItems: @String.Join("; ", SelectedItems.Select(p => p.Name))
diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns2.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns2.razor index 4d428ce059..c2b7ae045f 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns2.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns2.razor @@ -1,18 +1,18 @@  + Items=RowsGrid.AsQueryable() + GridTemplateColumns="1fr 1fr" + TGridItem=SampleGridData + OnRowClick="HandleRowClick" + OnRowFocus="HandleRowFocus" + OnCellClick="HandleCellClick" + OnCellFocus="HandleCellFocus" + RowSize="@DataGridRowSize.Medium"> - + - + diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridAutoFitPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridAutoFitPage.razor new file mode 100644 index 0000000000..e4586ac9b1 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridAutoFitPage.razor @@ -0,0 +1,31 @@ +@page "/datagrid-auto-fit" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Auto fit + + +

+ The example and code below show what you need to add to one of your Blazor page components to implement auto-fit. +

+

+ The AutoFit parameter is used to automatically adjust the column widths to fit the content. It only runs on + the first render and does not update when the content changes. +

+

+ The column widths are calculated with the process below: +

    +
  • + Loop through the columns and find the biggest width of each cell of the column +
  • +
  • + Build the GridTemplateColumns string using the fr unit +
  • +
+

+

+ This does not work + when Virtualization is turned on. The GridTemplateColumns parameter is ignored + when AutoFit is set to true. This is untested in MAUI. +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridAutoItemsPerPagePage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridAutoItemsPerPagePage.razor new file mode 100644 index 0000000000..d545bd9a6d --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridAutoItemsPerPagePage.razor @@ -0,0 +1,88 @@ +@page "/datagrid-auto-items-per-page" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Auto items per page + + +

+ The example and code below show what you need to get auto items per page functionality for the pagination of a datagrid. +

+

+ Resize the page vertically to see the number of rows being displayed per page adapt to the available height. +

+

+ The AutoItemsPerPage parameter must be set to true and obviously Pagination must be used as well for this to work. + Also, the DataGrid container must have styling that makes it automatially adapt to the available height. +
+
+ An example of how that can be done for this demo site layout is shown in the <style> section below +

+ +
+
+ + + <style> + + #datagrid-container { + height: calc(100% - 3rem); + min-height: 8rem; + overflow-x: auto; + overflow-y: hidden; + } + + article { + min-height: 32rem; + max-height: 80dvh; + } + + .demo-section-content { + height: calc(100% - 10rem); + } + + .demo-section-example { + height: 100%; + } + + fluent-tabs { + height: 100%; + } + + #tab-example-autoitemsperpage-panel { + height: 100% !important; + max-height: calc(100% - 2rem) !important; + } + </style> + + + diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridColumnHeaderGenerationPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridColumnHeaderGenerationPage.razor new file mode 100644 index 0000000000..8846b0ee25 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridColumnHeaderGenerationPage.razor @@ -0,0 +1,13 @@ +@page "/datagrid-header-generation" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Header generation + + + + The DataGrid can generate column headers by using the System.ComponentModel.DataAnnotations.DisplayAttribute on properties + shown in the grid. +
+ See the 'Razor' tab on how these attributes have been applied to the class properties. +
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridCustomComparerPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridCustomComparerPage.razor new file mode 100644 index 0000000000..a4a2bf3af9 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridCustomComparerPage.razor @@ -0,0 +1,22 @@ +@page "/datagrid-custom-comparer" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Custom comparer + + +

+ Here a custom comparer is being used to sort counties by the length of their name. The code has examples for both + PropertyColumn and TemplateColumn implementations (see the Razor tab).
+ For this example the code for the comparer is placed in the DataGridCustomComparer.razor.cs file but it + could of course be placed in its own file. +

+

+ For the paginator, this example also shows how to use the SummaryTemplate and PaginationTextTemplate parameters. +

+

+ This example also shows using an OnRowFocus event callback to detect which row the cursor is over. By setting ShowHover + to true, the current row will be highlighted. By default the system will use the designated hover color for this but you can specify an alternative + color by setting the --datagrid-hover-color CSS variable. See the Razor tab for how this is done in this example. +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridCustomPagingPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridCustomPagingPage.razor new file mode 100644 index 0000000000..cae7db6a3e --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridCustomPagingPage.razor @@ -0,0 +1,13 @@ +@page "/datagrid-custom-paging" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Custom Paging + + +

+ You can customize the appearance of Paginator by supplying a SummaryTemplate. + If you want further customization, you can create your own alternative UI that works with + PaginationState. Example: +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridDynamicColumnsPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridDynamicColumnsPage.razor new file mode 100644 index 0000000000..cb90acc6a2 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridDynamicColumnsPage.razor @@ -0,0 +1,16 @@ +@page "/datagrid-dynamic-columns" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Dynamic columns + + + +

+ You can make columns appear conditionally using normal Razor logic such as @@if. Example: +

+

+ Also, in this example the column's Width parameter is being set instead of specifying all widths for all + columns in the GridTemplateColumn parameter. +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridGetStartedPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridGetStartedPage.razor new file mode 100644 index 0000000000..9cb2dc515a --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridGetStartedPage.razor @@ -0,0 +1,10 @@ +@page "/datagrid-get-started" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Getting started + + + + The example and code below show what you need to add to one of your Blazor page components to render a very simple grid (with sortable columns) + + diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridManualPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridManualPage.razor new file mode 100644 index 0000000000..bf211aa666 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridManualPage.razor @@ -0,0 +1,5 @@ +@page "/datagrid-manual" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Manual grid + diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridMultiSelectPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridMultiSelectPage.razor new file mode 100644 index 0000000000..18c5a7ce7d --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridMultiSelectPage.razor @@ -0,0 +1,58 @@ +@page "/datagrid-multi-select" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Multi select + + + +

The same example, adding a SelectColumn, to allow multi-select rows.

+

To utilize the SelectColumn feature in the Fluent DataGrid, there are two approaches available:

+ +

+ Automatic Management via SelectedItems +

    +
  • Provide a list of data via the Items property.
  • +
  • Let the grid handle selected rows entirely through the SelectedItems property.
  • +
+

+

+ Manual Management via Property and OnSelect: +

    +
  • Control how selected lines are saved manually.
  • +
  • Utilize the Property, OnSelect, and SelectAll attributes.
  • +
+ This method offers more flexibility but requires additional configuration, making it particularly useful when + using Virtualize or directly managing a custom IsSelected property. +

+ +
+ By default the Fluent Design System recommends to only use the checkbox to indicate selected rows. + It is possible to change this behavior by using a CSS style like this to set a background on selected rows: + +
+.fluent-data-grid-row:has([row-selected]) > td {
+    background-color: var(--neutral-fill-stealth-hover)
+}
+
+
+ +
+
+ +

+ Using this SelectColumn, you can customize the checkboxes by using ChildContent to define the contents of the selection for each row of the grid; + or SelectAllTemplate to customize the header of this column. + If you don't want the user to be able to interact (click and change) on the SelectAll header, you can set the SelectAllDisabled="true" attribute. +

+ + Example: + + <SelectAllTemplate> + @@(context.AllSelected == true ? "✅" : context.AllSelected == null ? "➖" : "⬜") + </SelectAllTemplate> + <ChildContent> + @@(SelectedItems.Contains(context) ? "✅" : " ") @@* Using SelectedItems *@@ + @@(context.Selected ? "✅" : " ") @@* Using Property and OnSelect *@@ + </ChildContent> + +

diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridMultilineTextPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridMultilineTextPage.razor new file mode 100644 index 0000000000..e591147fb9 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridMultilineTextPage.razor @@ -0,0 +1,9 @@ +@page "/datagrid-multi-line" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Multi line text + + + Set the grid parameter MultiLine to true when you have cells in your data that will take up more than a single line. + + diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.razor new file mode 100644 index 0000000000..9fa9ab2b70 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.razor @@ -0,0 +1,11 @@ +@page "/datagrid-loading-and-empty-content" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Using LoadingContent and EmptyContent + + +

+ Use the options below the grid to toggle the content of this non virtualized grid on and off and to simulate a loading state +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridRankSortPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridRankSortPage.razor new file mode 100644 index 0000000000..8e54973672 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridRankSortPage.razor @@ -0,0 +1,11 @@ +@page "/datagrid-custom-sort" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Custom sort + + +

+ Here is an example that demonstrates the rank sort. +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridRemoteDataPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridRemoteDataPage.razor new file mode 100644 index 0000000000..28b32c83cd --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridRemoteDataPage.razor @@ -0,0 +1,35 @@ +@page "/datagrid-remote-data" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Remote data + + +

+ If you're using Blazor WebAssembly, it's very common to fetch data from a JSON API on a server. If you want to + fetch only the data that's needed for the current page/viewport and apply any sorting or filtering rules on the + server, you can use the ItemsProvider parameter. +

+

+ You can also use ItemsProvider with Blazor Server if it needs to query an external endpoint, or in any + other case where your requirements aren't covered by an IQueryable. +

+

+ To do this, supply a callback matching the GridItemsProvider<TGridItem> delegate type, where TGridItem + is the type of data displayed in the grid. Your callback will be given a parameter of type GridItemsProviderRequest<TGridItem> + which specifies the start index, maximum row count, and sort order of data to return. As well as returning the matching items, you need + to return a totalItemCount so that paging or virtualization can work. +

+

+ Here is an example of connecting a grid to the public OpenFDA Food Enforcement database. +

+

+ This grid is using a 'sticky' header (i.e. the header is always visible when scrolling). The buttons in the last column disappear under the header when scrolling. + In this example they don't really do anything more than writing a message in the console log' +

+

+ The second column has a custom Style parameter set and applied to it. The 4th column has its Tooltip + parameter set to true. This will show the full content of the cell when hovering over it. See the 'Razor' tab for how these + parameters have been applied. +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTableScrollbarsPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTableScrollbarsPage.razor new file mode 100644 index 0000000000..4425874e7a --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTableScrollbarsPage.razor @@ -0,0 +1,16 @@ +@page "/datagrid-scrollbars" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Table scrollbars + + +

+ Example of using an outside div and the Style parameter to achieve a table like DataGrid with infinite horizontal scrollbars to display all content on all devices. +

+

+ If you use this in combination with a sticky header, the style of the header will be lost for the columns that are rendered out of the view initially. + You can fix this by adding the following Style to your data grid: Style="min-width: max-content;" +

+ +
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTemplateColumns2Page.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTemplateColumns2Page.razor new file mode 100644 index 0000000000..e470c44fed --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTemplateColumns2Page.razor @@ -0,0 +1,12 @@ +@page "/datagrid-template-columns-2" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +More template columns + + +

+ TemplateColumn uses arbitrary Razor fragments to supply contents for its cells. It can't + infer the column's title or sort order automatically. +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTemplateColumnsPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTemplateColumnsPage.razor new file mode 100644 index 0000000000..cddfab5109 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTemplateColumnsPage.razor @@ -0,0 +1,12 @@ +@page "/datagrid-template-columns" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Template columns + + +

+ TemplateColumn uses arbitrary Razor fragments to supply contents for its cells. It can't + infer the column's title or sort order automatically. +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTypicalPage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTypicalPage.razor new file mode 100644 index 0000000000..c695322ada --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridTypicalPage.razor @@ -0,0 +1,22 @@ +@page "/datagrid-typical" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Typical usage + + + +

+ Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing. +

+

+ All columns, except 'Bronze', have a Tooltip parameter value of true.
+ When using this for a TemplateColumn (like 'Rank' here), you need to also supply a value for the TooltipText parameter. No value given equals no tooltip shown.
+ When using this for a PropertyColumn, a value for the TooltipText is not required. By default, the value given for Property + will be re-used for the tooltip. If you do supply a value for TooltipText its outcome will be used instead. +
+
+ TooltipText is a lambda function that takes the current item as input and returns the text to show in the tooltip (and aria-label). + Look at the Razor tab to see how this is done and how it can be customized. +

+
+
diff --git a/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridVirtualizePage.razor b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridVirtualizePage.razor new file mode 100644 index 0000000000..d2764d45b2 --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Pages/DataGridVirtualizePage.razor @@ -0,0 +1,26 @@ +@page "/datagrid-virtualize" +@using FluentUI.Demo.Shared.Pages.DataGrid.Examples + +Virtualize + + +

+ It can be expensive both to fetch and to render large numbers of items. If the amount of data you're + displaying might be large, you should use either paging or virtualization. +

+

+ Virtualization provides the appearance of continuous scrolling through an arbitrarily-large data set, + while only needing to fetch and render the rows that are currently in the scroll viewport. This can provide + excellent performance even when the data set is vast. FluentDataGrid's virtualization feature is built on Blazor's + built-in Virtualize component, so it shares the + same capabilities, requirements, and limitations. +

+

+ Enabling virtualization is just a matter of passing Virtualize="true". For it to work + properly and reliably, every row rendered must have the same known height. + This is handled by the + FluentDataGrid code + . +

+
+
diff --git a/examples/Demo/Shared/Shared/DemoNavProvider.cs b/examples/Demo/Shared/Shared/DemoNavProvider.cs index a205d6ba8c..1bfa9dbcef 100644 --- a/examples/Demo/Shared/Shared/DemoNavProvider.cs +++ b/examples/Demo/Shared/Shared/DemoNavProvider.cs @@ -378,10 +378,109 @@ public DemoNavProvider() icon: new Icons.Regular.Size20.ContactCardGroup(), title: "Card" ), - new NavLink( - href: "/DataGrid", + new NavGroup( + title: "Data grid", + expanded: true, + gap: "10px", icon: new Icons.Regular.Size20.Grid(), - title: "Data grid" + children: + [ + new NavLink( + href: "/datagrid-overview", + icon: new Icons.Regular.Size20.Grid(), + title: "Overview" + ), + new NavLink ( + href:"/datagrid-auto-fit", + icon: new Icons.Regular.Size20.Grid(), + title: "Auto fit" + ), + new NavLink ( + href:"/datagrid-auto-items-per-page", + icon: new Icons.Regular.Size20.Grid(), + title: "Auto items per page" + ), + new NavLink ( + href:"/datagrid-custom-comparer", + icon: new Icons.Regular.Size20.Grid(), + title: "Custom comparer" + ), + new NavLink ( + href:"/datagrid-custom-paging", + icon: new Icons.Regular.Size20.Grid(), + title: "Custom paging" + ), + new NavLink ( + href:"/datagrid-custom-sort", + icon: new Icons.Regular.Size20.Grid(), + title: "Custom sorting" + ), + new NavLink ( + href:"/datagrid-dynamic-columns", + icon: new Icons.Regular.Size20.Grid(), + title: "Dynamic columns" + ), + new NavLink ( + href:"/datagrid-get-started", + icon: new Icons.Regular.Size20.Grid(), + title: "Getting started" + ), + new NavLink ( + href:"/datagrid-header-generation", + icon: new Icons.Regular.Size20.Grid(), + title: "Header generation" + ), + new NavLink ( + href:"/datagrid-loading-and-empty-content", + icon: new Icons.Regular.Size20.Grid(), + title: "Intemittent loading" + ), + new NavLink ( + href:"/datagrid-manual", + icon: new Icons.Regular.Size20.Grid(), + title: "Manual grid" + ), + new NavLink ( + href:"/datagrid-multi-line", + icon: new Icons.Regular.Size20.Grid(), + title: "Multi line text in cells" + ), + new NavLink ( + href:"/datagrid-multi-select", + icon: new Icons.Regular.Size20.Grid(), + title: "Single/Multi select" + ), + new NavLink ( + href:"/datagrid-remote-data", + icon: new Icons.Regular.Size20.Grid(), + title: "Remote data" + ), + new NavLink ( + href:"/datagrid-scrollbars", + icon: new Icons.Regular.Size20.Grid(), + title: "Table with scrollbars" + ), + new NavLink ( + href:"/datagrid-template-columns", + icon: new Icons.Regular.Size20.Grid(), + title: "Template columns" + ), + new NavLink ( + href:"/datagrid-template-columns-2", + icon: new Icons.Regular.Size20.Grid(), + title: "More template columns" + ), + new NavLink ( + href:"/datagrid-typical", + icon: new Icons.Regular.Size20.Grid(), + title: "Typical grid usage" + ), + new NavLink ( + href:"/datagrid-virtualize", + icon: new Icons.Regular.Size20.Grid(), + title: "Virtualized grid" + ), + ] ), new NavLink( href: "/Dialog", diff --git a/examples/Demo/Shared/wwwroot/css/site.css b/examples/Demo/Shared/wwwroot/css/site.css index 3655ed8a5b..dfaa44baa1 100644 --- a/examples/Demo/Shared/wwwroot/css/site.css +++ b/examples/Demo/Shared/wwwroot/css/site.css @@ -100,7 +100,7 @@ blockquote p:last-child { nav.sitenav { padding: 1.5rem 1rem; height: calc(100dvh - 90px); - width: 18rem; + width: 20rem; overflow-y: auto; } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 6afdbe0a22..2d19f2910b 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -261,6 +261,12 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve [Parameter] public bool AutoFit { get; set; } + /// + /// Automatically fit the number of items per page to the available height. + /// + [Parameter] + public bool AutoItemsPerPage { get; set; } + [Parameter] public DataGridDisplayMode DisplayMode { get; set; } = DataGridDisplayMode.Grid; @@ -301,6 +307,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve internal bool EffectiveLoadingValue => Loading ?? ItemsProvider is not null; private ElementReference? _gridReference; + //private DotNetObjectReference? _dotNetObjectReference; private Virtualize<(int, TGridItem)>? _virtualizeComponent; // IQueryable only exposes synchronous query APIs. IAsyncQueryExecutor is an adapter that lets us invoke any @@ -431,6 +438,10 @@ protected override async Task OnAfterRenderAsync(bool firstRender) try { _jsEventDisposable = await Module.InvokeAsync("init", _gridReference, AutoFocus); + if (AutoItemsPerPage) + { + await Module.InvokeVoidAsync("dynamicItemsPerPage", _gridReference, DotNetObjectReference.Create(this), (int)RowSize); + } } catch (JSException ex) { @@ -443,7 +454,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (_checkColumnOptionsPosition && _displayOptionsForColumn is not null) { _checkColumnOptionsPosition = false; - _ = Module?.InvokeVoidAsync("checkColumnPopupPosition", _gridReference, ".col-options").AsTask(); + Module?.InvokeVoidAsync("checkColumnPopupPosition", _gridReference, ".col-options").AsTask(); } if (_checkColumnResizePosition && _displayResizeForColumn is not null) @@ -930,6 +941,42 @@ private void SaveStateToQueryString() NavigationManager.NavigateTo(NavigationManager.GetUriWithQueryParameters(stateParams), replace: true); } + /// + /// Updates the s ItemPerPage parameter. + /// Guards the CurrentPageIndex from getting greater than the LastPageIndex + /// + /// + /// The maixmum number of rows that fits the available space + /// + [JSInvokable] + public async Task UpdateItemsPerPageAsync(int visibleRows) + { + if (Pagination is null) + { + return; + } + + if (visibleRows < 2) + { + visibleRows = 2; + } + + await Pagination.SetItemsPerPageAsync(visibleRows - 1); // subtract 1 for the table header + + //if (Pagination.CurrentPageIndex > Pagination.LastPageIndex && Pagination.LastPageIndex.HasValue && Pagination.LastPageIndex.Value > 0) + //{ + // await Pagination.SetCurrentPageIndexAsync(Pagination.LastPageIndex.Value); + //} + + //await RefreshDataAsync(); + //StateHasChanged(); + } + + //public void SetPageReference(Type page) + //{ + // _dotNetObjectReference = DotNetObjectReference.Create(page); + //} + public async Task OnKeyDownAsync(FluentKeyCodeEventArgs args) { if (args.ShiftKey == true && args.Key == KeyCode.KeyR) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.js b/src/Core/Components/DataGrid/FluentDataGrid.razor.js index b01c7e4470..6ff482d16d 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.js +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.js @@ -361,3 +361,33 @@ export function autoFitGridColumns(gridElement, columnCount) { grids[gridElement.id] = gridTemplateColumns; } + +function calculateVisibleRows(gridElement, rowHeight) { + if (rowHeight <= 0) { + return 0; + } + + const gridContainer = gridElement.parentElement; + + if (!gridContainer) { + return 0; + } + + const availableHeight = gridContainer?.clientHeight || window.visualViewport?.height || window.innerHeight; + + const visibleRows = Math.max(Math.floor(availableHeight / rowHeight), 1); + return visibleRows; +} + +export function dynamicItemsPerPage(gridElement, dotNetObject, rowSize) { + const observer = new ResizeObserver(() => { + const visibleRows = calculateVisibleRows(gridElement, rowSize) + dotNetObject.invokeMethodAsync('UpdateItemsPerPageAsync', visibleRows) + .catch(err => console.error("Error invoking Blazor method:", err)); + }); + + const targetElement = gridElement.parentElement; + if (targetElement) { + observer.observe(targetElement); + } +} diff --git a/src/Core/Components/Pagination/PaginationState.cs b/src/Core/Components/Pagination/PaginationState.cs index 6f124c1326..0bddcc03ab 100644 --- a/src/Core/Components/Pagination/PaginationState.cs +++ b/src/Core/Components/Pagination/PaginationState.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; namespace Microsoft.FluentUI.AspNetCore.Components; @@ -9,6 +13,7 @@ public class PaginationState { /// /// Gets or sets the number of items on each page. + /// To set it and update any associated , call /// public int ItemsPerPage { get; set; } = 10; @@ -52,9 +57,33 @@ public Task SetCurrentPageIndexAsync(int pageIndex) return CurrentPageItemsChanged.InvokeCallbacksAsync(this); } - public Task SetTotalItemCountAsync(int totalItemCount) + /// + /// Sets the items per page and notifies any associated + /// to fetch and render updated data. + /// + /// The new number of items per page. + /// A representing the completion of the operation. + public async Task SetItemsPerPageAsync(int itemsPerPage) + { + ItemsPerPage = itemsPerPage; + + await CurrentPageItemsChanged.InvokeCallbacksAsync(this); + if (TotalItemCount.HasValue) + { + await SetTotalItemCountAsync(TotalItemCount.Value, true); + } + return; + } + + /// + /// Sets the total number of items nd makes sure the current page index stays valid. + /// + /// The total number of items + /// If true, the total item count will be updated even if it is the same as the current value. + /// + public Task SetTotalItemCountAsync(int totalItemCount, bool force = false) { - if (totalItemCount == TotalItemCount) + if (totalItemCount == TotalItemCount && !force) { return Task.CompletedTask; }