Infinite scrolling in ASP.NET MVC Grid component

25 Nov 202415 minutes to read

The infinite scrolling feature in the Grid is a powerful tool for seamlessly handling extensive data sets without compromising grid performance. It operates on a “load-on-demand” concept, ensuring that data is fetched only when needed. In the default infinite scrolling mode, a new block of data is loaded each time the scrollbar reaches the end of the vertical scroller. This approach significantly enhances the user experience when working with large data collections in the ASP.NET MVC Grid.

In this mode, a block of data accumulates every time the scrollbar reaches the end of the scroller. To clarify, in this context, a block represents the PageSettings.PageSize of the Grid. If the PageSize is not explicitly specified, the Grid will automatically calculate it based on the grid viewport height and row height.

To enable infinite scrolling, you need to define EnableInfiniteScrolling as true and content height by Height property.

In this feature, the Grid will not initiate a new data request when revisiting the same page.
The Height property must be specified when enabling EnableInfiniteScrolling.

The following an example that demonstrates how to enable infinite scroll in the Grid:

@Html.EJS().Grid("grid").DataSource((IEnumerable<object>)ViewBag.dataSource).EnableInfiniteScrolling().Height("300").Columns(col =>
{
    col.Field("TaskID").HeaderText("Task ID").Width("120").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Engineer").HeaderText("Engineer").Width("150").Add();
    col.Field("Designation").HeaderText("Designation").Width("120").Format("C2").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Estimation").HeaderText("Estimation").Width("150").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Status").HeaderText("Status").Width("170").Add();
}).PageSettings(page => { page.PageSize(50); }).Render();
public IActionResult Index()
{
    ViewBag.dataSource = GenerateData(5000);
    return View();
}

private List<object> GenerateData(int count)
{
    var names = new[] { "TOM", "Hawk", "Jon", "Chandler", "Monica", "Rachel", "Phoebe", "Gunther", "Ross", "Geller", "Joey", "Bing", "Tribbiani", "Janice", "Bong", "Perk", "Green", "Ken", "AdAMS" };
    var hours = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    var designation = new[] { "Manager", "Engineer 1", "Engineer 2", "Developer", "Tester" };
    var status = new[] { "Completed", "Open", "In Progress", "Review", "Testing" };
    var random = new Random();
    var data = new List<object>();
    for (int i = 0; i < count; i++)
    {
        data.Add(new
        {
            TaskID = i + 1,
            Engineer = names[random.Next(names.Length)],
            Designation = designation[random.Next(designation.Length)],
            Estimation = hours[random.Next(hours.Length)],
            Status = status[random.Next(status.Length)]
        });
    }
    return data;
}

Infinite scrolling

Number of blocks rendered during initial loading

The number of blocks to be initially rendered when the Grid is loaded. Each block corresponds to a page size of the Grid, resulting in the rendering of a certain number of row elements determined by multiplying the initial block size with the page size.

You can define the initial loading pages count by using InfiniteScrollSettings.InitialBlocks property . By default, this property loads three pages during the initial rendering. Subsequently, additional data is buffered and loaded based on either the page size or the number of rows rendered within the provided height.

The following an example of how you can use the InitialBlocks property to set the initial loading pages based on DropDownList input:

@{
    ViewBag.dropDownData = new List<object> {
       new { text = "1", value = "1" },
       new { text = "2", value = "2" },
       new { text = "3", value = "3" },
       new { text = "4", value = "4" },
       new { text = "5", value = "5" },
       new { text = "6", value = "6" },
       new { text = "7", value = "7" },
       new { text = "8", value = "8" },
   };

}
<div style="display: flex">
    <label style="padding: 10px 10px 26px 0;font-weight:bold"> Select initialBlocks count:  </label>
    <span style="height:fit-content">
        @Html.EJS().DropDownList("dropDown").DataSource(@ViewBag.dropDownData).Width("120px").Index(2).Change("valueChange").Render()
    </span>
</div>
@Html.EJS().Grid("grid").DataSource((IEnumerable<object>)ViewBag.dataSource).EnableInfiniteScrolling().Height("300").Columns(col =>
{
    col.Field("TaskID").HeaderText("Task ID").Width("120").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Engineer").HeaderText("Engineer").Width("150").Add();
    col.Field("Designation").HeaderText("Designation").Width("120").Format("C2").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Estimation").HeaderText("Estimation").Width("150").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Status").HeaderText("Status").Width("170").Add();
}).PageSettings(page => { page.PageSize(50); }).Render();
<script>
    function valueChange(args) {
        var grid = document.getElementById("grid").ej2_instances[0];
        grid.infiniteScrollSettings.initialBlocks = parseInt(args.value, 10);
        grid.refresh();
    }
</script>
public IActionResult Index()
{
    var data = GenerateData(5000);
    ViewBag.dataSource = data;
    return View();
}

private List<GridData> GenerateData(int count)
{
    var names = new[] { "TOM", "Hawk", "Jon", "Chandler", "Monica", "Rachel", "Phoebe", "Gunther", "Ross", "Geller", "Joey", "Bing", "Tribbiani", "Janice", "Bong", "Perk", "Green", "Ken", "Adams" };
    var hours = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    var designations = new[] { "Manager", "Engineer 1", "Engineer 2", "Developer", "Tester" };
    var statuses = new[] { "Completed", "Open", "In Progress", "Review", "Testing" };

    var result = new List<GridData>();
    var random = new Random();
    for (var i = 0; i < count; i++)
    {
        result.Add(new GridData
           {
                TaskID = i + 1,
                Engineer = names[random.Next(names.Length)],
                Designation = designations[random.Next(designations.Length)],
                Estimation = hours[random.Next(hours.Length)],
                Status = statuses[random.Next(statuses.Length)]
            });
    }
    return result;
}

