Crud Manager in the Scheduler

Introduction

The Crud Manager handles communication between multiple stores and a backend, loading and syncing them using a single request. It uses the Fetch API to send and receive JSON formatted data.

This guide describes how to use the Crud Manager in Bryntum Scheduler. It contains only Scheduler specific details. For in depth information on Crud Manager implementation and architecture see this guide.

Scheduler stores

The Crud Manager supports these Scheduler stores out of the box:

StoreDescription
resourceStoreHolds a collection of resources
eventStoreHolds a collection of events
assignmentStoreHolds a collection of assignments
dependencyStoreHolds a collection of dependencies
timeRangeStoreHolds a collection of time ranges
resourceTimeRangeStoreHolds a collection of resource time ranges

Here's how a basic configuration could look:

const crudManager = new CrudManager({
    autoLoad : true,
    loadUrl  : 'php/read.php',
    syncUrl  : 'php/save.php'
});

You can either pass the crudManager instance to Scheduler:

const scheduler = new Scheduler({
    // ... other configs
    crudManager : crudManager
});

or pass the configuration object directly without first creating an instance:

const scheduler = new Scheduler({
    // ... other configs
    crudManager : {
        autoLoad : true,
        loadUrl  : 'php/read.php',
        syncUrl  : 'php/save.php'
    } 
});

Load inline data

You can also load inline data, please read the working with inline data guide for more information.

Load request structure

The load request has a payload, which by default looks like:

{
    "type"      : "load",
    "requestId" : 17228564331330,
    "stores"    : [ "resources", "events", "assignments", "dependencies", ... ]
}

The stores property contains a list of all the stores the crud manager uses. This property is optional and can be modified. You can use this information for any server-side implementation. For example when a backend should support loading of only certain crud manager stores. In this case, one should pass the list of stores to load in that property.

To modify the stores property, you can use the beforeLoad event. This event allows you to customize a load request data before it's sent to the backend.

crudManager.on('beforeLoad', ({ source, pack }) => { 
    const updatedStores = ['dependencies', 'assignments'];
    pack.stores = updatedStores;
});

Alternatively, you can remove a store from the Crud Manager, which will exclude it from the load request payload. Of course in such case the crud manager will not serve the removed store (not load its data nor sync its changes). So your application will have to do it manually.

const crudManager = new CrudManager({});
crudManager.removeCrudStore("assignments");

By using this method, the assignmentStore will not receive any data, ensuring it remains empty regardless of the backend response.

Load response structure

