Лучший способ связать dhtmlxGantt с серверной частью — это настроить RESTful API на сервере и использовать dhtmlxDataProcessor на клиентской стороне. Библиотека DataProcessor, которая поставляется вместе с dhtmlxGantt.js, отслеживает изменения данных и обрабатывает серверные запросы на клиенте.
Gantt работает со своей собственной версией DataProcessor, которая имеет некоторые уникальные особенности по сравнению со стандартной библиотекой. В этом руководстве объясняется, как использовать Gantt DataProcessor для интеграции с серверной платформой.
Для практического примера посмотрите видеоурок, который демонстрирует, как настроить диаграмму Ганта на странице и загрузить в нее данные, используя платформу Node.js в качестве примера.
Чтобы загрузить данные с серверной части с использованием REST API, обычно требуются следующие шаги:
1) Используйте метод load
для загрузки данных Gantt. Укажите URL, который возвращает данные в формате JSON, в качестве параметра.
2) Чтобы настроить экземпляр DataProcessor, вы можете сделать это одним из двух способов:
gantt.init("gantt_here");
gantt.load("apiUrl");
// Убедитесь, что следующие строки идут в правильном порядке
var dp = new gantt.dataProcessor("apiUrl");
dp.init(gantt);
dp.setTransactionMode("REST");
dp.deleteAfterConfirmation = true;
createDataProcessor
и передача объекта с параметрами конфигурации:var dp = gantt.createDataProcessor({
url: "apiUrl",
mode: "REST",
deleteAfterConfirmation: true
});
Для получения более подробной информации, ознакомьтесь со следующим разделом.
При использовании метода API createDataProcessor
для создания DataProcessor у вас есть несколько вариантов настройки:
var dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
deleteAfterConfirmation: true
});
Вот что означают свойства:
var dp = gantt.createDataProcessor(router);
Router может быть функцией:
// entity - "task"|"link"|"resource"|"assignment"
// action - "create"|"update"|"delete"
// data - объект данных задачи или ссылки
// id – ID обрабатываемого объекта (задачи или ссылки)
var dp = gantt.createDataProcessor(function(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;
}
});
Или объектом со следующей структурой:
var dp = gantt.createDataProcessor({
task: {
create: function(data) {},
update: function(data, id) {},
delete: function(id) {}
},
link: {
create: function(data) {},
update: function(data, id) {},
delete: function(id) {}
}
});
Все функции объекта router должны возвращать Promise или объект с данными ответа. Это позволяет DataProcessor применить ID из базы данных и вызвать событие onAfterUpdate.
router = function(entity, action, data, id) {
return new gantt.Promise(function(resolve, reject) {
// Выполните некоторую логику
return resolve({tid: databaseId});
});
}
Этот подход позволяет сохранять данные в localStorage, других пользовательских решениях для хранения или даже когда отдельные серверы обрабатывают создание и удаление объектов.
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. Чтобы включить это, необходимо настроить это явно. Подробнее здесь.
Запросы на создание/обновление/удаление включают все публичные свойства объекта задачи или ссылки с клиентской стороны:
Задача:
Ссылка:
Примечание:
date_format
._
) или знака доллара ($
). Например, свойства типа task._owner или link.$state не отправляются на сервер.Gantt DataProcessor поддерживает режим "REST-JSON" в дополнение к "POST", "GET", "REST" и "JSON" режимам транзакций.
gantt.load("apiUrl");
var dp = gantt.createDataProcessor({
url: "/apiUrl",
mode: "REST-JSON"
});
Этот режим использует те же URL для запросов, но параметры запроса для задач и ссылок, а также способ их отправки, отличаются.
В режиме REST данные отправляются в виде формы:
Content-Type: application/x-www-form-urlencoded
В режиме REST-JSON данные отправляются в формате JSON:
Headers
Content-type: application/json
Параметры отправляются в виде JSON-объекта:
Тело запроса
{
"start_date": "20-09-2018 00:00",
"text": "Новая задача",
"duration":1,
"end_date": "21-09-2018 00:00",
"parent": 0,
"usage":[{
{"id":"1", "value":"30"},
{"id":"2", "value":"20"}
}]
}
{
"source": 1,
"target": 2,
"type": "0"
}
Этот формат упрощает обработку сложных записей на различных серверных платформах.
Когда задачи или ссылки добавляются, обновляются или удаляются в Gantt, DataProcessor отправляет AJAX-запросы на сервер. Эти запросы содержат все необходимые данные для обновления базы данных.
В режиме REST DataProcessor использует разные HTTP-глаголы для каждой операции. Поскольку REST API широко поддерживаются, вы можете реализовать серверную часть с использованием различных фреймворков и языков программирования. Доступные серверные реализации для Gantt включают:
Задачи в Gantt отображаются в том же порядке, в котором они приходят из источника данных. Если пользователи могут изменять порядок задач вручную, вам нужно будет сохранить этот порядок в базе данных и убедиться, что поток данных возвращает задачи в правильном порядке.
Конфигурация на стороне клиента:
// Разрешить изменение порядка задач внутри структуры Gantt
gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
gantt.init("gantt_here");
gantt.load("/api");
var dp = gantt.createDataProcessor({
url: "/api",
mode: "REST"
});
Один из способов сохранить порядок — добавить числовую колонку, например, 'sortorder', в таблицу задач:
MAX(sortorder) + 1
новым задачам.HTTP-метод | URL | Параметры | Ответ |
---|---|---|---|
PUT | /apiUrl/task/taskId |
|
{"action":"updated"} |
Параметр target указывает ID ближайшей задачи, либо перед, либо после текущей задачи.
Форматы для target:
Изменение порядка задач часто включает обновление нескольких задач. Вот пример псевдокода:
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;
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.
Если необходимо включить дополнительные заголовки в запросы, отправляемые из Gantt на ваш сервер, вы можете использовать метод dataProcessor.setTransactionMode.
Вот быстрый пример добавления токена авторизации в ваши запросы:
gantt.init("gantt_here");
gantt.load("/api");
var dp = gantt.createDataProcessor({
url: "/api",
mode:"REST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
});
На данный момент метод load
не позволяет добавлять заголовки или параметры тела в GET-запросы. Если вам нужна эта функциональность, вы можете вручную обработать XHR и затем загрузить данные в Gantt, используя метод parse
. Вот пример:
gantt.ajax.get({
url: "/api",
headers: {
"Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
}).then(function (xhr) {
gantt.parse(xhr.responseText)
});
Существует несколько способов включения дополнительных параметров в ваши запросы.
Один из простых способов — добавить новое свойство прямо в объект данных. Gantt автоматически отправит это свойство на сервер:
gantt.attachEvent("onTaskCreated", function(task){
task.userId = currentUser;
return true;
});
Вы также можете включить пользовательские параметры во все запросы, сделанные data processor, используя свойство payload в методе setTransactionMode:
gantt.init("gantt_here");
gantt.load("/api");
var dp = gantt.createDataProcessor({
url: "/api",
mode:"REST",
payload: {
token: "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
});
Еще один способ добавить пользовательские параметры — использовать событие onBeforeUpdate DataProcessor:
var dp = gantt.createDataProcessor({
url: "/api",
mode:"REST"
});
dp.attachEvent("onBeforeUpdate", function(id, state, data){
data.projectId = "1";
return true;
});
Чтобы динамически изменить URL на стороне сервера, вы можете использовать метод dataProcessor.url:
dp.url("/url");
Когда data processor инициализирован, любые изменения, сделанные пользователями или программно, автоматически синхронизируются с источником данных.
Для программного обновления конкретной задачи или зависимости вы можете использовать методы updateTask
или updateLink
:
gantt.parse([
{id:1, start_date:"2019-05-13 6:00", end_date:"2019-05-13 8:00", text:"Событие 1"},
{id:2, start_date:"2019-06-09 6:00", end_date:"2019-06-09 8:00", text:"Событие 2"}
],"json");
gantt.getTask(1).text = "Задача 111"; // Обновить данные задачи
gantt.updateTask(1); // Отобразить обновленную задачу
Другие методы, которые запускают обновления на сервере, включают:
Если RESTful AJAX API не соответствует вашим потребностям на сервере или если вам нужно больше контроля над данными, отправляемыми на сервер, можно реализовать пользовательскую маршрутизацию.
Это полезно в настройках, таких как 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
Модуль Gantt AJAX может помочь настроить пользовательские маршруты. Он позволяет Gantt обрабатывать объекты Promise для операций, обеспечивая обработку действий при разрешении Promise.
Вот пример, где создается новая задача. Если ответ сервера включает ID новой задачи, Gantt применит его:
gantt.createDataProcessor(function(entity, action, data, id){
...
switch (action) {
case "create":
return gantt.ajax.post({
headers: {
"Content-Type": "application/json"
},
url: server + "/task",
data: JSON.stringify(data)
});
break;
}
});
Начиная с версии 8.0, назначения ресурсов могут быть отправлены в dataProcessor как отдельные записи с постоянными ID, что упрощает интеграцию с серверными API. Изменения ресурсов также могут быть отправлены таким образом.
Эта функция выключена по умолчанию. По умолчанию только изменения задач и ссылок отправляются в dataProcessor. Чтобы включить это, настройте следующее:
gantt.config.resources = {
dataprocessor_assignments: true,
dataprocessor_resources: true,
};
С включенной этой функцией, ресурсы и назначения будут отправляться как отдельные запросы при использовании режима REST. В режиме Пользовательской маршрутизации вы можете обрабатывать эти изменения в процессоре:
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(function(entity, action, data, id){
switch (entity) {
case "task":
break;
case "link":
break;
case "resource":
break;
case "assignment":
break;
}
});
Если сервер возвращает ошибку для какого-либо действия, он может уведомить Gantt, используя ответ следующего вида:
{"action":"error"}
Вы можете обработать такие ошибки на клиентской стороне с помощью gantt.dataProcessor
:
var dp = gantt.createDataProcessor({
url: "/api",
mode:"REST"
});
dp.attachEvent("onAfterUpdate", function(id, action, tid, response){
if(action == "error"){
// Обработка ошибки
}
});
Объект response
может включать дополнительные свойства, которые доступны в обработчике onAfterUpdate
.
Это событие обрабатывает только управляемые ошибки, которые возвращают JSON-ответ, как показано выше.
Для HTTP-ошибок обратитесь к событию API onAjaxError
.
Если серверная ошибка вызывает несоответствие с состоянием на клиентской стороне, вы можете очистить состояние клиента и загрузить свежие данные с сервера:
dp.attachEvent("onAfterUpdate", function(id, action, tid, response){
if(action == "error"){
gantt.clearAll();
gantt.load("url1");
}
});
Чтобы синхронизировать состояние клиент-сервер без дополнительных вызовов на сервер, вы можете использовать метод silent(). Это предотвращает внутренние события или вызовы на сервер:
gantt.silent(function(){
gantt.deleteTask(item.id);
});
gantt.render();
Когда задача удаляется, ее вложенные задачи и связанные ссылки также удаляются по умолчанию. Gantt отправляет отдельный запрос на удаление для каждого удаленного элемента. Это помогает поддерживать целостность данных без участия сервера.
Однако, если это генерирует слишком много AJAX-вызовов, вы можете отключить каскадное удаление с помощью конфигурации cascade_delete
. В этом случае удаляется только верхний элемент, и предполагается, что сервер обрабатывает связанные удаления.
Gantt не включает встроенные средства защиты от угроз, таких как SQL-инъекции, XSS или CSRF-атаки. Разработчикам необходимо обеспечить безопасность своих приложений.
Для получения более подробной информации о потенциальных уязвимостях и способах повышения безопасности обратитесь к статье Безопасность приложения
.