public class GridData
{
    public int TaskID { get; set; }
    public string Engineer { get; set; }
    public string Designation { get; set; }
    public int Estimation { get; set; }
    public string Status { get; set; }
}

blocks rendered

Efficient data caching and DOM management in grid cache mode

In Grid cache mode, cached data blocks are reused when revisiting them, reducing the need for frequent data requests while navigating the same block. This mode also manages DOM row elements based on the InfiniteScrollSettings.MaxBlocks count value. If this limit is exceeded, it removes a block of row elements to create new rows.

To enable cache mode, you need to define EnableCache property of InfiniteScrollSettings as true.

To enable maximum blocks, you need to define MaxBlocks count of InfiniteScrollSettings, By default this property value is 3.

The following example that demonstrates how to enable/disable cache mode in infinite scrolling of the grid based on a Switch componentChange event :

<div style="padding-bottom: 20px; display: flex">
    <label style="margin: 0px 5px 0px 0px;font-weight: bold;">Enable/Disable Cache mode</label>
    @Html.EJS().Switch("switch").Change("toggleCacheMode").Render()
</div>
@Html.EJS().Grid("grid").DataSource((IEnumerable<object>)ViewBag.dataSource).EnableInfiniteScrolling().Height("300").Columns(col =>
{
    col.Field("TaskID").HeaderText("Task ID").Width("120").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Engineer").HeaderText("Engineer").Width("150").Add();
    col.Field("Designation").HeaderText("Designation").Width("120").Format("C2").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Estimation").HeaderText("Estimation").Width("150").TextAlign(Syncfusion.EJ2.Grids.TextAlign.Right).Add();
    col.Field("Status").HeaderText("Status").Width("170").Add();
}).PageSettings(page => { page.PageSize(50); }).Render();
<script>
    function toggleCacheMode(args) {
        var grid = document.getElementById("grid").ej2_instances[0];
        grid.infiniteScrollSettings.enableCache = args.checked;
        grid.refresh();
    }
</script>
public IActionResult Index()
{
    var data = GenerateData(5000);
    ViewBag.dataSource = data;
    return View();
}

private List<GridData> GenerateData(int count)
{
    var names = new[] { "TOM", "Hawk", "Jon", "Chandler", "Monica", "Rachel", "Phoebe", "Gunther", "Ross", "Geller", "Joey", "Bing", "Tribbiani", "Janice", "Bong", "Perk", "Green", "Ken", "Adams" };
    var hours = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    var designations = new[] { "Manager", "Engineer 1", "Engineer 2", "Developer", "Tester" };
    var statuses = new[] { "Completed", "Open", "In Progress", "Review", "Testing" };

    var result = new List<GridData>();
    var random = new Random();
    for (var i = 0; i < count; i++)
    {
        result.Add(new GridData
           {
                TaskID = i + 1,
                Engineer = names[random.Next(names.Length)],
                Designation = designations[random.Next(designations.Length)],
                Estimation = hours[random.Next(hours.Length)],
                Status = statuses[random.Next(statuses.Length)]
            });
    }
    return result;
}

public class GridData
{
    public int TaskID { get; set; }
    public string Engineer { get; set; }
    public string Designation { get; set; }
    public int Estimation { get; set; }
    public string Status { get; set; }
}

Limitations

  • Due to the element height limitation in browsers, the maximum number of records loaded by the grid is limited due to the browser capability.
  • It is necessary to set a static height for the component or its parent container when using infinite scrolling. The 100% height will work only if the component height is set to 100%, and its parent container has a static height.
  • When infinite scrolling is activated, compatibility for copy-paste and drag-and-drop operations is limited to the data items visible in the current viewport of the grid.
  • Cell selection will not be persisted in cache mode.
  • The group records cannot be collapsed in cache mode.
  • Lazy load grouping with infinite scrolling does not support cache mode, and the infinite scrolling mode is exclusively applicable to parent-level caption rows in this scenario.
  • The aggregated information and total group items are displayed based on the current view items. To get these information regardless of the view items, refer to the Group with paging topic.
  • InfiniteScrolling support for Normal and Dialog mode editing, so this feature is not compatible with AutoFill.
  • Programmatic selection using the selectRows and selectRow method is not supported in infinite scrolling.
  • Infinite scrolling is not compatible with the following features:
    1. Batch editing
    2. Normal editing
    3. Row spanning
    4. Column spanning
    5. Row template
    6. Row virtual scrolling
    7. Detail template
    8. Hierarchy features
    9. Autofill
    10. Page
  • Limitations of row drag and drop with infinite scrolling
    1. In cache mode, the grid refreshes automatically if the content’s tr element count exceeds the cache limit of the grid’s content after the drop action.
    2. When performing row drag and drop with lazy load grouping, the grid will refresh automatically.
    3. In remote data, changes are applied only in the UI. They will be lost once the grid is refreshed. To restore them, you need to update the changes in your database. By using the RowDrop event, you can send the request to the server and apply the changes in your database. After this, you need to refresh the grid to show the updated data.