Virtual scrolling in React Schedule component
30 Jan 202624 minutes to read
Virtual scrolling support in the Scheduler component enhances performance when working with a substantial number of resources and events. This feature allows large sets of resources and events to load dynamically in the timeline views as users scroll, resulting in a seamless user experience. Enable virtual scrolling by setting the allowVirtualScrolling property to true within the specific timeline view settings. In the Agenda view, enable the same property in the agenda view configuration to allow virtual loading of events.
import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { ScheduleComponent, ViewsDirective, ViewDirective, ResourcesDirective, ResourceDirective, TimelineMonth, TimelineYear, Resize, DragAndDrop, Inject } from '@syncfusion/ej2-react-schedule';
const App = () => {
const generateStaticEvents = (start, resCount, overlapCount) => {
let data = [];
let id = 1;
for (let i = 0; i < resCount; i++) {
let randomCollection = [];
let random = 0;
for (let j = 0; j < overlapCount; j++) {
random = Math.floor(Math.random() * (30));
random = (random === 0) ? 1 : random;
if (randomCollection.indexOf(random) !== -1 || randomCollection.indexOf(random + 2) !== -1 ||
randomCollection.indexOf(random - 2) !== -1) {
random += (Math.max.apply(null, randomCollection) + 10);
}
for (let k = 1; k <= 2; k++) {
randomCollection.push(random + k);
}
let startDate = new Date(start.getFullYear(), start.getMonth(), random);
startDate = new Date(startDate.getTime() + (((random % 10) * 10) * (1000 * 60)));
let endDate = new Date(startDate.getTime() + ((1440 + 30) * (1000 * 60)));
data.push({
Id: id,
Subject: 'Event #' + id,
StartTime: startDate,
EndTime: endDate,
IsAllDay: (id % 10) ? false : true,
ResourceId: i + 1
});
id++;
}
}
return data;
}
const eventSettings = { dataSource: generateStaticEvents(new Date(2018, 4, 1), 300, 12) }
const group = { resources: ['Resources'] };
const generateResourceData = (startId, endId, text) => {
let data = [];
let colors = [
'#ff8787', '#9775fa', '#748ffc', '#3bc9db', '#69db7c',
'#fdd835', '#748ffc', '#9775fa', '#df5286', '#7fa900',
'#fec200', '#5978ee', '#00bdae', '#ea80fc'
];
for (let a = startId; a <= endId; a++) {
let n = Math.floor(Math.random() * colors.length);
data.push({
Id: a,
Text: text + ' ' + a,
Color: colors[n]
});
}
return data;
}
return (<ScheduleComponent cssClass='virtual-scrolling' width='100%' height='550px' selectedDate={new Date(2018, 4, 1)} eventSettings={
eventSettings} group={group}>
<ResourcesDirective>
<ResourceDirective field='ResourceId' title='Resource' name='Resources' allowMultiple={true} dataSource={generateResourceData(1, 300, 'Resource')} textField='Text' idField='Id' colorField='Color'>
</ResourceDirective>
</ResourcesDirective>
<ViewsDirective>
<ViewDirective option='TimelineMonth' allowVirtualScrolling={true} isSelected={true} />
<ViewDirective option='TimelineYear' orientation='Vertical' allowVirtualScrolling={true} />
</ViewsDirective>
<Inject services={[TimelineMonth, TimelineYear, Resize, DragAndDrop]} />
</ScheduleComponent>);
}
const root = ReactDOM.createRoot(document.getElementById('schedule'));
root.render(<App />);import * as ReactDOM from 'react-dom';
import * as React from 'react';
import {
ScheduleComponent, ViewsDirective, ViewDirective, ResourcesDirective,
ResourceDirective, TimelineMonth, TimelineYear, Resize, DragAndDrop, Inject, EventSettingsModel, GroupModel
} from '@syncfusion/ej2-react-schedule';
const App = () => {
const generateStaticEvents = (start: Date, resCount: number, overlapCount: number): Object[] => {
let data: Object[] = [];
let id: number = 1;
for (let i: number = 0; i < resCount; i++) {
let randomCollection: number[] = [];
let random: number = 0;
for (let j: number = 0; j < overlapCount; j++) {
random = Math.floor(Math.random() * (30));
random = (random === 0) ? 1 : random;
if (randomCollection.indexOf(random) !== -1 || randomCollection.indexOf(random + 2) !== -1 ||
randomCollection.indexOf(random - 2) !== -1) {
random += (Math.max.apply(null, randomCollection) + 10);
}
for (let k: number = 1; k <= 2; k++) {
randomCollection.push(random + k);
}
let startDate: Date = new Date(start.getFullYear(), start.getMonth(), random);
startDate = new Date(startDate.getTime() + (((random % 10) * 10) * (1000 * 60)));
let endDate: Date = new Date(startDate.getTime() + ((1440 + 30) * (1000 * 60)));
data.push({
Id: id,
Subject: 'Event #' + id,
StartTime: startDate,
EndTime: endDate,
IsAllDay: (id % 10) ? false : true,
ResourceId: i + 1
});
id++;
}
}
return data;
}
const eventSettings: EventSettingsModel = { dataSource: generateStaticEvents(new Date(2018, 4, 1), 300, 12) };
const group: GroupModel = { resources: ['Resources'] };
const generateResourceData = (startId: number, endId: number, text: string): Object[] => {
let data: { [key: string]: Object }[] = [];
let colors: string[] = [
'#ff8787', '#9775fa', '#748ffc', '#3bc9db', '#69db7c',
'#fdd835', '#748ffc', '#9775fa', '#df5286', '#7fa900',
'#fec200', '#5978ee', '#00bdae', '#ea80fc'
];
for (let a: number = startId; a <= endId; a++) {
let n: number = Math.floor(Math.random() * colors.length);
data.push({
Id: a,
Text: text + ' ' + a,
Color: colors[n]
});
}
return data;
}
return (
<ScheduleComponent cssClass='virtual-scrolling' width='100%'
height='550px' selectedDate={new Date(2018, 4, 1)}
eventSettings={eventSettings}
group={group} >
<ResourcesDirective>
<ResourceDirective field='ResourceId' title='Resource' name='Resources' allowMultiple={true}
dataSource={generateResourceData(1, 300, 'Resource')}
textField='Text' idField='Id' colorField='Color'>
</ResourceDirective>
</ResourcesDirective>
< ViewsDirective >
<ViewDirective option='TimelineMonth' allowVirtualScrolling={true} isSelected={true} />
<ViewDirective option='TimelineYear' orientation='Vertical' allowVirtualScrolling={true} />
</ViewsDirective>
< Inject services={[TimelineMonth, TimelineYear, Resize, DragAndDrop]} />
</ScheduleComponent>
);
}
const root = ReactDOM.createRoot(document.getElementById('schedule'));
root.render(<App />);<!DOCTYPE html>
<html lang="en">
<head>
<title>Syncfusion React Schedule</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Essential JS 2 for React Components" />
<meta name="author" content="Syncfusion" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-base/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-buttons/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-calendars/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-dropdowns/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-inputs/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-navigations/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-popups/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-schedule/styles/tailwind3.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
<link href="index.css" rel="stylesheet" />
<script src="systemjs.config.js"></script>
<style>
#loader {
color: #008cff;
height: 40px;
left: 45%;
position: absolute;
top: 45%;
width: 30%;
}
.e-past-app {
background-color: chocolate !important;
}
.custom-class.e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment,
.custom-class.e-schedule .e-vertical-view .e-day-wrapper .e-appointment,
.custom-class.e-schedule .e-month-view .e-appointment {
background: green;
}
</style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
<div id='schedule'>
<div id='loader'>Loading....</div>
</div>
</body>
</html>For now, the virtual loading of resources and events is not supported in
MonthAgenda,YearandTimelineYear(Horizontal Orientation) views.
Enabling lazy loading for appointments
The lazy loading feature provides an efficient approach for loading appointment data into the Scheduler on-demand. This allows large volumes of appointments to be loaded without performance issues.
By default, the Scheduler retrieves all relevant appointments within the current date range from the server. When lazy loading is enabled, the Scheduler sends queries to the server to retrieve appointments only for resources currently displayed as a result of scroll actions. These queries include the resource IDs and the current date range as a comma-separated string. On the server, these resource IDs are parsed to filter and serve only the necessary appointments for rendering in the Scheduler.
With this feature enabled, the Scheduler fetches events from remote services solely for the appointments currently visible in the viewport, optimizing data retrieval. Additional appointment data is fetched from the server on-demand as new resources enter the viewport during scrolling.
Enable this feature by setting the enableLazyLoading property to true in the appropriate view settings.
import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { ScheduleComponent, ViewsDirective, ViewDirective, ResourcesDirective, ResourceDirective, TimelineMonth, Inject } from '@syncfusion/ej2-react-schedule';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
const App = () => {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/VirtualEventData',
adaptor: new WebApiAdaptor,
crossDomain: true
});
const eventSettings = { dataSource: dataManager }
const group = { resources: ['Resources'] };
const generateResourceData = (startId, endId, text) => {
let data = [];
let colors = [
'#ff8787', '#9775fa', '#748ffc', '#3bc9db', '#69db7c',
'#fdd835', '#748ffc', '#9775fa', '#df5286', '#7fa900',
'#fec200', '#5978ee', '#00bdae', '#ea80fc'
];
for (let a = startId; a <= endId; a++) {
let n = Math.floor(Math.random() * colors.length);
data.push({
Id: a,
Text: text + ' ' + a,
Color: colors[n]
});
}
return data;
}
return (<ScheduleComponent width='100%' height='550px' selectedDate={new Date(2023, 3, 1)} eventSettings={
eventSettings} group={group} readonly={true}>
<ResourcesDirective>
<ResourceDirective field='ResourceId' title='Resource' name='Resources' dataSource={generateResourceData(1, 1000, 'Resource')} textField='Text' idField='Id' colorField='Color'>
</ResourceDirective>
</ResourcesDirective>
<ViewsDirective>
<ViewDirective option='TimelineMonth' enableLazyLoading={true} isSelected={true} />
= </ViewsDirective>
<Inject services={[TimelineMonth]} />
</ScheduleComponent>);
}
const root = ReactDOM.createRoot(document.getElementById('schedule'));
root.render(<App />);import * as ReactDOM from 'react-dom';
import * as React from 'react';
import {
ScheduleComponent, ViewsDirective, ViewDirective, ResourcesDirective,
ResourceDirective, TimelineMonth, Inject, EventSettingsModel, GroupModel
} from '@syncfusion/ej2-react-schedule';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
const App = () => {
const dataManager: DataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/VirtualEventData',
adaptor: new WebApiAdaptor,
crossDomain: true
});
const eventSettings: EventSettingsModel = { dataSource: dataManager };
const group: GroupModel = { resources: ['Resources'] };
const generateResourceData = (startId: number, endId: number, text: string): Object[] => {
let data: { [key: string]: Object }[] = [];
let colors: string[] = [
'#ff8787', '#9775fa', '#748ffc', '#3bc9db', '#69db7c',
'#fdd835', '#748ffc', '#9775fa', '#df5286', '#7fa900',
'#fec200', '#5978ee', '#00bdae', '#ea80fc'
];
for (let a: number = startId; a <= endId; a++) {
let n: number = Math.floor(Math.random() * colors.length);
data.push({
Id: a,
Text: text + ' ' + a,
Color: colors[n]
});
}
return data;
}
return (
<ScheduleComponent width='100%'
height='550px' selectedDate={new Date(2023, 3, 1)}
eventSettings={eventSettings}
group={group} readonly={true}>
<ResourcesDirective>
<ResourceDirective field='ResourceId' title='Resource' name='Resources'
dataSource={generateResourceData(1, 1000, 'Resource')}
textField='Text' idField='Id' colorField='Color'>
</ResourceDirective>
</ResourcesDirective>
< ViewsDirective >
<ViewDirective option='TimelineMonth' enableLazyLoading={true} isSelected={true} />
</ViewsDirective>
< Inject services={[TimelineMonth]} />
</ScheduleComponent>
);
}
const root = ReactDOM.createRoot(document.getElementById('schedule'));
root.render(<App />);<!DOCTYPE html>
<html lang="en">
<head>
<title>Syncfusion React Schedule</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Essential JS 2 for React Components" />
<meta name="author" content="Syncfusion" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-base/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-buttons/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-calendars/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-dropdowns/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-inputs/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-navigations/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-popups/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-react-schedule/styles/tailwind3.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
<link href="index.css" rel="stylesheet" />
<script src="systemjs.config.js"></script>
<style>
#loader {
color: #008cff;
height: 40px;
left: 45%;
position: absolute;
top: 45%;
width: 30%;
}
</style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
<div id='schedule'>
<div id='loader'>Loading....</div>
</div>
</body>
</html>The following server-side controller code demonstrates how to retrieve appointment data based on resource IDs received as query parameters:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.OData.Query;
namespace LazyLoadingServices.Controllers
{
public class VirtualEventDataController : Controller
{
private readonly EventsContext dbContext;
[HttpGet]
[EnableQuery]
[Route("api/VirtualEventData")]
public IActionResult GetData([FromQuery] Params param)
{
IQueryable<EventData> query = dbContext.Events;
// Filter the appointment data based on the ResourceId query params.
if (!string.IsNullOrEmpty(param.ResourceId))
{
string[] resourceId = param.ResourceId.Split(',');
query = query.Where(data => resourceId.Contains(data.ResourceId.ToString()));
}
return Ok(query.ToList());
}
}
public class Params
{
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string ResourceId { get; set; }
}
}Note:
- The property will be effective, when large number of resources and appointments bound to the Scheduler.
- This property is applicable only when resource grouping is enabled in Scheduler.
See Also
You can refer to our React Scheduler feature tour page for its groundbreaking feature representations. You can also explore our React Scheduler example to knows how to present and manipulate data.