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

Лучший способ связать dhtmlxGantt с серверной частью — это настроить RESTful API на сервере и использовать dhtmlxDataProcessor на клиентской стороне. Библиотека DataProcessor, которая поставляется вместе с dhtmlxGantt.js, отслеживает изменения данных и обрабатывает серверные запросы на клиенте.

Gantt работает со своей собственной версией DataProcessor, которая имеет некоторые уникальные особенности по сравнению со стандартной библиотекой. В этом руководстве объясняется, как использовать Gantt DataProcessor для интеграции с серверной платформой.

Для практического примера посмотрите видеоурок, который демонстрирует, как настроить диаграмму Ганта на странице и загрузить в нее данные, используя платформу Node.js в качестве примера.

Техника

Чтобы загрузить данные с серверной части с использованием REST API, обычно требуются следующие шаги:

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

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

2) Чтобы настроить экземпляр DataProcessor, вы можете сделать это одним из двух способов:

  • Инициализация DataProcessor и связывание его с объектом dhtmlxGantt:
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
});

Для получения более подробной информации, ознакомьтесь со следующим разделом.

Создание DataProcessor

При использовании метода API createDataProcessor для создания DataProcessor у вас есть несколько вариантов настройки:

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

Вот что означают свойства:

  • url: URL на стороне сервера.
  • mode: Режим отправки данных на сервер. Варианты включают "JSON", "REST-JSON", "POST", "GET".
  • deleteAfterConfirmation: Обеспечивает удаление задач из Gantt только после подтверждения удаления сервером. Зависимые ссылки и подзадачи будут удалены только после подтверждения удаления родительской задачи.
  1. Используйте пользовательский объект router:
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/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:2017-04-08 00:00:00
  • duration:4
  • text:Задача #2.2
  • parent:3
  • end_date:2017-04-12 00:00:00

Ссылка:

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

Примечание:

  • Форматы start_date и end_date зависят от конфигурации date_format.
  • Клиентская сторона отправляет все публичные свойства объектов задачи или ссылки. Дополнительные параметры также могут быть включены, если модель данных расширена.
  • Публичные свойства — это те, которые не начинаются с подчеркивания (_) или знака доллара ($). Например, свойства типа task._owner или link.$state не отправляются на сервер.

Режим REST-JSON

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', в таблицу задач:

  • При выполнении действия GET сортируйте задачи по этой колонке в порядке возрастания.
  • Назначайте MAX(sortorder) + 1 новым задачам.
  • Когда порядок изменяется на клиентской стороне, Gantt отправляет PUT (или POST, если не в режиме REST) запрос с свойствами задачи и значениями, связанными с позицией.
HTTP-методURLПараметрыОтвет
PUT /apiUrl/task/taskId
  • target= adjacentTaskId
{"action":"updated"}

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

Форматы для target:

  • 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;
 
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

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

Модуль 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;
  }
});

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

Начиная с версии 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. В этом случае удаляется только верхний элемент, и предполагается, что сервер обрабатывает связанные удаления.

Атаки XSS, CSRF и SQL Injection

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

Для получения более подробной информации о потенциальных уязвимостях и способах повышения безопасности обратитесь к статье Безопасность приложения.

К началу