Перейти к основному содержимому

Интеграция с серверной стороной

Лучший способ подключить dhtmlxGantt к серверу - настроить RESTful API на серверной стороне и использовать модуль dataprocessor на стороне клиента.

DataProcessor - это встроенная функция, которая отслеживает изменения данных в Gantt и отправляет обновления в REST API в необходимом формате. Это делает интеграцию с серверными платформами простой и удобной. При работе с объектным источником данных DataProcessor может быть настроен для предоставления callback-функций при изменении данных, что удобно для data binding.

Также есть видеоурок, демонстрирующий, как создать диаграмму Gantt и загрузить в неё данные на примере Node.js.

Техника


В целом, чтобы загрузить данные с сервера с помощью REST API, необходимо:

Клиентская сторона

  1. Используйте метод load для загрузки данных Gantt, указав URL, который возвращает данные в формате JSON.

  2. Создайте экземпляр DataProcessor одним из двух способов:

  • Инициализируйте DataProcessor и свяжите его с объектом dhtmlxGantt:
gantt.init("gantt_here");
gantt.load("apiUrl");

// сохраняйте порядок строк ниже
const dp = new gantt.dataProcessor("apiUrl");
dp.init(gantt);
dp.setTransactionMode("REST");
dp.deleteAfterConfirmation = true;
заметка

Рекомендуется использовать второй способ.

  • Используйте метод createDataProcessor, передав объект с параметрами конфигурации:
const dp = gantt.createDataProcessor({
url: "apiUrl",
mode: "REST",
deleteAfterConfirmation: true
});

Подробнее см. в следующем разделе.

Создание DataProcessor

При создании DataProcessor через API-метод createDataProcessor есть несколько способов передачи параметров.

  1. Используйте один из предопределённых режимов запроса, например:
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
deleteAfterConfirmation: true
});

где:

  • url - конечная точка на сервере
  • mode - способ отправки данных на сервер: "GET" | "POST" | "REST" | "JSON" | "REST-JSON"
  • deleteAfterConfirmation - определяет, должен ли таск быть удалён из Gantt только после подтверждения удаления сервером. Связи и подзадачи будут удалены после подтверждения удаления родительской задачи.
  1. Передайте собственный объект router:
const dp = gantt.createDataProcessor(router);
  • где 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, либо когда создание и удаление обрабатываются разными серверами.

Custom data api - using local storage

Детали запросов и ответов

URL-адреса имеют следующий шаблон:

  • api/link/id
  • api/task/id
  • api/resource/id
  • api/assignment/id

где "api" - это URL, указанный в конфигурации dataProcessor.

Вот список возможных запросов и ответов:

ДействиеHTTP-методURLОтвет
загрузка данныхGET/apiUrlJSON формат
Задачи
добавить новую задачу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. Чтобы включить их, необходимо явно это разрешить. Подробнее см. здесь.

Параметры запроса

Запросы на создание, обновление и удаление включают все публичные свойства клиентского объекта задачи или связи:

Задача:

  • start_date: 2025-04-08 00:00:00
  • duration: 4
  • text: Task #2.2
  • parent: 3
  • end_date: 2025-04-12 00:00:00

Связь:

  • source: 1
  • target: 2
  • type: 0

Примечание:

  • Формат start_date и end_date задаётся конфигурацией date_format.
  • Клиент отправляет все публичные свойства задачи или связи, поэтому запросы могут содержать дополнительные параметры.
  • Если вы добавляете новые колонки или свойства в свою модель данных, gantt автоматически отправит их на сервер.
заметка

Публичные свойства - это те, чьи имена не начинаются с подчёркивания (_) или знака доллара ($), поэтому свойства вроде task._owner или link.$state не будут отправлены на сервер.

Режим REST-JSON

Помимо режимов "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"
});

Есть несколько способов сохранить порядок; вот один из примеров.

  • Добавьте числовую колонку в таблицу задач, например, 'sortorder'.
  • При обработке GET-запроса сортируйте задачи по этой колонке по возрастанию.
  • При добавлении новой задачи присваивайте ей MAX(sortorder) + 1.
  • При изменении порядка на клиенте gantt отправляет PUT (или POST, если не используется REST-режим) со всеми свойствами задачи плюс значениями, описывающими положение задачи в структуре проекта.
HTTP-методURLПараметрыОтвет
PUT/apiUrl/task/taskIdtarget=adjacentTaskId("action":"updated")

Параметр target содержит id ближайшей задачи - непосредственно перед или после текущей.

Его значение может быть в двух форматах:

  • target="targetId" - поместить текущую задачу прямо перед задачей с targetId
  • target="next:targetId" - поместить текущую задачу прямо после задачи с targetId

Применение изменений порядка обычно требует обновления нескольких задач. Вот пример псевдокода:

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);
}
});

Custom data api - using local storage

Использование AJAX для настройки пользовательских роутеров

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)
});
}
});

Маршрутизация CRUD-операций над ресурсами и назначениями ресурсов

Начиная с версии 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 должен будет самостоятельно удалить связанные связи и подзадачи.

XSS, CSRF и SQL-инъекции


Важно отметить, что Gantt не предоставляет встроенной защиты от угроз, таких как SQL-инъекции, XSS или CSRF-атаки. Обеспечение безопасности приложения лежит на разработчиках, реализующих backend.

См. статью Безопасность приложения, чтобы узнать о наиболее уязвимых местах компонента и рекомендуемых мерах по повышению безопасности вашего приложения.