Lazy data loading (infinite scroll)

Enabling lazy loading on the Scheduler makes it possible to load the different datasets in chunks when they rendered into view, instead of loading and replacing the datasets completely.

Before continuing reading, please read the Grid's lazy loading guide as it covers the basics of the lazy loading functionality.

There are two main paths to take when implementing lazy load support, either you use the CrudManager, or you do not.

Not using CrudManager

If you are not using the CrudManager, you will need to configure each store separately. Currently, these Scheduler stores supports lazy loading:

ResourceStore

The ResourceStore is used to populate the "rows" of the Scheduler. Please read the Grid's lazy loading guide for a detailed explanation of the basics of lazy loading which fully applies to the ResourceStore.

new Scheduler({
    resourceStore : {
        // This will create an AjaxStore
        readUrl: 'backend/resources/read',
        // This will activate the lazy load functionality
        lazyLoad: true,
        // This will load the Store initially upon creation
        autoLoad: true
    }
});

The other stores

Begin by setting the lazyLoad config on the Store to true to enable this behaviour.

Using an AjaxStore

If your store is an AjaxStore you only need to configure the readUrl, and then you are done (at least on the frontend).

new Scheduler({
    eventStore: {
        // This will create an AjaxStore
        readUrl: 'backend/events/read',
        // This will activate the lazy load functionality
        lazyLoad: true
    }
});

Using a regular Store

If you are using a regular Store, then you will need to implement the requestData function with your own data loading logic. How this function is called is a little different depending on store type:

  • For EventStore it is called when events for a resource in a certain timespan is requested, and has not yet been loaded.
  • For AssignmentStore it is called when the EvenStore's requestData is called. It is therefore not possible to have the EventStore's lazyLoad false and the AssignmentStore's lazyLoad true.
  • For TimeRangeStore it is called when the EvenStore's requestData is called. It is therefore not possible to have the EventStore's lazyLoad false and the TimeRangeStore's lazyLoad true.
  • For ResourceTimeRangeStore it is called when resourceTimeRanges for a resource in a certain timespan is requested, and has not yet been loaded.

requestData will be called with a single object argument containing a startIndex and a count value, and also a startDate and an endDate value. If the ResourceStore is configured as a tree, startIndex and count will be replaced by a resourceIds array. For a detailed explanation of these values, please read the 'Resource indexes and dates' chapter below.

class MyEventStore extends EventStore {
    static configurable = {
        lazyLoad: true
    };

    async requestData({
        startIndex,
        count,
        startDate,
        endDate
    }) {
        const response = await fetchData({
            startIndex,
            count,
            startDate,
            endDate
        });

        // The requestData function is expected to return an object
        // with a data property, which value contains all the records
        return {
            data: response.data
        }
    }
}

new Scheduler({
    eventStore: new MyEventStore()
});

ResourceStore as tree

If the ResourceStore is configured with tree set to true there are some very important differences to know about:

  1. For the lazy load functionality to recognize that a store is expecting tree data, the tree config must be set to true. The autoTree config is not supported.
  2. The isFullyLoaded field can be set from the backend, indicating that a TreeNode should not initiate any more load requests. If omitted, it will be set to true automatically when a load request returns a smaller number of children than asked for.

When using a Tree store, a load request will be made in the following scenarios:

  1. The initial load request, either by autoLoad or a separate load call.
  2. When a TreeNode is rendered and:
  3. A TreeNode with expanded set to false is manually or programmatically expanded

It is allowed, and in most cases recommended, to include nested data in the server responses. Please read more in the upcoming chapters.

Resource indexes and dates

For resources, the lazy loading depends on indexes to know what has been loaded and what has not. When it comes to events, resourceTimeRanges or assignments it is a bit more complicated. When a EventStore, for example, gets a request for events for a specified resource and a specified timespan, a load request will be made containing four parameters:

  • startIndex - The resource start index
  • count - The resource count
  • startDate - The start date of the calculated timespan
  • endDate - The end date of the calculated time span

If the ResourceStore is configured as a tree, or, the other store (i.e. the EventStore) is configured with useResourceIds the load request will contain these tree parameters instead:

  • resourceIds - An array of ids for every resource in the resource range
  • startDate - The start date of the calculated timespan
  • endDate - The end date of the calculated time span

