Skip to content

[dev-v5] Add DataGrid - part 2, get base code working #3915

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

Draft
wants to merge 37 commits into
base: dev-v5
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
93f6cf9
- Add enums
vnbaaij Jun 12, 2025
b255f69
- Add DataGrid component
vnbaaij Jun 12, 2025
96e40df
Add DataGridGeneratedHeaderType
vnbaaij Jun 12, 2025
f6d0fb1
Merge branch 'users/vnbaaij/dev-v5/datagrid-part1' into users/vnbaaij…
vnbaaij Jun 12, 2025
1059dbe
Merge branch 'dev-v5' into users/vnbaaij/dev-v5/datagrid-part1
dvoituron Jun 12, 2025
595ff78
Fix analyzer errors
vnbaaij Jun 12, 2025
b1cf680
- Add Typical demo and flags
vnbaaij Jun 12, 2025
8393e35
Work on review comments
vnbaaij Jun 12, 2025
8cd48c6
Merge branch 'users/vnbaaij/dev-v5/datagrid-part1' of https://github.…
vnbaaij Jun 12, 2025
202c10a
Process review comments
vnbaaij Jun 12, 2025
5aa2c65
Add language resources
vnbaaij Jun 12, 2025
777e854
Add Unit Tests for Paginator
vnbaaij Jun 12, 2025
448c89e
More review comments
vnbaaij Jun 13, 2025
3146aeb
Update tests. Now 100% lc
vnbaaij Jun 13, 2025
69ffae6
- Add Typical demo and flags
vnbaaij Jun 13, 2025
35e0334
Replace tokens (WIP)
vnbaaij Jun 13, 2025
b92c83c
Remove flags. These will be added to sample data project in separate PR
vnbaaij Jun 17, 2025
430abd5
Remove DI abstractions package. Not needed
vnbaaij Jun 17, 2025
d0c1052
Merge dev-v5
vnbaaij Jun 17, 2025
33af462
Add IsFixed parameter
vnbaaij Jun 17, 2025
689fe43
merge dev-v5
vnbaaij Jun 17, 2025
44f7d0d
MAke example grid mor functional and look better
vnbaaij Jun 17, 2025
6fc0809
Add initial tests. They are not passing yet
vnbaaij Jun 18, 2025
5bcb9fa
Add surpression atribute, fix other analyzer warnings
vnbaaij Jun 18, 2025
a30dabf
Use localized strings
vnbaaij Jun 18, 2025
aadf080
Merg dev-v5
vnbaaij Jun 18, 2025
70c342b
Fix tests
vnbaaij Jun 18, 2025
b826128
Add Tests
vnbaaij Jun 19, 2025
d0979ea
Add tests for PaginatinState and TotalItemCountChangedEventArgs
vnbaaij Jun 19, 2025
a6aa6fc
Merge branch 'users/vnbaaij/dev-v5/pagination-tests' into users/vnbaa…
vnbaaij Jun 19, 2025
613cbd0
Merge branch 'dev-v5' into users/vnbaaij/dev-v5/pagination-tests
vnbaaij Jun 19, 2025
af4f1e8
Add Row tests
vnbaaij Jun 19, 2025
89038a7
process review comments
vnbaaij Jun 19, 2025
bcdffd9
Merge branch 'users/vnbaaij/dev-v5/pagination-tests' into users/vnbaa…
vnbaaij Jun 19, 2025
ba9c0fb
More tests
vnbaaij Jun 19, 2025
924df75
More tests
vnbaaij Jun 19, 2025
726cf24
Merge branch 'users/vnbaaij/dev-v5/datagrid-part2' of https://github.…
vnbaaij Jun 20, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<FluentStack VerticalAlignment="VerticalAlignment.Center" HorizontalGap="5">
<FluentSelect Items="@(Enum.GetValues<DataGridSelectMode>())"
@bind-Value="@Mode" />
<FluentCheckbox @bind-Value="@UseSelectedItems"
@bind-Value:after="@(() => ResetSelectItems())"
Label="Use `SelectedItems` property" />
<FluentCheckbox @bind-Value="@SelectFromEntireRow"
@bind-Value:after="@(() => ResetSelectItems())"
Label="Use `SelectFromEntireRow` property" />
<FluentCheckbox @bind-Value="@SelectableBefore2000"
@bind-Value:after="@(() => ResetSelectItems())"
Label="Use `Selectable` property" />
</FluentStack>