The backend (in the above case it's "php/read.php" script) should return a JSON similar to the one seen below:

{
    "success" : true,

    "events" : {
        "rows" : [
            {
                "id"          : 11,
                "name"        : "Investigate",
                "startDate"   : "2021-02-08",
                "endDate"     : "2021-02-13"
            },
            {
                "id"          : 12,
                "name"        : "Assign resources",
                "startDate"   : "2021-02-08",
                "endDate"     : "2021-02-20"
            },
            {
                "id"          : 17,
                "name"        : "Report to management",
                "startDate"   : "2021-02-20",
                "endDate"     : "2021-02-20"
            }
        ]
    },

    "resources" : {
        "rows" : [
            {
                "id"   : 1,
                "name" : "Mats"
            },
            {
                "id" : 2,
                "name" : "Nickolay"
            }
        ]
    },

    "assignments" : {
        "rows" : [
            {
                "id"       : 1,
                "event"    : 11,
                "resource" : 1
            }
        ]
    },

    "dependencies" : {
      "rows" : [
        {
          "id"      : 1,
          "from"    : 11,
          "to"      : 17
        }
      ]
    }
}

The above response sections contain corresponding stores data which are covered in the following chapters.

Event data

The Crud Manager reads events from the events section of the load response. The records are provided as an array of objects under the rows property. In the provided response example it looks like this:

{
    ...

    "events": {
        "rows": [
            {
                "id"          : 11,
                "name"        : "Investigate",
                "startDate"   : "2021-02-08",
                "endDate"     : "2021-02-13"
            },
            {
                "id"          : 12,
                "name"        : "Assign resources",
                "startDate"   : "2021-02-08",
                "endDate"     : "2021-02-20"
            },
            {
                "id"          : 17,
                "name"        : "Report to management",
                "startDate"   : "2021-02-20",
                "endDate"     : "2021-02-20"
            }
        ]
    }
}

Each object in the events.rows array represents a EventModel where each object key represents an event field. See EventModel fields for the full list of event fields.

Resources data

The Crud Manager reads resources from the resources section of the load response. The records are provided as an array of objects under the rows property. In the provided response example it looks this:

{
    ...

    "resources" : {
        "rows" : [
            {
                "id"   : 1,
                "name" : "Mats"
            },
            {
                "id"   : 2,
                "name" : "Nickolay"
            }
        ]
    }
}

Each object in the resources.rows array represents a ResourceModel where each object key represents a resource field. See ResourceModel fields for the full list of resource fields.

Assignments data

Assignments specify resources assigned to certain tasks. The Crud Manager reads them from the assignments section of the load response. The records are provided as an array of objects under the rows property. In the provided response example it looks this:

{
    ...

    "assignments" : {
        "rows" : [
            {
                "id"       : 1,
                "event"    : 11,
                "resource" : 1
            }
        ]
    }
}

Each object in the assignments.rows array represents an AssignmentModel where each object key represents an assignment field. See AssignmentModel fields for the full list of assignment fields.

Dependencies data

Event dependencies represent links between events that visually indicates how events are dependent of each other. The Crud Manager reads them from the dependencies section of the load response. The records are provided as an array of objects under the rows property. In the provided response example it looks this:

{
    ...

    "dependencies": {
        "rows" : [
            {
                "id"      : 1,
                "from"    : 11,
                "to"      : 17
            }
        ]
    }
}

Each object in the dependencies.rows array represents a DependencyModel where each object key represents a dependency field. See DependencyModel fields for the full list of dependency fields.

Sync request structure

Syncing includes changes for all linked stores in a single request, with sections for added, updated and removed records per store. For changes to the EventStore and the ResourceStore a sync request might look like this:

{
    "requestId" : 124,
    "type"      : "sync",
    "revision"  : 5,

    "events"     : {
        "added" : [
            { "$PhantomId" : "_generated5", "name" : "New event" }
        ],
        "updated" : [
            { "id" : 50, "startDate" : "2022-05-02" }
        ],
        "removed" : [
            { "id" : 9001 }
        ]
    },

    "resources"      : {
        "added" : [
            { "$PhantomId" : "_generated7", "name" : "Steven", "surname" : "Grant" }
        ]
    }
}

Each added record is sent should include its phantom identifier (auto-generated client side unique value used to identify the record) (by default the $PhantomId, field name is used). Please do not persist phantom record identifiers as-is on the server. That might cause collisions on the client after data reloading. It's expected that backend assigns new identifiers to added records.

For more details on the sync request structure, please see the Crud Manager in depth guide.

Sync response structure

The response to a sync request should confirm that changes were applied and optionally update the client with any additional changes made on the server.

If there are no additional changes made on the server, a short sync response such as this one is enough:

{
    "success"   : true,
    "requestId" : 124,
    "revision"  : 6
}

The success attribute is by default optional for successful calls, and if you are not using revision validation the response can be made even shorter:

{
    "requestId" : 124
}

Whenever the server makes changes to the synced data, the new values must be part of the response. For example, when saving a new record the server provides a new value for its id, and that has to be included for the client side to use the correct id. This is a valid response to the sync request above:

{
    "success"     : true,
    "requestId"   : 124,
    "revision"    : 6,

    "events" : {
        "rows" : [
            { "$PhantomId" : "_generated5", "id" : 543, "added_dt" : "2022-05-02T11:30:00" }
        ]
    },

    "resources" : {
        "rows" : [
            { "$PhantomId" : "_generated7", "id" : 12 }
        ],
        "removed" : [
            { "id" : 5 }
        ]
    }
}

For each store there are two possible sections: rows and removed.

The rows section list data changes made by the server.

If the server decides to update any other field of any record it should return an object holding a combination of the record identifier and new field values (this is shown in above snippet where server sets added_dt field value). When adding a new record the server generates an identifier for it and responds with both old phantom identifier and the new identifier. The field values will be applied to the corresponding store record on the client.

Note that this way the server can also provide new records to the client by passing them in the rows section.

The removed section contains identifiers of records removed by the server, perhaps by another user since the last load or sync call. In the above snippet, the response includes removal of a resource with id 5, initiated by the server.

For more details on the sync response structure, please see the Crud Manager in depth guide.

Sending extra HTTP request parameters

Extra params may be added using transport configuration:

const crudManager = new CrudManager({
    transport : {
        load : {
            url    : 'php/read.php',
            method : 'POST',
            params : {
                userAccess : 'granted',
                viewId     : 'full'
            }
        },
        sync : {
            url : 'php/save.php'
        }
    }
});

Or dynamically by passing into the load method:

crudManager.load({
    request : {
        params : {
            startDate : '2021-01-01'
        }
    }
})

Or by listening to the beforeLoad event:

const crudManager = new CrudManager({
    loadUrl : 'php/read.php',
    syncUrl : 'php/save.php',
    listeners : {
        beforeLoad({ pack }){
            pack.params.includeHidden = false;
        }
    }
});

Dealing with extra stores

You can provide any number of additional stores using the crudStores config:

const 
    store1      = new Store({ id : 'store1' }),
    store2      = new Store({ id : 'store2' }),
    store3      = new Store({ id : 'store3' }),
    crudManager = new CrudManager({
        // Register additional stores, to also handle them
        // in a batch when loading data
        crudStores : [ store1, store2, store3 ],
        loadUrl    : 'php/read.php',
        syncUrl    : 'php/save.php'
});

Or add them programmatically using the addCrudStore method:

crudManager.addCrudStore([ store2, store3 ]);

Triggering loading and saving

In the following example the CrudManager will start data loading automatically due to the provided autoLoad config. In this case the Crud Manager schedules asynchronous loading on construction stage:

const crudManager = new CrudManager({
    autoLoad : true,
    loadUrl  : 'php/read.php',
    syncUrl  : 'php/save.php'
});

And in order to start data loading manually the Crud Manager has a load method. The method returns a Promise that gets resolved once data is loaded and processed by the Scheduling Engine:

// load data
try {
    await crudManager.load();
    console.log('Data loaded and processed...');
} catch (e) {
    console.log('Data loading error');
}

To persist changes automatically, there is the autoSync option. When set to true it causes Crud Manager to react on data changes made in the registered stores and schedule data syncing. For example, in the following snippet the Crud Manager will trigger data saving (handled by php/save.php script) as soon as any registered store record gets changed:

const crudManager = new CrudManager({
    autoSync : true,
    loadUrl  : 'php/read.php',
    syncUrl  : 'php/save.php'
});

And of course manual saving is also possible with the sync method:

try{
    await crudManager.sync();
    console.log('Changes saved...');
} catch(e) {
    console.log('Data saving error');
}

Response format validation

Crud Manager will validate responses and log found issues to the browser console. This should help implementing backend integration on development stage.

Example of the validation report:

Project sync response error(s):
- "events" store "rows" section should mention added record(s) #XXX sent in the request. It should contain the added records identifiers (both phantom and "real" ones assigned by the backend).
- "events" store "rows" section should mention updated record(s) #XXX sent in the request. It should contain the updated record identifiers.
- "events" store "removed" section should mention removed record(s) #XXX sent in the request. It should contain the removed record identifiers.
Please adjust your response to look like this:
{
    "events": {
        "removed": [
            {
                "id": XXX
            },
            ...
        ],
        "rows": [
            {
                "$PhantomId": XXX,
                "id": ...
            },
            {
                "id": XXX
            },
            ...
        ]
    }
}
Note: Please consider enabling "supportShortSyncResponse" option to allow less detailed sync responses (https://bryntum.com/products/scheduler/docs/api/Scheduler/crud/AbstractCrudManagerMixin#config-supportShortSyncResponse)
Note: To disable this validation please set the "validateResponse" config to false

The validation can be disabled with validateResponse config, which is recommended in a production environment.