If the ResourceStore has remote filters or sorters those will be applied to the other stores requests as well. It will use the filterParamName or sortParamName configured on the ResourceStore as parameter name, but if they are identical to those configured on the other store, the names will be prepended with the text "resource". For example, if sortParamName is identical on both the ResourceStore and the EventStore, a sortParamName of sort will be changed to resourceSort, .

The dates will be calculated using the current visible (rendered) timespan as a base. The duration specified in the Store's lazyLoad config's properties bufferUnit and bufferAmount will be subtracted from the visible start date and added to the visible end date. These values default to the full visible timespan length. If you are using a non-continuous timeaxis, you may have to adjust these values manually.

This "two-dimensional" request range will then be remembered, so when a new request is made, the EventStore knows that it has or has not already loaded events for that range, and can act accordingly.

Sorting and filtering

Local sorting and filtering is not supported when the store is lazy loaded. Remote sorting and filtering, however, is supported. If you use an AjaxStore configured with remote sorting or filtering (which is done by setting filterParamName or sortParamName), the sorter and/or filter information will be available in the fetch request parameters.

If you do not use an AjaxStore, you should configure the Store with remoteSort or remoteFilter. This will add a sorters and/or a filters param to the requestData call.

A filter or a sort action on the Store will clear all loaded records and a new load request will be made. This is because a sort or a filter action will change the record's indexes, which makes the loaded dataset invalid.

Backend

For each lazy load request you need to fetch records corresponding to the specified parameters:

  1. The record should have a resourceId in the resource range (startIndex and count, or included in resourceIds). Ignore for timeRanges.
  2. The record should either have a startDate or an endDate in the date range (startDate and endDate).
The `endDate` is interpreted as "exclusive", meaning that the comparing date must be less or greater, but not equal.

If you are using AjaxStore, the response(s) need to look like this:

// Return the expected JSON response
res.json({
    success: true,
    data: events // Or timeRanges, or assignments etc...
});

Using CrudManager

When you are using the CrudManager, things are a bit simpler. Set the lazyLoad to true and see to it that you have a loadUrl configured. Also, you will either have to set autoLoad to true or call the load function manually.

new Scheduler({
    crudManager: {
        loadUrl: 'backend/events/read',
        // This will activate the lazy load functionality
        lazyLoad: true,
        // This will initiate the first load upon creation
        autoLoad: true,
    }
});

The CrudManager will initiate a load request either when the ResourceStore requests more records or when the EventStore requests more records. Then it will perform a load request to the specified loadUrl and add the data from the response to the affected Stores.

Data for the Stores that currently has no lazy load support, can either be supplied in the first load request, or set manually.

Please read the chapter about Resource indexes and dates as it applies when using the CrudManager as well.

Backend

When implementing a backend that serves the CrudManager, you first need to read the CrudManager guide. Here is a simple example of a (pseudo) JavaScript read endpoint.

const { 
    startIndex,
    count,
    startDate,
    endDate,
    requestId
    }           = JSON.parse(params), // Params from the client
    // Extracting the resources that's requested
    resources   = ALL_RESOURCES.slice(startIndex, startIndex + count),
    resourceIds = resources.map(resource => resource.id),
    // Extracting the events that's requested
    events      = ALL_EVENTS.filter(event => resourceIds.includes(event.resourceId) 
            && ((event.startDate >= startDate && event.startDate < endDate) 
            || (event.endDate > startDate && event.endDate <= endDate)));

return {
    responseText : JSON.stringify({
        success      : true,
        requestId,
        events       : loadEvents(startIndex, count, new Date(startDate), new Date(endDate)),
        resources    : loadResources(startIndex, count),
        assignments  : singleAssignments ? undefined : loadAssignments(startIndex, count, new Date(startDate), new Date(endDate)),
        dependencies : { rows : [{ id : 1, from : 1, to : 2 }] }
    })
};

The params which are sent to the backend also contains an array of ids of the Stores that is requested. On the first load, all stores managed by the CrudManager will be included in the array. After that, only the lazy load supported stores will be included. If the timespan of the Scheduler changes, a request will be initiated from the EventStore, and in those requests the stores param will exclude the ResourceStore as well. You need not provide data for stores that is not present in the stores param.

Not supported

There is a number of Scheduler features, functions and configs that is either not supported at all or only works in a limited way. Such information is available in the corresponding documentation. Please note that support will continuously be added where it makes sense to be added. Please let us know of the functionality that lacks support and is most important to you.

LazyLoading is currently not supported in Calendar, Gantt nor TaskBoard.