@if (UseSelectedItems)
{
@* Sample using SelectedItems *@
<div>Using SelectedItems</div>

<FluentDataGrid Items="@People" ShowHover="@SelectFromEntireRow" TGridItem="Person">
<SelectColumn TGridItem="Person"
SelectMode="@Mode"
SelectFromEntireRow="@SelectFromEntireRow"
Selectable="@(e => !SelectableBefore2000 || e.BirthDate.Year >= 2000)"
@bind-SelectedItems="@SelectedItems" />
<PropertyColumn Width="100px" Property="@(p => p.PersonId)" Title="ID" />
<PropertyColumn Width="300px" Property="@(p => p.Name)" />
<PropertyColumn Width="150px" Property="@(p => p.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
</FluentDataGrid>

<div style="margin-top: 1rem;">
<b>SelectedItems:</b>
@String.Join("; ", SelectedItems.Select(p => p.Name))
</div>
}
else
{
@* Sample using Property and OnSelect *@
<div>Using Property and OnSelect</div>

<FluentDataGrid Items="@People" ShowHover="@SelectFromEntireRow" TGridItem="Person">
<SelectColumn TGridItem="Person"
SelectMode="@Mode"
SelectFromEntireRow="@SelectFromEntireRow"
Selectable="@(e => !SelectableBefore2000 || e.BirthDate.Year >= 2000)"
Property="@(e => e.Selected)"
OnSelect="@(e => e.Item.Selected = e.Selected)"
SelectAll="@(People.All(p => p.Selected))"
SelectAllChanged="@(all => People.ToList().ForEach(p => p.Selected = (all == true)))" />
<PropertyColumn Width="100px" Property="@(p => p.PersonId)" Title="ID" />
<PropertyColumn Width="300px" Property="@(p => p.Name)" />
<PropertyColumn Width="150px" Property="@(p => p.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
</FluentDataGrid>

<div style="margin-top: 1rem;">
<b>Persons:</b>
@String.Join("; ", People.Where(p => p.Selected).Select(p => p.Name))
</div>
}

@code {
bool UseSelectedItems = true;
bool SelectFromEntireRow = true;
bool SelectableBefore2000 = false;
DataGridSelectMode Mode = DataGridSelectMode.Single;

IEnumerable<Person> SelectedItems = People.Where(p => p.Selected);

record Person(int PersonId, string Name, DateOnly BirthDate)
{
public bool Selected { get; set; }
};

static IQueryable<Person> People = new[]
{
new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)) { Selected = true },
new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
}.AsQueryable();

