Brett JB
Brett JB

Reputation: 866

Blazor Quickgrid - how to add a row counter when using Virtualise

Using Blazors Quickgrid. I would like to show a row number for the items. They are 'ApplicationUser' Items. (The id is a guid) It looks like this:

            <div class="grid" tabindex="-1">
                <QuickGrid Items="FilteredUsers" Virtualize="true" @ref="_gridview" >
                    <PropertyColumn Property="@(u => u.FirstName)" Sortable="true" />
                    <PropertyColumn Property="@(u => u.LastName)" Sortable="true" Title="Name">
                        <ColumnOptions>
                            <input type="search" autofocus @bind="LastNamefilter" @bind:event="oninput" placeholder="Last Name ..." />
                        </ColumnOptions>
                    </PropertyColumn>
                    .
                    .
                    .
                    more stuff..

The 'FilteredUsers' comes from:


        private IQueryable<ApplicationUser> FilteredUsers;

        private void UpdateUsers()
        {
            FilteredUsers = _rgContext.Users.Where(u => u.LastName!.Contains(LastNamefilter)
             && u.Email!.Contains(Emailfilter)
             && u.PhoneNumber!.Contains(Phonefilter)
             );
            totalcount = FilteredUsers.Count();
        }

The _rgcontext is created using a contextFactory created in the OnInit function:

        protected override async Task  OnInitializedAsync()
        {
            _rgContext = _contextFactory.CreateDbContext();
            await _userService.CheckUpdateCurrentUserInfo();
            UpdateUsers();
            await base.OnInitializedAsync();
            return;
        }

... and disposed: (THough not sure if this is relevant... seems to work so far.

        public void Dispose()
        {
            _rgContext?.Dispose();
        }

I have the totalcount value from the query. But I would like to add a row number. I can't seem to find anywhere I can (say) get a callback to create a column with such a number in it.. Any thoughts?

Upvotes: 0

Views: 238

Answers (2)

Henk Holterman
Henk Holterman

Reputation: 273524

Easiest option is to use an ItemsProvider. I find that works better for large datasets with EF anyway.

An ItemsProvider is a callback with a request parameter. You can use request.StartIndex to base your numbering upon. You'll probably want to wrap the entities with a ViewModel to store the number.

Basic idea:


GridItemsProvider<TblAddress>? itemsProvider;
Dictionary<TblAddress, int> rownumLookup = new();
int? GetRowNum(TblAddress e) => rownumLookup.TryGetValue(e, out int row) ? row : null;

protected override async Task OnInitializedAsync()
{        
    itemsProvider = async request =>
    {
        using var dbContext = CtxFactory.CreateDbContext();

        var query = dbContext.TblAddresses.AsNoTracking();
        query = ApplyFilters(query);

        var result = new GridItemsProviderResult<TblAddress>
            {
                TotalItemCount = await query.CountAsync(request.CancellationToken),

                Items = await request.ApplySorting(query)
                                    .Skip(request.StartIndex)
                                    .Take(request.Count ?? 100)
                                    .ToListAsync(request.CancellationToken),
            };

        rownumLookup = result.Items
        .Select((TblAddress e, int n) => ( e, n ))
        .ToDictionary(t => t.e, t => request.StartIndex + t.n);

        return result;
    };
}

Upvotes: 1

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30177

Here's a quick and dirty demo using the WeatherForecast page. It demonstrates how to use request and result objects to pass data up and down the data pipeline.

First a data request object to pass down the data pipeline to get the data.

public readonly record struct DataRequest(int StartIndex, int PageSize, string SummaryFilter);

My quick and dirty mock up for the data pipeline:

public class DataBroker
{
    private List<WeatherForecast> _items = new List<WeatherForecast>();

    public event EventHandler<int>? ListUpdated;

    public DataBroker() 
    {
        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
        _items = Enumerable.Range(1, 5000).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        }).ToList();
    }

    public async ValueTask<GridItemsProviderResult<WeatherForecast>> GetDataAsync(DataRequest request)
    {
        // simulate an async call
        await Task.Delay(500);

        var query = _items.Where(item => item.Summary.Contains(request.SummaryFilter));

        var count = query.Count();

        var list = query
            .Skip(request.StartIndex)
            .Take(request.PageSize)
            .ToList();

        this.ListUpdated?.Invoke(null, count);
        
        return new GridItemsProviderResult<WeatherForecast>() { Items= list, TotalItemCount=count }; 
    }
}

Register the data broker in DI Services.

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddScoped<DataBroker>();
var app = builder.Build();

Next, a RecordCount Component to display the count [and update when it changes].

@implements IDisposable
@inject DataBroker _dataBroker

<div class="m-2 alert alert-info">
    Count: @_count
</div>

@code {
    private int _count;

    protected override void OnInitialized()
    {
        _dataBroker.ListUpdated += this.OnListUpdated;
    }

    private void OnListUpdated(object? sender, int count)
    {
        _count = count;
        this.StateHasChanged();
    }

    public void Dispose()
    {
        _dataBroker.ListUpdated -= this.OnListUpdated;
    }
}

And finally the demo Weather Page:

@page "/weather"
@inject DataBroker _dataBroker
<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<RecordCount />

<QuickGrid TGridItem="WeatherForecast" ItemsProvider="this.GetDataAsync" Virtualize>
    <PropertyColumn Title="Date" Property="@(c => c!.Date)" Format="dd-MMM-yy" />
    <PropertyColumn Title="Temp &deg; C" Sortable="true" Format="N0" Property="@(c => c!.TemperatureC)" Align=Align.End />
    <PropertyColumn Title="Temp &deg; F" Sortable="true" Format="N0" Property="@(c => c!.TemperatureF)" Align=Align.End />
    <PropertyColumn Title="Summary" Sortable="true" Property="@(c => c!.Summary)" Align=Align.Center />
</QuickGrid>


@code {
    private ValueTask<GridItemsProviderResult<WeatherForecast>> GetDataAsync(GridItemsProviderRequest<WeatherForecast> request)
    {
        // Build a data request from the GridItemsProviderRequest provided by Quickgrid
        var dataRequest = new DataRequest(request.StartIndex, request.Count ?? 0, "Hot");
        return _dataBroker.GetDataAsync(dataRequest);
    }
}

Upvotes: 1

Related Questions