Virtual scrolling in EJ2 TypeScript Scheduler control
31 Jan 202619 minutes to read
To improve performance when loading many resources and events, the Scheduler supports virtual scrolling. Virtual scrolling loads resources and events on demand as the user scrolls, reducing initial render time and memory usage. Enable virtual scrolling by setting true on the allowVirtualScrolling property within the view-specific settings. Virtual loading of events is also supported in the Agenda view by enabling allowVirtualScrolling in the agenda view settings.
import { Schedule, TimelineViews, TimelineMonth, TimelineYear, Resize, DragAndDrop } from '@syncfusion/ej2-schedule';
Schedule.Inject(TimelineViews, TimelineMonth, TimelineYear, Resize, DragAndDrop);
let ownerData: Object[] = generateResourceData(1, 300, 'Resource');
let eventData: Object[] = generateStaticEvents(new Date(2018, 4, 1), 300, 12);
let scheduleObj: Schedule = new Schedule({
height: '550px', width: '100%',
currentView: 'TimelineMonth',
views: [
{ option: 'TimelineMonth', eventTemplate: '#timeline-event-template', allowVirtualScrolling: true },
{ option: 'TimelineYear', orientation: 'Vertical', eventTemplate: '#timeline-event-template', allowVirtualScrolling: true }
],
group: {
byGroupID: false,
resources: ['Owners']
},
resources: [
{
field: 'OwnerId', title: 'Owner',
name: 'Owners', allowMultiple: true,
dataSource: ownerData,
textField: 'Text', idField: 'Id', colorField: 'Color'
}
],
selectedDate: new Date(2018, 4, 1),
eventSettings: { dataSource: eventData }
});
scheduleObj.appendTo('#Schedule');
function 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,
OwnerId: i + 1
});
id++;
}
}
return data;
}
function 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;
}<!DOCTYPE html>
<html lang="en">
<head>
<title>Schedule Typescript Component</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Typescript Schedule Control" />
<meta name="author" content="Syncfusion" />
<link href="index.css" rel="stylesheet" />
<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-buttons/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-calendars/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-dropdowns/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-inputs/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-navigations/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-popups/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-schedule/styles/tailwind3.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js" type="text/javascript"></script>
<script src="systemjs.config.js" type="text/javascript"></script>
<style>
.e-schedule .e-timeline-month-view .template-wrap .subject {
padding: 10px 25px;
}
.e-schedule .template-wrap {
width: 100%;
}
.e-schedule .e-timeline-year-view .template-wrap .subject {
padding: 1px 25px;
}
.e-schedule .e-more-event-popup .template-wrap .subject {
padding: 0px 25px;
}
.e-schedule .e-timeline-month-view .e-resource-left-td {
width: 150px;
}
</style>
<script id="timeline-event-template" type="text/x-template">
<div class='template-wrap' style='background:${PrimaryColor}'>
<div class="subject" style='background:${SecondaryColor};'>${Subject}</div>
</div>
</script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
<div id='loader'>LOADING....</div>
<div id='container'>
<div id="Schedule"></div>
</div>
</body>
</html>Note: Virtual loading of resources and events is not supported in the MonthAgenda, Year, and TimelineYear (horizontal orientation) views.
Enabling lazy loading for appointments
Lazy loading allows the Scheduler to fetch appointment data on demand for the currently visible resources, improving responsiveness for large datasets.
By default, the Scheduler requests all appointments for the current date range. When lazy loading is enabled, the Scheduler issues queries to the server whenever new resources are rendered due to scrolling. These queries include the resource IDs currently in view (usually passed as a comma-separated string) and the current date range. On the server, parse the resource IDs to filter and return only the appointments needed for the visible resources.
With lazy loading enabled, the Scheduler fetches events from remote services only for the current viewport, and remaining data is retrieved from the server on demand as the user scrolls through the Scheduler content.
Enable lazy loading by setting true for the enableLazyLoading property within the view-specific settings.
import { Schedule, TimelineMonth } from '@syncfusion/ej2-schedule';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
Schedule.Inject(TimelineMonth);
let resourceData: Object[] = generateResourceData(1, 1000, 'Resource');
let dataManager: DataManager = new DataManager({
url: 'https://services.syncfusion.com/js/production/api/VirtualEventData',
adaptor: new WebApiAdaptor,
crossDomain: true
});
let scheduleObj: Schedule = new Schedule({
height: '550px', width: '100%',
currentView: 'TimelineMonth',
readonly: true,
views: [
{ option: 'TimelineMonth', enableLazyLoading: true }
],
group: {
resources: ['Resources']
},
resources: [
{
field: 'ResourceId', title: 'Resources',
name: 'Resources',
dataSource: resourceData,
textField: 'Text', idField: 'Id', colorField: 'Color'
}
],
selectedDate: new Date(2023, 3, 1),
eventSettings: { dataSource: dataManager }
});
scheduleObj.appendTo('#Schedule');
function 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;
}<!DOCTYPE html>
<html lang="en">
<head>
<title>Schedule Typescript Component</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Typescript Schedule Control" />
<meta name="author" content="Syncfusion" />
<link href="index.css" rel="stylesheet" />
<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-buttons/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-calendars/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-dropdowns/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-inputs/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-navigations/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-popups/styles/tailwind3.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-schedule/styles/tailwind3.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js" type="text/javascript"></script>
<script src="systemjs.config.js" type="text/javascript"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
<div id='loader'>LOADING....</div>
<div id='container'>
<div id="Schedule"></div>
</div>
</body>
</html>Here’s a sample server-side controller that retrieves appointment data based on resource IDs provided 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; }
}
}Notes:
- This feature is most effective when a large number of resources and appointments are bound to the Scheduler.
- Lazy loading applies only when resource grouping is enabled on the Scheduler.
Refer to the JavaScript Scheduler feature tour for an overview of capabilities, and see the JavaScript Scheduler example to learn how to present and manipulate data.