private void ResetSelectItems()
{
People.ToList().ForEach(i => i.Selected = false);
People.First().Selected = true;
SelectedItems = People.Where(p => p.Selected);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
@using static FluentUI.Demo.SampleData.Olympics2024


<p>To test set ResizeType on the DataGrid to either DataGridResizeType.Discrete or DataGridResizeType.Exact</p>
<p>Remove the parameter completely to get the original behavior</p>

<div style="height: 380px; overflow-x:auto; display:flex;">
<FluentDataGrid @ref="grid"
Items="@FilteredItems"
ResizableColumns=true
ResizeType="DataGridResizeType.Discrete"
GridTemplateColumns="0.3fr 1fr 0.2fr 0.2fr 0.2fr 0.2fr"
Pagination="@pagination"
RowClass="@rowClass"
RowStyle="@rowStyle"
ShowHover="true"
HeaderCellAsButtonWithMenu="true">
<TemplateColumn Tooltip="true" TooltipText="@(c => "Flag of " + c.Name)" Title="Rank" SortBy="@rankSort" Align="HorizontalAlignment.Center" InitialSortDirection="DataGridSortDirection.Ascending" IsDefaultSortColumn=true>
<img class="flag" src="@(context.Flag())" alt="Flag of @(context.Code)" />
</TemplateColumn>
<PropertyColumn Property="@(c => c.Name)" Sortable="true" Filtered="!string.IsNullOrWhiteSpace(nameFilter)" Tooltip="true" Title="Name of the country">
<ColumnOptions>
<div class="search-box">
<FluentTextInput Autofocus=true @bind-Value=nameFilter @oninput="HandleCountryFilter" @onkeydown="HandleCloseFilterAsync" @bind-Value:after="HandleClear" Placeholder="Country name..." Style="width: 100%;" Label="Filter" />
</div>
</ColumnOptions>
</PropertyColumn>
<PropertyColumn Property="@(c => c.Medals.Gold)" Sortable="true" Align="HorizontalAlignment.Start" Tooltip="true" TooltipText="@(c => "That is " + c.Medals.Gold + " x GOLD!!")" />
<PropertyColumn Property="@(c => c.Medals.Silver)" Sortable="true" Align="HorizontalAlignment.Center" Tooltip="true" />
<PropertyColumn Property="@(c => c.Medals.Bronze)" Sortable="false" Align="HorizontalAlignment.End" />
<PropertyColumn Property="@(c => c.Medals.Total)" Sortable="true" Filtered="@(minMedals != 0 || maxMedals != 130)" Align="HorizontalAlignment.End" Tooltip="true">
<ColumnOptions>
<div style="width: 100%; height: 150px;">
<FluentSlider Label="@($"Min ({minMedals})")" Min="0" Max="150" Step="10" Orientation="Orientation.Horizontal" @bind-Value=minMedals Immediate="true" Style="width: 100%;" />
<FluentSlider Label="@($"Max ({maxMedals})")" Min="0" Max="150" Step="10" Orientation="Orientation.Horizontal" @bind-Value=maxMedals Immediate="true" Style="width: 100%;" />
</div>
</ColumnOptions>
</PropertyColumn>
</FluentDataGrid>
</div>

<FluentPaginator State="@pagination" />

<FluentSwitch @bind-Value="@_clearItems"
@bind-Value:after="ToggleItemsAsync"
UncheckedMessage="Clear all results"
CheckedMessage="Restore all results">
</FluentSwitch>

@code {
FluentDataGrid<Country>? grid;
bool _clearItems = false;
IQueryable<Country>? items;
PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
string nameFilter = string.Empty;
int minMedals;
int maxMedals = 130;

GridSort<Country> rankSort = GridSort<Country>
.ByDescending(x => x.Medals.Gold)
.ThenDescending(x => x.Medals.Silver)
.ThenDescending(x => x.Medals.Bronze);

Func<Country, string?> rowClass = x => x.Name.StartsWith("A") ? "highlighted" : null;
Func<Country, string?> rowStyle = x => x.Name.StartsWith("Au") ? "background-color: var(--highlight-bg)" : null;

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

IQueryable<Country>? FilteredItems
{
get
{
var result = items?.Where(c => c.Medals.Total <= maxMedals);

if (result is not null && !string.IsNullOrEmpty(nameFilter))
{
result = result.Where(c => c.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase));
}

if (result is not null && minMedals > 0)
{
result = result.Where(c => c.Medals.Total >= minMedals);
}

return result;
}
}

protected override void OnInitialized()
{
items = SampleData.Olympics2024.Countries.AsQueryable();
}

private void HandleCountryFilter(ChangeEventArgs args)
{
if (args.Value is string value)
{
nameFilter = value;
}
}

private void HandleClear()
{
if (string.IsNullOrWhiteSpace(nameFilter))
{
nameFilter = string.Empty;
}
}

private async Task HandleCloseFilterAsync(KeyboardEventArgs args)
{
if (args.Key == "Escape")
{
nameFilter = string.Empty;
}
if (args.Key == "Enter" && grid is not null)
{
await grid.CloseColumnOptionsAsync();
}
}

private async Task ToggleItemsAsync()
{
if (_clearItems)
{
items = null;
}
else
{
items = SampleData.Olympics2024.Countries.AsQueryable();
}
await Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* Ensure all the flags are the same size, and centered */
.flag {
height: 1rem;
border: 1px solid var(--neutral-layer-3);
}
.search-box {
min-width: 250px;
width: 100%;
}

.search-box fluent-search {
width: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,24 @@ route: /DataGrid

Overview page and examples to follow

{{ FlagTest }}
## 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.

The Country filter option can be used to quickly filter the list of countries shown. Pressing the ESC key just closes the option popup without changing the filtering currently being used.
Pressing enter finishes the filter action by the current input to filter on and closes the option popup.

The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through
the custom localizer which is registered in the Server project's `Program.cs` file.



{{ DataGridMultiSelect }}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
### Renamed parameters
- `ColumnOptionsLabels` has been renamed to `ColumnOptionUISettings`
- `ColumnOptionsLabels` has been renamed to `ColumnOptionsUISettings`
- `ColumnResizeLabels` has been renamed to `ColumnResizeUISettings`
- `ColumnSortLabels` has been renamed to `ColumnSortUISettings`

These `...UISettings` parameters are now only used to set a custom icon and icon position. All labels that could be set in earlier versions
have now been replaced with our standard Localization capabilities. You can use a custom localizer to set custom labels for these UI settings.
An example of this can be found in the `Server` project of the demo application, where a custom localizer is registered in the `Program.cs` file.

### Enum changes
- `Align` has been renamed to `HorizontalAlignment`
- `GenerateHeaderOption` has been renamed to `DataGridGeneratedHeaderType`
Expand Down
9 changes: 7 additions & 2 deletions examples/Demo/FluentUI.Demo/MyLocalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ internal class MyLocalizer : IFluentLocalizer
_ => IFluentLocalizer.GetDefault(key, arguments),
};
}
// Provide custom translations based on the key
return key switch
{
"DataGrid_ResizeDiscrete" => "Width (+/- 10px)",

// By default, returns the English version of the string
return IFluentLocalizer.GetDefault(key, arguments);
// Fallback to the Default/English if no translation is found
_ => IFluentLocalizer.GetDefault(key, arguments),
};
}
}
}
29 changes: 29 additions & 0 deletions spelling.dic
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

# ***************************
# ***************************
# List of misspelled words
# Please, sort the list alphabetically
Expand All @@ -7,6 +8,7 @@ ansi
appsettings
blazor
brotli
colspan
columnheader
combobox
cref
Expand All @@ -17,9 +19,11 @@ datagrid
datalist
demopanel
dialogtoggle
displaymode
elementreference
evenodd
eventargs
eventargs
gzip
henkan
heure
Expand Down Expand Up @@ -47,22 +51,47 @@ noattribute
nonfile
onaccordionchange
onchange
onclosecolumnoptions
onclosecolumnresize
ondialogbeforetoggle
ondropdownchange
onkeydown
onmenuitemchange
ontabchange
ontreechanged
ontreetoggle
rendertree
rightclick
rowcount
rowheader
rowindex
rrggbb
secondes
sortabillity
sortabillity
sourcecode
Subscribable
Subscribable
summarydata
tabindex
tablist
tabpanel
testid
textarea
wdelta
displaymode
Overscan
gipr
gridcell
Voituron
beforetoggle
resx
yyyy
Langa
Overscan
gipr
rowindex
rowcount
displaymode
Voituron
inputfile
Loading
Loading