asp.netrazorblazorblazor-server-siderazor-class-library

Persisting data between events in Razor Class Library control


I am trying to create a Razor Class Library (RCL), which I can then use as a reusable set of controls in my Blazor Server app. The RCL control is a table that includes pagination and sorting of columns. The data flow for the control is that the Blazor app handles all the data request and then passed the data to the RCL control. The RCL control handles UI events (next/previous pagination) and the display of the records. The issue I have is that the current page count (which gets updated when the user clicks next or previous page) does not persist between events, so the count never changes. How can I persist the data across events (I am hoping to keep track of ui events within the RCL control and not in the Blazor app using the control). Here is my code (abbreviated):

Razor Class Library Control:

@if (employeeModels == null)
{
    <p><em>Loading...</em></p>
}
else
{
   //table with data ...
   //controls to handle pagination
   <div class="pagination">
    <button class="btn btn-custom" @onclick=@(async ()=>await NavigateToPage("previous"))>Prev</button>
    @for (int i = startPage; i <= endPage; i++)
    {
        var currentPage = i;
        <button class="btn btn-custom pagebutton @(currentPage==curPage?"btn-danger":"")" @onclick=@(async () =>await refreshRecords(currentPage))>
            @currentPage
        </button>
    }
    <button class="btn btn-custom" @onclick=@(async ()=>await NavigateToPage("next"))>Next</button>
</div>
}
@code {
    [Parameter]
    public List<EmployeesModel> employeeModels { get; set; }
    [Parameter]
    public EventCallback<bool> RefreshDataEvent { get; set; }
    int totalPages;
    int totalRecords;
    [Parameter]
    public int TotalNumRecords { get; set; }
    [Parameter]
    public int curPage { get; set; }
    [Parameter]
    public int pagerSize{ get; set; }
    [Parameter]
    public int pageSize { get; set; }
    int startPage;
    int endPage;
    [Parameter]
    public string sortColumnName { get; set; } = "LastName";
    [Parameter]
    public string sortDir { get; set; } = "DESC";

    protected override void OnParametersSet()
    {

        base.OnParametersSet();
        totalRecords = TotalNumRecords;
        totalPages = (int)Math.Ceiling(totalRecords / (decimal)pageSize);
        SetPagerSize("forward");        
    }
    public async Task refreshRecords(int currentPage)
    { 
        curPage = currentPage;
        await RefreshDataEvent.InvokeAsync(true);
        this.StateHasChanged();
    }
    public void SetPagerSize(string direction)
    {
        if (direction == "forward" && endPage < totalPages)
        {
            startPage = endPage + 1;
            if (endPage + pagerSize < totalPages)
            {
                endPage = startPage + pagerSize - 1;
            }
            else
            {
                endPage = totalPages;
            }
            this.StateHasChanged();
        }
        else if (direction == "back" && startPage > 1)
        {
            endPage = startPage - 1;
            startPage = startPage - pagerSize;
        }
    }
    public async Task NavigateToPage(string direction)
    {
        if (direction == "next")
        {
            if (curPage < totalPages)
            {
                if (curPage == endPage)
                {
                    SetPagerSize("forward");
                }
                curPage += 1;
            }
        }
        else if (direction == "previous")
        {
            if (curPage > 1)
            {
                if (curPage == startPage)
                {
                    SetPagerSize("back");
                }
                curPage -= 1;
            }
        }    
        await refreshRecords(curPage);
    }
}

Blazor App Using Control:

<SortablePaginatedEmployeeTable  
    employeeModels="@employees" 
    curPage="@paginationCurrentPage" 
    pageSize="@paginationPageSize" 
    pagerSize="@paginationPagerSize" 
    sortColumnName="@paginationSortColumnName" 
    sortDir="@paginationSortDirection" 
    RefreshDataEvent="EmployeeListRefrshed" 
    @ref="EmployeeTableControl" 
    TotalNumRecords="@totalNumbEmployees" 
    GotToPageEven="EmployeeNextClick"  >
</SortablePaginatedEmployeeTable>

@code {
    private SortablePaginatedEmployeeTable EmployeeTableControl;
    int paginationCurrentPage = 1;
    int paginationPageSize = 3;
    int paginationPagerSize = 3;
    string paginationSortColumnName = "LastName";
    string paginationSortDirection = "Desc";
    int totalNumbEmployees = 0;

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        await EmployeeListRefrshed();
    }
    
    private async Task EmployeeListRefrshed()
    {
        all_employees = await _db.ListAllEmployees((EmployeeTableControl.curPage - 1) * paginationPageSize, paginationPageSize, paginationSortColumnName, paginationSortDirection);
        totalNumbEmployees = _db.CountNumberEmployees();
        foreach(var e in all_employees)
        {
            employees.Add(new EmployeesModel { Email = e.Email, FirstName = e.FirstName, LastName = e.LastName, Id = e.Id, PhoneNumber = e.PhoneNumber });
        }
    }

}

With this code, when the user clicks Next the NavigateToPage function gets called but it always returns 2. I believe this is because the data is not persisting between button presses? How can I resolve this?


Solution

  • Your problem is in updating a Parameter in a Component. It's basically a NONO. Read them, use them, but never modify them.

    When you trigger RefreshDataEvent it calls EmployeeListRefrshed which gets the data. This updates employees. When the EmployeeListRefrshed completes the component re-renders. This includes triggering a re-render of SortablePaginatedEmployeeTable. paginationCurrentPage is still set to 1 so the component re-renders on page 1.

    You need to do something like:

    Component

    [Parameter]
        public EventCallback<int> RefreshDataEvent { get; set; }
    
        public async Task refreshRecords(int currentPage)
        { 
            await RefreshDataEvent.InvokeAsync(currentPage);
            // probably not needed
            this.StateHasChanged();
        }
    
    

    Page

        
        private async Task EmployeeListRefrshed(int pageNo)
        {
            all_employees = await _db.ListAllEmployees((pageNo - 1) * paginationPageSize, paginationPageSize, paginationSortColumnName, paginationSortDirection);
            totalNumbEmployees = _db.CountNumberEmployees();
            foreach(var e in all_employees)
            {
                employees.Add(new EmployeesModel { Email = e.Email, FirstName = e.FirstName, LastName = e.LastName, Id = e.Id, PhoneNumber = e.PhoneNumber });
            }
            this.pagingationcurrentpage = pageNo 
        }