Unlike most Data grids, To use RfDataGrid the columns are broken into three seperate render fragments: Headers, Cells, and Filters.
Each render fragement fills in the approriate section of the table. There are several pre built components foreach of these render fragments that can be used.
Each of these sub components communicate with the RfDataGrid via DataGridContext which the RfDataGrid
creates and passes down via a CascadingParameter.
Headers
The render fragment Headers is where each columns th elements should go.
As an option, if sorting isn't required, you could remove the creation of additional sub components by just writing the standard
elements within Headers.
//Example Headers
| Column 1 |
Column 2 |
Column 3 |
Column 4 |
Column n |
Below list out sub components of RfDataGrid
| Component |
Purpose |
RfDgHeader |
Wraps a th providing support for sorting columns. |
Filters
Filters render fragment is an optional location to add filters for the DataGrid. They are included in the thead
underneath the Headers row. There are several prebuilt filters that can be used or
if a custom one is required look to RfDgFilterInput base class for an example of a filter.
However, this class is not required. To make the filter work correctly you will need to have a DataGridContext
parameter and properly call DataGridContext.OnFilterChanged, or call the pages listener for RfDataGrid.OnLoad.
Below list out all predefined filters. More are planned to be written.
| Component |
Purpose |
RfDgFilterNone |
Use this if a column doesn't need a filter. |
RfDgFilterInputDate |
A date input filter. |
RfDgFilterInputDateTime |
A date and time input filter. |
RfDgFilterInputDateTimeOffset |
A date and time input filter using DateTimeOffset |
RfDgFilterInputDouble |
A double input filter. |
RfDgFilterInputFloat |
A float input filter. |
RfDgFilterInputInt |
A int input filter. |
RfDgFilterInputLong |
A long input filter. |
RfDgFilterInputText |
A string input filter. |
RfDgFilterInputTime |
A time input filter. |
NOTE: To improve performance, the RfDataGrid is not aware of any filters nor does it manage them.
Treat this simarly to how EditForm component works. This has the additional benifit to easily support filters
outside of the RfDataGrid.
Cells
The Cells render fragment is where you format the content of a row of the table. The Cells has the current
row of data being passed into it giving it access to context where context equals to the TRowData.
Unlike Headers needing to use RfDgHeader, the Cells has no interactions with the RfDataGrid.
This allows a row to render with no components improving performance.
There are currently no sub components for Cells
Required Parameters
RfDataGrid needs the following parameters set in order to render properly.
Data - Without it the skeleton is shown.
TotalCount - Needs to show for pagination.
CurrentPageIndex - Needs to show for pagination.
SortOrder - Required if sorting is allowed.
SortKey - Required if sorting is allowed.
OnLoadData - Required to setup Data
Example
Below shows an example of setting up the RfDataGrid.
The example doesn't show the properties declarations or the OnLoadData function.
<RfDataGrid OnLoadData="@OnLoadData"
Data=UserCollection
TotalCount=TotalCount
@bind-CurrentPageIndex=CurrentPageIndex
@bind-SortOrder=CurrentSortOrder
@bind-SortKey=@CurrentSortKey>
<Headers>
<RfDgHeader SortKey="Id" >Id</RfDgHeader>
<RfDgHeader SortKey="FirstName" >First Name</RfDgHeader>
<RfDgHeader SortKey="LastName" >Last Name</RfDgHeader>
<RfDgHeader SortKey="Email" >Email</RfDgHeader>
</Headers>
<Filters>
<RfDgFilterNone />
<RfDgFilterInputText @bind-Value=Filter.FirstName />
<RfDgFilterInputText @bind-Value=Filter.LastName />
<RfDgFilterInputText @bind-Value=Filter.Email />
</Filters>
<Cells>
<td>@context.Id</td>
<td>@context.FirstName</td>
<td>@context.LastName</td>
<td>@context.Email</td>
</Cells>
</RfDataGrid>
Empty Content
If the Data is empty, the RfDataGrid will render a EmptyContent wrapped in a tr.
This is useful to show a message to the user that there is no data to display.
Example
<RfDataGrid OnLoadData="OnLoadData"
Data="UserCollection"
TotalCount="TotalCount"
bind-CurrentPageIndex="CurrentPageIndex"
bind-SortOrder="CurrentSortOrder"
bind-SortKey="CurrentSortKey">
<Headers>
<RfDgHeader SortKey="Id">Id</RfDgHeader>
<RfDgHeader SortKey="FirstName">First Name</RfDgHeader>
<RfDgHeader SortKey="LastName">Last Name</RfDgHeader>
<RfDgHeader SortKey="Email">Email</RfDgHeader>
</Headers>
<Cells>
<td>@context.Id</td>
<td>@context.FirstName</td>
<td>@context.LastName</td>
<td>@context.Email</td>
</Cells>
<EmptyContent>
<tr>
<td colspan="4">No data available.</td>
</tr>
</EmptyContent>
</RfDataGrid>
Paging on an Empty Grid
TotalPages is derived from TotalCount and PageSize, so a grid
with TotalCount=0 has zero pages. In that state the pager renders fully disabled: First
and Previous by the guard CurrentPageIndex == 0, and Next and Last by the guard
CurrentPageIndex + 1 >= TotalPages. The page tab list is empty, so there is no tab
to click into an invalid page either.
The page index is also clamped in code, not just visually: OnNextPageClick and
OnLastPageClick never target a negative page. On an empty grid both clamp to page 0,
which equals the current page, so CurrentPageIndexChanged does not fire at all. A
consumer that feeds the index into a Skip can never receive a negative index from the
pager. Paging on non-empty grids is unchanged.
Changing a filter always returns the grid to the first page, because the old page number may no longer
exist in the filtered results. CurrentPageIndexChanged(0) fires, and the pager label and the
next Next or Last click use page 1 straight away, the same as when the sort changes.
Async Context Events
The DataGridContext raises its notifications as async, multicast events:
OnSortChanged (raised by DataGridContext.SortChanged) and
OnFilterChanged (raised by DataGridContext.FilterChanged).
Any number of components can subscribe to the same event, including the
RfDataGrid itself: its own subscription is what resets paging and calls
OnLoadData.
Raising one of these events awaits every subscriber before it returns.
Subscribers are invoked in registration order; if a handler yields, the later handlers start
without waiting for it, and the raiser completes only once all of the handlers' tasks have
finished. After a sort click, for example, the event does not finish until both the grid's
data reload and every other OnSortChanged handler have completed.
Fault handling: all subscribers always run to completion. If exactly one subscriber faults,
its exception is rethrown unchanged to the code that raised the event. If more than one
faults, the faults are combined into a single AggregateException containing
every subscriber exception. A subscriber that throws before its first await
does not stop the others from running.
Example
A component rendered inside the grid can subscribe to the cascaded context. Below, one
handler records the sort and a second reloads related data; both complete before the sort
event returns.
public partial class SortWatcher : ComponentBase, IDisposable
{
[CascadingParameter]
private DataGridContext Context { get; set; }
private string LastSort { get; set; }
private List<RelatedItem> Related { get; set; }
protected override void OnInitialized()
{
Context.OnSortChanged += RecordSortAsync;
Context.OnSortChanged += ReloadRelatedAsync;
}
private Task RecordSortAsync(object sender, DataGridSortBy sortBy)
{
LastSort = $"{sortBy.SortKey} {sortBy.SortOrder}";
return Task.CompletedTask;
}
// Runs to completion before DataGridContext.SortChanged returns.
private async Task ReloadRelatedAsync(object sender, DataGridSortBy sortBy)
{
Related = await relatedApi.GetAsync(sortBy.SortKey);
StateHasChanged();
}
public void Dispose()
{
Context.OnSortChanged -= RecordSortAsync;
Context.OnSortChanged -= ReloadRelatedAsync;
}
}
Unsubscribe in Dispose. The context lives as long as the grid, so a forgotten
handler stays attached and is awaited on every future event.