Лучший способ подключить dhtmlxGantt к серверу — настроить RESTful API на серверной стороне и использовать модуль dataprocessor на стороне клиента.
DataProcessor — это встроенная функция, которая отслеживает изменения данных в Gantt и отправляет обновления в REST API в необходимом формате. Это делает интеграцию с серверными платформами простой и удобной. При работе с объектным источником данных DataProcessor может быть настроен для предоставления callback-функций при изменении данных, что удобно для data binding.
Также есть видеоурок, демонстрирующий, как создать диаграмму Gantt и загрузить в неё данные на примере Node.js.
В целом, чтобы загрузить данные с сервера с помощью REST API, необходимо:
1) Используйте метод load для загрузки данных Gantt, указав URL, который возвращает данные в формате JSON.
2) Создайте экземпляр DataProcessor одним из двух способов:
gantt.init("gantt_here");
gantt.load("apiUrl");
// сохраняйте порядок строк ниже
const dp = new gantt.dataProcessor("apiUrl");
dp.init(gantt);
dp.setTransactionMode("REST");
dp.deleteAfterConfirmation = true;
Рекомендуется использовать второй способ.
const dp = gantt.createDataProcessor({
url: "apiUrl",
mode: "REST",
deleteAfterConfirmation: true
});
Подробнее см. в следующем разделе.
При создании DataProcessor через API-метод createDataProcessor есть несколько способов передачи параметров.
1. Используйте один из предопределённых режимов запроса, например:
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
deleteAfterConfirmation: true
});
где:
2. Передайте собственный объект router:
const dp = gantt.createDataProcessor(router);
// entity - "task"|"link"|"resource"|"assignment"
// action - "create"|"update"|"delete"
// data - объект с данными задачи или связи
// id – id обрабатываемого объекта (задача или связь)
const dp = gantt.createDataProcessor((entity, action, data, id) => {
switch(action) {
case "create":
return gantt.ajax.post(
server + "/" + entity,
data
);
break;
case "update":
return gantt.ajax.put(
server + "/" + entity + "/" + id,
data
);
break;
case "delete":
return gantt.ajax.del(
server + "/" + entity + "/" + id
);
break;
}
});
const dp = gantt.createDataProcessor({
task: {
create: (data) => {},
update: (data, id) => {},
delete: (id) => {}
},
link: {
create: (data) => {},
update: (data, id) => {},
delete: (id) => {}
}
});
Все функции в объекте router должны возвращать либо Promise, либо объект с данными ответа. Это позволяет dataProcessor применить id из базы данных и вызвать событие onAfterUpdate.
const router = (entity, action, data, id) => {
return new gantt.Promise((resolve, reject) => {
// … некоторая логика
return resolve({ tid: databaseId });
});
};
Такая гибкость позволяет использовать DataProcessor для сохранения данных в localStorage или любое другое хранилище, не привязанное к конкретному URL, либо когда создание и удаление обрабатываются разными серверами.
Related sample: Custom data api - using local storage
URL-адреса имеют следующий шаблон:
где "api" — это URL, указанный в конфигурации dataProcessor.
Вот список возможных запросов и ответов:
Действие | HTTP-метод | URL | Ответ |
---|---|---|---|
загрузка данных | GET | /apiUrl | JSON формат |
Задачи | |||
добавить новую задачу | POST | /apiUrl/task | {"action":"inserted","tid":"id"} |
обновить задачу | PUT | /apiUrl/task/id | {"action":"updated"} |
удалить задачу | DELETE | /apiUrl/task/id | {"action":"deleted"} |
Связи | |||
добавить новую связь | POST | /apiUrl/link | {"action":"inserted","tid":"id"} |
обновить связь | PUT | /apiUrl/link/id | {"action":"updated"} |
удалить связь | DELETE | /apiUrl/link/id | {"action":"deleted"} |
Ресурсы | |||
добавить новый ресурс | POST | /apiUrl/resource | {"action":"inserted","tid":"id"} |
обновить ресурс | PUT | /apiUrl/resource/id | {"action":"updated"} |
удалить ресурс | DELETE | /apiUrl/resource/id | {"action":"deleted"} |
Назначения ресурсов | |||
добавить новое назначение | POST | /apiUrl/assignment | {"action":"inserted","tid":"id"} |
обновить назначение | PUT | /apiUrl/assignment/id | {"action":"updated"} |
удалить назначение | DELETE | /apiUrl/assignment/id | {"action":"deleted"} |
По умолчанию ресурсы и назначения ресурсов не включаются в запросы DataProcessor. Чтобы включить их, необходимо явно это разрешить. Подробнее см. здесь.
Запросы на создание, обновление и удаление включают все публичные свойства клиентского объекта задачи или связи:
Задача:
Связь:
Примечание:
Публичные свойства — это те, чьи имена не начинаются с подчёркивания (_) или знака доллара ($), поэтому свойства вроде task._owner или link.$state не будут отправлены на сервер.
Помимо режимов "POST", "GET", "REST" и "JSON", Gantt DataProcessor также поддерживает режим "REST-JSON".
gantt.load("apiUrl");
const dp = gantt.createDataProcessor({
url: "/apiUrl",
mode: "REST-JSON"
});
Используются те же URL для запросов, но способ передачи параметров отличается.
В режиме REST данные отправляются как form data:
Content-Type: application/x-www-form-urlencoded
В режиме REST-JSON данные отправляются в формате JSON:
Headers
Content-type: application/json
Параметры передаются как JSON-объект:
Request Payload
{
"start_date": "20-09-2025 00:00",
"text": "New task",
"duration": 1,
"end_date": "21-09-2025 00:00",
"parent": 0,
"usage": [
{ "id": "1", "value": "30" },
{ "id": "2", "value": "20" }
]
}
{
"source": 1,
"target": 2,
"type": "0"
}
Этот формат упрощает обработку сложных записей на сервере.
При каждом изменении в Gantt (добавление, обновление или удаление задач или связей) dataProcessor отправляет AJAX-запрос на сервер.
Каждый запрос содержит все необходимые данные для обновления базы данных. Поскольку dataProcessor работает в режиме REST, используются различные HTTP-методы в зависимости от операции.
REST API позволяет реализовать серверную часть с помощью различных фреймворков и языков программирования. Вот некоторые готовые серверные реализации для интеграции с Gantt:
Gantt отображает задачи в том порядке, в котором они приходят из источника данных. Если пользователи могут перемещать задачи вручную, вам нужно сохранять этот порядок в базе данных и возвращать задачи отсортированными в вашем фиде данных.
Клиентская настройка:
// перемещение задач по всему gantt
gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
gantt.init("gantt_here");
gantt.load("/api");
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST"
});
Есть несколько способов сохранить порядок; вот один из примеров.
MAX(sortorder) + 1
.HTTP-метод | URL | Параметры | Ответ |
---|---|---|---|
PUT | /apiUrl/task/taskId | target=adjacentTaskId | {"action":"updated"} |
Параметр target содержит id ближайшей задачи — непосредственно перед или после текущей.
Его значение может быть в двух форматах:
Применение изменений порядка обычно требует обновления нескольких задач. Вот пример псевдокода:
const target = request["target"];
const currentTaskId = request["id"];
let nextTask;
let targetTaskId;
// Определяем, помещается ли задача до или после соседней
if (target.startsWith("next:")) {
targetTaskId = target.substr("next:".length);
nextTask = true;
} else {
targetTaskId = target;
nextTask = false;
}
const currentTask = tasks.getById(currentTaskId);
const targetTask = tasks.getById(targetTaskId);
if (!targetTaskId) return;
// Присваиваем sortorder соседней задачи обновляемой задаче
let targetOrder = targetTask.sortorder;
// Если помещаем после соседней задачи — увеличиваем sortorder
if (nextTask) targetOrder++;
// Увеличиваем sortorder у задач, следующих за обновляемой
tasks.where(task => task.sortorder >= targetOrder)
.update(task => task.sortorder++);
// Обновляем задачу с новым sortorder
currentTask.sortorder = targetOrder;
tasks.save(currentTask);
Подробные примеры сохранения порядка задач для конкретных серверных платформ: plain PHP, Laravel, Node.js, ASP.NET Web API и Rails.
Можно добавить дополнительные заголовки в запросы, отправляемые на ваш backend. Например, вы можете добавить токен авторизации в ваши запросы:
gantt.init("gantt_here");
gantt.load("/api");
const dp = gantt.createDataProcessor({
url: "/api",
mode:"REST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
});
В настоящее время load не поддерживает передачу заголовков или параметров payload для GET-запросов, поэтому если вам нужно их добавить, потребуется отправить xhr вручную и затем загрузить данные в Gantt с помощью parse, например так:
gantt.ajax.get({
url: "/api",
headers: {
"Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
}).then(xhr => {
gantt.parse(xhr.responseText);
});
Существует несколько способов добавить дополнительные параметры в ваши запросы.
Поскольку Gantt отправляет все свойства объекта данных обратно на backend, вы можете просто добавить новое свойство непосредственно в объект данных, и оно будет включено в запрос:
gantt.attachEvent("onTaskCreated", (task) => {
task.userId = currentUser;
return true;
});
Другой вариант — добавить пользовательские параметры ко всем запросам, отправляемым data processor, используя свойство payload:
gantt.init("gantt_here");
gantt.load("/api");
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
payload: {
token: "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
});
Также можно добавить пользовательские параметры к запросам с помощью события onBeforeUpdate объекта DataProcessor:
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
});
dp.attachEvent("onBeforeUpdate", (id, state, data) => {
data.projectId = "1";
return true;
});
Когда dataProcessor инициализирован, любые изменения, внесённые пользователями или программно, автоматически сохраняются в источник данных.
Для программного обновления определённой задачи или зависимости обычно используются методы updateTask и updateLink:
gantt.parse([
{ id: 1, text: "Task 1", start_date: "2025-05-13 06:00", duration: 2 },
{ id: 2, text: "Task 2", start_date: "2025-05-19 08:00", duration: 3 }
]);
const task = gantt.getTask(1);
task.text = "Task 37"; // обновить данные задачи
gantt.updateTask(1); // перерисовать обновлённую задачу
Другие методы, которые инициируют отправку обновлений на backend:
Если RESTful AJAX API не соответствует вашим требованиям backend или вы хотите полностью контролировать содержимое отправляемого на сервер, можно использовать пользовательскую маршрутизацию.
Например, в таких фреймворках, как Angular или React, компонент может не отправлять изменения напрямую на сервер, а передавать их другому компоненту, отвечающему за сохранение данных.
Для настройки пользовательской маршрутизации DataProcessor используйте метод createDataProcessor():
gantt.createDataProcessor(function(entity, action, data, id) {
const services = {
"task": this.taskService,
"link": this.linkService
};
const service = services[entity];
switch (action) {
case "update":
return service.update(data);
case "create":
return service.insert(data);
case "delete":
return service.remove(id);
}
});
Related sample: Custom data api - using local storage
AJAX-модуль Gantt может быть полезен при настройке пользовательских маршрутов. Gantt ожидает, что пользовательский роутер будет возвращать объект Promise из операции, что позволяет определить, когда действие завершено.
AJAX-модуль поддерживает promises, что делает его подходящим для использования внутри пользовательских роутеров. Gantt обработает Promise и выполнит необходимые действия после его завершения.
В примере ниже создается новая задача. Если в ответе сервера содержится id только что созданной задачи, Gantt применит его соответствующим образом.
gantt.createDataProcessor((entity, action, data, id) => {
...
switch (action) {
case "create":
return gantt.ajax.post({
headers: {
"Content-Type": "application/json"
},
url: `${server}/task`,
data: JSON.stringify(data)
});
}
});
Начиная с версии 8.0, изменения назначений ресурсов могут отправляться в DataProcessor как отдельные записи с постоянными ID, что упрощает интеграцию с backend API. Изменения самих объектов ресурсов также могут отправляться в DataProcessor.
Обратите внимание, что эта функция отключена по умолчанию. По умолчанию DataProcessor принимает изменения только задач и связей. Чтобы включить обработку ресурсов, установите следующие параметры:
gantt.config.resources = {
dataprocessor_assignments: true,
dataprocessor_resources: true,
};
Когда режим ресурсов включён и DataProcessor работает в режиме REST, ресурсы и назначения ресурсов отправляются на backend отдельными запросами.
Если DataProcessor использует режим пользовательской маршрутизации, вы можете обрабатывать изменения назначений ресурсов и ресурсов в вашем обработчике:
gantt.createDataProcessor({
task: {
create: (data) => {
return createRecord({type: "task", ...data}).then((res) => {
return { tid: res.id, ...res };
});
},
update: (data, id) => {
return updateRecord({type: "task", ...data}).then(() => ({}));
},
delete: (id) => {
return deleteRecord({type: "task:", id: id}).then(() => ({}));
}
},
link: {
create: (data) => {
...
},
update: (data, id) => {
...
},
delete: (id) => {
...
}
},
assignment: {
create: (data) => {
...
},
update: (data, id) => {
...
},
delete: (id) => {
...
}
},
resource: {
create: (data) => {
...
},
update: (data, id) => {
...
},
delete: (id) => {
...
}
}
});
Альтернативно, с использованием объявления функции:
gantt.createDataProcessor((entity, action, data, id) => {
switch (entity) {
case "task":
break;
case "link":
break;
case "resource":
break;
case "assignment":
break;
}
});
Если сервер сообщает о неудачном выполнении действия, он может вернуть ответ с "action":"error"
:
{"action":"error"}
Вы можете отловить такие ответы на клиенте с помощью gantt.dataProcessor:
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST"
});
dp.attachEvent("onAfterUpdate", (id, action, tid, response) => {
if (action === "error") {
// обработка ошибки здесь
}
});
Объект ответа может содержать дополнительные свойства, доступные через аргумент response
в обработчике onAfterUpdate.
Это событие срабатывает только для управляемых ошибок, возвращающих JSON-ответ, как показано выше. Для обработки HTTP-ошибок используйте событие onAjaxError.
Если сервер вернул ошибку, но изменения на клиенте были сохранены, лучший способ синхронизировать состояния — очистить состояние клиента и загрузить корректные данные с сервера:
dp.attachEvent("onAfterUpdate", (id, action, tid, response) => {
if (action === "error") {
gantt.clearAll();
gantt.load("url1");
}
});
Если вы хотите синхронизировать состояния клиента и сервера без обращения к серверу, используйте метод silent(), чтобы предотвратить внутренние события или обращения к серверу во время операции:
gantt.silent(() => {
gantt.deleteTask(item.id);
});
gantt.render();
По умолчанию при удалении задачи происходит каскадное удаление её вложенных задач и связанных связей. Gantt отправляет delete-запрос для каждой удаляемой задачи и связи.
Это означает, что целостность данных на backend не нужно поддерживать вручную, так как Gantt делает это автоматически.
Однако такой подход может привести к большому количеству AJAX-запросов на backend, поскольку dhtmlxGantt не поддерживает пакетные запросы, а количество задач и связей может быть большим.
При необходимости каскадное удаление можно отключить с помощью настройки cascade_delete.
В этом случае при удалении ветки проекта будет отправлен delete-запрос только для верхнего элемента, и backend должен будет самостоятельно удалить связанные связи и подзадачи.
Важно отметить, что Gantt не предоставляет встроенной защиты от угроз, таких как SQL-инъекции, XSS или CSRF-атаки.
Обеспечение безопасности приложения лежит на разработчиках, реализующих backend.
См. статью Безопасность приложения, чтобы узнать о наиболее уязвимых местах компонента и рекомендуемых мерах по повышению безопасности вашего приложения.
К началу