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

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

2. Передайте собственный объект 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, либо когда создание и удаление обрабатываются разными серверами.

Related sample:  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 /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. Чтобы включить их, необходимо явно это разрешить. Подробнее см. здесь.

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

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

Задача:

  • 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/taskId target=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);
    }
});

Related sample:  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.

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

К началу