The recommended approach to connect dhtmlxScheduler to a backend is to implement RESTful API on the server and use dhtmlxDataProcessor on the client.
DataProcessor is a client-side library included into dhtmlxScheduler.js. It monitors data changes and gets the server requests on the client side.
It's possible to bind dhtmlxScheduler to the server side using REST API together with different frameworks and programming languages. Here's a list of available server side implementations that you can use for Scheduler backend integration:
Generally, to load data from the server side using REST API, you need to:
1) Call the load method, where as a parameter specify the URL that returns Scheduler data in the JSON format
2) Call the createDataProcessor method and pass an object with configuration options as its parameter:
const dp = scheduler.createDataProcessor({
url: "apiUrl",
mode: "REST"
});
Alternatively, create a dataProcessor using the constructor and attach it to the dhtmlxScheduler object. The scheduler.DataProcessor() constructor accepts the path to the same server-side script:
scheduler.init("scheduler_here", new Date(), "month");
scheduler.load("apiUrl");
const dp = new scheduler.DataProcessor("apiUrl");
dp.init(scheduler);
Check the detailed info in the next section.
While creating a DataProcessor via the API method createDataProcessor you have several possible options for passing parameters.
1. Use one of the predefined request modes, as in:
const dp = scheduler.createDataProcessor({
url: "/api",
mode: "REST"
});
where:
2. Provide a custom router object:
const dp = scheduler.createDataProcessor(router);
where the router is either a function:
const server = "/api";
// entity - "event"
// action - "create"|"update"|"delete"
// data - an object with event data
// id – the id of a processed object (event)
const dp = scheduler.createDataProcessor(function(entity, action, data, id) {
switch(action) {
case "create":
return scheduler.ajax.post(
`${server}/${entity}`,
data
);
break;
case "update":
return scheduler.ajax.put(
`${server}/${entity}/${id}`,
data
);
break;
case "delete":
return scheduler.ajax.del(
`${server}/${entity}/${id}`
);
break;
}
});
or an object of the following structure:
const dp = scheduler.createDataProcessor({
event: {
create: function(data) {},
update: function(data, id) {},
delete: function(id) {}
}
});
All the functions of the router object should return either a Promise or a data response object. This is needed for the dataProcessor to apply the database id and to hook the onAfterUpdate event of the data processor.
const router = function(entity, action, data, id) {
return new Promise(function(resolve, reject) {
// … some logic
return resolve({tid: databaseId});
});
}
Thus you can use DataProcessor for saving data in localStorage, or any other storage which is not linked to a certain URL, or in case if there are two different servers (URLs) responsible for creation and deletion of objects.
The URL is formed by the following rule:
where "api" is the url you've specified in the dataProcessor configuration.
To enable the REST mode, set the mode
property of the createDataProcessor configuration object to the "REST" value:
const dp = scheduler.createDataProcessor({
url: "apiUrl",
mode: "REST"
});
The list of possible requests and responses is:
Action | HTTP Method | URL | Response |
---|---|---|---|
load data | GET | /apiUrl | JSON format |
add a new event | POST | /apiUrl | {"action":"inserted","tid":"eventId"} |
update an event | PUT | /apiUrl/:id | {"action":"updated"} |
delete an event | DELETE | /apiUrl/:id | {"action":"deleted"} |
To use the REST-JSON mode, set the mode
property of the createDataProcessor configuration object to the "REST-JSON" value:
const dp = scheduler.createDataProcessor({
url: "apiUrl",
mode: "REST-JSON"
});
In this mode, the scheduler will send POST/PUT/DELETE requests using the application/json
content type.
The list of possible requests and responses is:
Action | HTTP Method | URL | Request Body | Response |
---|---|---|---|---|
load data | GET | /apiUrl | JSON format | |
add a new event | POST | /apiUrl | { "start_date":"2019-12-18 00:00", "end_date":"2019-12-18 00:05", "text":"New event", ... } |
{ "action":"inserted", "tid":"eventId" } |
update an event | PUT | /apiUrl/:id | { "start_date":"2024-12-18 00:00", "end_date":"2024-12-18 00:05", "text":"New event", ... } |
{"action":"updated"} |
delete an event | DELETE | /apiUrl/:id | {"action":"deleted"} |
To enable the POST mode, set the mode
property of the createDataProcessor configuration object to the "POST" value:
const dp = scheduler.createDataProcessor({
url: "apiUrl",
mode: "POST"
});
The list of possible requests and responses is:
Action | HTTP Method | URL | Response |
---|---|---|---|
load data | GET | /apiUrl | JSON format |
update an event | POST | /apiUrl | {"action":"inserted|updated|deleted", "tid":"eventId"} |
To enable the JSON mode, set the mode
property of the createDataProcessor configuration object to the "JSON" value:
const dp = scheduler.createDataProcessor({
url: "apiUrl",
mode: "JSON"
});
In this mode, the scheduler will send POST request to the server after each updating of data (similarly to the POST mode, except for the request format).
The list of possible requests and responses is:
Action | HTTP Method | Request Body | Response |
---|---|---|---|
load data | GET | JSON format | |
add a new event | POST | {
"id": temporaryId,
"action":"inserted", "data":{ "start_date":"2019-12-18 00:00", "end_date":"2019-12-18 00:05", "text":"New event", ... } } |
{ "action":"inserted", "tid":"eventId" } |
update an event | POST | {
"id": id,
"action":"updated",
"data":{ "start_date":"2019-12-18 00:00", "end_date":"2019-12-18 00:05", "text":"New event", ... } } |
{"action":"updated"} |
delete an event | POST | {
"id": id,
"action":"deleted",
"data":{ "start_date":"2019-12-18 00:00", "end_date":"2019-12-18 00:05", "text":"New event", ... } } |
{"action":"deleted"} |
The request and response for dynamic loading are the following:
Action | HTTP Method | URL | Response |
---|---|---|---|
load data | GET | /apiUrl?from=minDate&to=maxDate | JSON format |
Create/Update/Delete requests will contain all public properties of a client-side event object:
The !nativeeditor_status parameter is relevant for the POST mode only.
On each action performed in Scheduler (adding, updating or deleting events), dataProcessor reacts by sending an AJAX request to the server.
Each request contains all the data needed to save changes in the database. As we initialized dataProcessor in the REST mode, it will use different HTTP verbs for each type of operation.
If by some reason you don't want to use REST API, the best solution is to use dhtmlxConnector library.
Recurring events are stored in the database as records that contain both all fields of a regular event and several additional fields: rrule, duration, recurring_event_id, original_start, deleted.
Read more in the Recurring Events article.
In addition to extra fields, a specific logic needs to be added to the server-side controller:
You can have a look at the detailed example on editing and deleting recurring events in the related section of the Recurring Events article.
When you need Scheduler to send additional headers to your backend, you can specify them using the dataProcessor.setTransactionMode method.
For example, let's suppose that you need to add an authorization token to your requests:
scheduler.init("scheduler_here");
scheduler.load("/api");
const dp = scheduler.createDataProcessor("/api");
dp.init(scheduler);
dp.setTransactionMode({
mode:"REST",
headers: {
"Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
});
Currently, load does not support header/payload parameters, so if you need them for GET request, you'll have to send xhr manually and load data into scheduler using parse, for example:
const authToken = '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b';
fetch("/api", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Token ${authToken}`
}
})
.then(response => response.json())
.then(data => {
scheduler.parse(data);
})
.catch(error => {
console.error("Error:", error);
});
There are several ways to send additional parameters to requests.
As you know, scheduler sends all properties of the data object back to the backend. Thus, you can add an extra property directly to the data object and it will be sent to the backend:
scheduler.attachEvent("onEventCreated", function(id,e){
const event = scheduler.getEvent(id);
event.userId = currentUser;
return true;
});
Alternatively, you can add custom parameters to all requests sent by data processor, using the payload property of the setTransactionMode parameter:
scheduler.init("gantt_here");
scheduler.load("/api");
const dp = scheduler.createDataProcessor("/api");
dp.init(scheduler);
dp.setTransactionMode({
mode:"REST",
payload: {
token: "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
});
Payload will be added into the query string of the request.
One more way to add custom parameters to a request is to use the onBeforeUpdate event of DataProcessor:
const dp = scheduler.createDataProcessor("data/events.php");
dp.attachEvent("onBeforeUpdate", function(id, state, data){
data.productName = "Product 2";
return true;
});
The event is called for each record sent to the backend and a custom parameter will be added to each Scheduler event, prefixed by an event id, like this:
123_productName:Product 2
If you have dataProcessor initialized, any change made by the user or programmatically will be automatically saved in the data source.
Generally, to update a specific event programmatically, use the addEvent method:
scheduler.parse([
{ id:1, start_date:"2017-05-13 6:00", end_date:"2017-05-13 8:00", text:"Event 1"},
{ id:2, start_date:"2017-06-09 6:00", end_date:"2017-06-09 8:00", text:"Event 2"}
]);
const event = scheduler.getEvent(1);
event.text = "Conference"; //changes event's data
scheduler.addEvent(event); // renders the updated event
When called for an event that is already loaded into the scheduler, addEvent will trigger an update request, otherwise insert will be called.
The methods that invoke sending an update to the backend:
dhtmlxScheduler can be used without gantt.dataProcessor. In that case you'll have to monitor all changes made in the scheduler manually and then send them to your backend. Here is the list of events you'll need to listen to:
When an event is created on the client side, it obtains a temporary id which is used until the item gets a permanent database id.
Once you insert a new item into the database, you'll need to pass it back to the client side and apply it to the related event using the changeEventId method:
// assume that eventService is some kind of CRUD service implementation
scheduler.attachEvent('onEventAdded', function(id, event) {
eventService.create(event)
.then(function(result){
scheduler.changeEventId(id, result.databaseId);
});
});
scheduler.attachEvent('onEventChanged', function(id, event) {
eventService.update(event);
});
scheduler.attachEvent('onEventDeleted', function(id) {
eventService.delete(id);
});
In case RESTful AJAX API isn't what you need on the backend, or if you want to manually control what is sent to the server, you can make use of custom routing.
For example, if you use Angular, React, or any other framework where a component on a page doesn't send changes directly to the server, but passes them to a different component which is responsible for data saving.
To provide custom routing options for DataProcessor, you should use the createDataProcessor() method:
const server = "/api";
scheduler.createDataProcessor(function(entity, action, data, id) {
switch(action) {
case "create":
return scheduler.ajax.post(
`${server}/${entity}`,
data
);
break;
case "update":
return scheduler.ajax.put(
`${server}/${entity}/${id}`,
data
);
break;
case "delete":
return scheduler.ajax.del(
`${server}/${entity}/${id}`
);
break;
}
});
Scheduler AJAX module can be useful for setting custom routes. Scheduler expects a custom router to return a Promise object as a result of an operation, which allows catching the end of an action. The AJAX module supports promises and is suitable for usage inside of custom routers. Scheduler will get Promise and process the content of Promise, when it is resolved.
In the example below a new task is created. If the server response includes the id of a newly created task, Scheduler will be able to apply it.
scheduler.createDataProcessor(function(entity, action, data, id){
...
switch (action) {
case "create":
return scheduler.ajax.post({
headers: {
"Content-Type": "application/json"
},
url: server + "/" + entity + "/" + id,
data: JSON.stringify(data)
});
break;
}
});
A server can inform Scheduler that an action has failed by returning the "action":"error" response:
{"action":"error"}
Such a response can be captured on the client with the help of dataProcessor:
const dp = scheduler.createDataProcessor("apiUrl");
dp.init(scheduler);
dp.attachEvent("onAfterUpdate", function(id, action, tid, response){
if(action === "error"){
// do something here
}
});
The response object may contain any number of additional properties, they can be accessed via the response
argument of the onAfterUpdate handler.
If the server responded with an error on some of your action but the changes were saved on the client side, the best way to synchronize their states is to clear the client's state, and reload the correct data from the server side:
dp.attachEvent("onAfterUpdate", function(id, action, tid, response){
if(action === "error"){
scheduler.clearAll();
scheduler.load(url);
}
});
In cases when you don't want to fully reload the data, you can delete a single event from only the client-side using the silent parameter of the deleteEvent method:
// removes the specified event only from the client-side, without server calls
scheduler.deleteEvent(id, true);
Pay attention that Scheduler doesn't provide any means of preventing an application from various threats, such as SQL injections or XSS and CSRF attacks. It is important that responsibility for keeping an application safe is on the developers implementing the backend.
Check the Application Security article to learn the most vulnerable points of the component and the measures you can take to improve the safety of your application.
Back to top