asp.net-corehtml-tablescrollblazor-webassembly

How to Get and Set the Scroll Position of a Table in Blazor WASM


I have a Blazor component that renders a table inside a div (see code below). The div has an id that ties it to CSS that specifies overflow:auto with max-height and max-width attributes. The table has sticky row and column headers. Scroll bars appear when the div overflows vertically and/or horizontally and the row and column headers remain in place when I scroll, all as expected.

I'd like to capture the vertical and horizontal scroll positions when I navigate to another component so that when I return to the table component, I can programmatically scroll back to where I was. I'm looking for guidance on how to capture the position when I navigate away and then scroll when I return. Thanks for any guidance you can provide.

<div class ="table-wrap">
    <table>
        <thead>
            @* Column Headers Row *@
            <tr>
                <th>&nbsp</th>

                @for (int colIndex = 0; colIndex < @Report.Columns.Count; colIndex++)
                    {
                    <th> @Report.Columns[colIndex].ColumnName </th>
                    }
            </tr>
        </thead>

        <tbody>
            @* Data Rows *@
            @for (int rowIndex = 0; rowIndex < @Report.Rows.Count; rowIndex++)
                {
                int rowIndex1 = @rowIndex;
                string rowId = $"row{rowIndex1}";

                if (@MES.CFS.ReportRowTagList[rowIndex1].Visible == false) continue;

                <tr class="@MES.CFS.ReportRowTagList[rowIndex1].ClassLeafOrTotal ">

                    @* Row Header *@

                    <th id="@rowId"
                        class="@MES.CFS.ReportRowTagList[rowIndex1].ClassIndent @MES.CFS.ReportRowTagList[rowIndex1].ClassDummy"
                        @onclick="@(() => ExCo_Click(@rowIndex1))">

                        @MES.CFS.ReportRowTagList[rowIndex1].RowHeader

                    </th>

                    @* Data Columns *@
                    @for (int colIndex = 0; colIndex < @Report.Columns.Count; colIndex++)
                        {
                        @*  For scrolling *@
                        int colIndex1 = colIndex;
                        string id = $"id-row{rowIndex1}-col{Report.Columns[colIndex1].ColumnName}";

                        string sign;
                        @if (MES.CFS.ReportRowTagList[rowIndex1].FormattedDataArray[@colIndex].StartsWith("-")) sign = "negative";
                        else sign = "positive";
                        <td id="@id" class="reportdata @sign">
                            @MES.CFS.ReportRowTagList[rowIndex1].FormattedDataArray[@colIndex]
                        </td>
                        }

                </tr>
                }
        </tbody>
    </table>
</div>

I've tried using onfocusout attributes on the table-wrap div to trigger an event when I navigate away from the table, but they aren't firing for some reason. Perhaps onfocusout isn't even valid but I'm not getting an error message.


Solution

  • You could try use jsinterop to get the table element position. By triggering the NaviationManager.OnlocationChanged event, using Blazored.SessionStorage to save the position value to session. Then read the position on page initialized.
    Install Blazored.SessionStorage

    builder.Services.AddBlazoredSessionStorage();
    

    Then following sample page will remember the scroll position when you navigate.

    @page "/"
    @using Blazored.SessionStorage
    @inject NavigationManager NAVI
    @inject IJSRuntime JS
    @inject ISessionStorageService SessionStorage
    
    
    <button @onclick="GetScrollPosition">GetScrollPosition</button>
    Current position: @scrollPosition[0],@scrollPosition[1]
    
    <style>
        .table-container {
            width: 1000px;
            height: 300px;
            overflow: auto;
        }
        thead {
            position: sticky;
            background-color: white;
            top: 0;
            z-index: 1;
        }
    
    </style>
    
    <div class="table-container" id="myTable">
        <table>
            <thead>
                <tr>
                    @for (int i = 1; i <= 100; i++)
                    {
                        <th>@i</th>
                    }
                </tr>
            </thead>
            <tbody>
                @for (int k = 1; k < 100; k++)
                {
                    <tr>
                        @for (int i = k; i < k + 100; i++)
                        {
                            <td>@i</td>
                        }
                    </tr>
                }
            </tbody>
        </table>
    </div>
    
    <script>
        function getScrollPosition(elementId) {
            const element = document.getElementById(elementId);
            if (element) {
                return [element.scrollTop, element.scrollLeft] // vertical scroll position
            }
            return 0;
        }
    </script>
    
    @code {
        private int[] scrollPosition = new int[] { 0, 0 };
    
        private async Task GetScrollPosition()
        {
            scrollPosition = await JS.InvokeAsync<int[]>("getScrollPosition", "myTable");
        }
        protected override async Task OnInitializedAsync()
        {
            NAVI.LocationChanged += OnNavi;
            scrollPosition = await SessionStorage.GetItemAsync<int[]>("position");
            if (scrollPosition == null)
            {
                scrollPosition = new int[] { 0, 0 };
            }
        }
        private async void OnNavi(object sender, LocationChangedEventArgs e)
        {
            await GetScrollPosition();
            await SessionStorage.SetItemAsync<int[]>("position", scrollPosition);
        }
    }