dhtmlxGantt с Ruby on Rails

Это руководство описывает процесс настройки диаграммы Ганта с использованием backend на Ruby on Rails. В примере используется Ruby 2.4.1, Rails 5.1.3 и MySQL. Предполагается, что вы уже установили необходимые зависимости. Если нет, сначала ознакомьтесь с официальными руководствами.

Если вы работаете с другой технологией, доступны другие варианты интеграции:

Вы также можете ознакомиться с демо на GitHub.


Шаг 1: Создание проекта

Для начала создайте новый проект Rails с MySQL в качестве базы данных:

rails new gantt-app -d mysql

Шаг 2: Добавление Ганта на страницу

Начните с создания контроллера и страницы по умолчанию для приложения. Перейдите в папку проекта и создайте новый контроллер с действием index:

cd gantt-app
rails generate controller gantt index

Вы должны увидеть подтверждение о создании новых файлов.

Установка маршрута по умолчанию

Обновите конфигурацию маршрутизации в config/routes.rb, чтобы установить действие index нового контроллера в качестве маршрута по умолчанию:

Rails.application.routes.draw do
  root :to => "gantt#index"
end

Запустите сервер Rails:

rails server

Откройте http://localhost:3000/ в браузере. Вы должны увидеть пустую страницу, что указывает на работу приложения. Теперь давайте добавим диаграмму Ганта.

Добавление Ганта в представление

Чтобы включить диаграмму Ганта, измените файл макета, добавив yield внутри тега <head>. Это позволит добавить файлы dhtmlxGantt на страницу:

<!DOCTYPE html>
<html>
<head>
  <title>dhtmlxGantt</title>
  <%= stylesheet_link_tag 'application', media:'all','data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>  
  <%= yield(:head) %>
  <%= csrf_meta_tags %>
</head>
<body>
  <%= yield %>
</body>
</html>

Затем обновите представление gantt/index, чтобы включить диаграмму Ганта:

<% content_for :head do %>
  <%= stylesheet_link_tag 'https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css' %>
  <%= javascript_include_tag 'https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js' %>
<% end %>
 
<div id="gantt_here" style='width:100%; height:800px;'></div>
 
<script>
    gantt.init("gantt_here");
</script>

Файлы Ганта загружаются с CDN. Для разработки вы можете использовать исходные файлы, включенные в пакет загрузки.

Откройте http://localhost:3000/ в вашем браузере снова. Теперь вы увидите диаграмму Ганта, где вы можете добавлять и изменять задачи, но функциональность сохранения еще недоступна. Следующий шаг включает создание моделей для возможности сохранения.


Шаг 3: Создание моделей

Поскольку мы используем MySQL, убедитесь, что настройки подключения к базе данных в config/database.yml правильные:

development:
  adapter: mysql2
  encoding: utf8
  host: localhost
  database: gantt-app
  username: root
  password:

Теперь создайте модели для задач и связей.

Создайте модель для задач со следующими свойствами:

rails generate model Task \
    text:string \
    start_date:datetime \
    duration:integer \
    parent:integer \
    progress:decimal

Для связей используйте эту более короткую команду:

rails generate model Link \
    source:integer \
    target:integer \
    link_type:string:limit1

Объект связи Ганта требует свойства с именем type для хранения типа связи. Поскольку type зарезервирован в ActiveRecord, мы будем использовать link_type и обрабатывать сопоставление в контроллере.

Выполните миграцию для обновления базы данных:

rake db:migrate

Добавьте некоторые тестовые данные:

  1. Откройте консоль Rails:

    rails c
  2. Добавьте задачи и связи:

    Task.create :text=>"Task 1", :start_date=>"2015-10-25",  :duration=>2, :progress=>0;
    Task.create :text=>"Task 2", :start_date=>"2015-10-27",  :duration=>3, :progress=>0.5;
    Link.create :source=>1, :target=>2, :link_type=>"0";
  3. Выйдите из консоли:

    exit

Далее, давайте загрузим и сохраним данные в диаграмме Ганта с помощью контроллеров.


Шаг 4: Загрузка данных

С моделями и базой данных, подготовленными, следующий шаг - загрузка данных в диаграмму Ганта. dhtmlxGantt ожидает данные в формате JSON. Добавьте новое действие в GanttController для чтения, форматирования и вывода данных:

class GanttController < ApplicationController
  def index
  end
 
  def data 
    tasks = Task.all
    links = Link.all
 
    render :json => {
      :data => tasks.map { |task| {
        :id => task.id,
        :text => task.text,
        :start_date => task.start_date.to_formatted_s(:db),
        :duration => task.duration,
        :progress => task.progress,
        :parent => task.parent,
        :open => true
      }},
      :links => links.map { |link| {
        :id => link.id,
        :source => link.source,
        :target => link.target,
        :type => link.link_type
      }}
    }
  end
end

Добавьте маршрут для этого действия в routes.rb:

Rails.application.routes.draw do
  root :to => "gantt#index"
 
  scope '/api' do
    get "/data", :to => "gantt#data"
  end
end

На стороне клиента вызовите это действие с помощью метода gantt.load:

gantt.config.date_format = "%Y-%m-%d %H:%i:%s";
 
gantt.init("gantt_here");
gantt.load("/api/data");

Конфигурация date_format гарантирует, что формат даты соответствует формату сервера.

Запустите сервер и откройте http://localhost:3000/ в браузере. Вы должны увидеть диаграмму Ганта, заполненную данными из базы данных. Однако изменения пока не будут сохранены. Давайте решим эту проблему дальше.


Шаг 5: Сохранение изменений

dhtmlxGantt может отправлять изменения, сделанные пользователем, в RESTful API на backend для сохранения. Подробности о протоколе доступны здесь.

Начнем с включения отправки изменений на стороне клиента:

gantt.config.date_format = "%Y-%m-%d %H:%i:%s";
 
gantt.init("gantt_here");
gantt.load("/api/data");
 
var dp = new gantt.dataProcessor("/api");
dp.init(gantt);
dp.setTransactionMode("REST");

Далее создайте контроллеры для задач и связей и реализуйте необходимые действия.

Создание контроллера задач

Создайте контроллер для задач:

rails generate controller task --no-helper --no-assets --no-view-specs

Поскольку этому контроллеру не требуются представления, флаги --no-* используются для пропуска ненужных файлов.

Реализуйте действия для создания, обновления и удаления задач:

class TaskController < ApplicationController
    protect_from_forgery
 
    def update
        task = Task.find(params["id"])
        task.text = params["text"]
        task.start_date = params["start_date"]
        task.duration = params["duration"]
        task.progress = params["progress"] || 0
        task.parent = params["parent"]
        task.save
 
        render :json => { :action => "updated" }
    end
 
    def add
        task = Task.create(
            :text => params["text"], 
            :start_date => params["start_date"], 
            :duration => params["duration"],
            :progress => params["progress"] || 0, 
            :parent => params["parent"]
        )
 
        render :json => { :action => "inserted", :tid => task.id }
    end
 
    def delete
        Task.find(params["id"]).destroy
        render :json => { :action => "deleted" }
    end
end

Несколько примечаний: - Действие get не нужно, так как данные загружаются через gantt#data. - Свойство progress по умолчанию равно 0, если не инициализировано клиентом. - Действие для создания новых элементов возвращает ID записи в базе данных клиенту.

Обновите конфигурацию маршрутов:

Rails.application.routes.draw do
  root :to => "gantt#index"
 
  scope '/api' do
    get "/data", :to => "gantt#data"
 
    post "/task", :to => "task#add"
    put "/task/:id", :to => "task#update"
    delete "/task/:id", :to => "task#delete"
  end
end

Тот же подход используется для связей.

Создание контроллера связей

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

rails generate controller link --no-helper --no-assets --no-view-specs

Вот пример того, как может выглядеть реализация:

app/controllers/link_controller.rb

class LinkController < ApplicationController
    protect_from_forgery
 
    def update
        link = Link.find(params["id"])
        link.source = params["source"]
        link.target = params["target"]
        link.link_type = params["type"]
        link.save
 
        render :json => {:action => "updated"}
    end
 
    def add
        link = Link.create( 
            :source => params["source"], 
            :target => params["target"], 
            :link_type => params["type"]
        )
 
        render :json => {:action => "inserted", :tid => link.id}
    end
 
    def delete
        Link.find(params["id"]).destroy
        render :json => {:action => "deleted"}
    end
end

Далее настройте маршруты для этих действий:

config/routes.rb

Rails.application.routes.draw do
  root :to => "gantt#index"
 
  scope '/api' do
    get "/data", :to => "gantt#data"
 
    post "/task", :to => "task#add"
    put "/task/:id", :to => "task#update"
    delete "/task/:id", :to => "task#delete"
 
    post "/link", :to => "link#add"    put "/link/:id", :to => "link#update"    delete "/link/:id", :to => "link#delete"  end
end

На этом этапе, если вы запустите свое приложение, у вас будет интерактивная диаграмма Ганта на базе Rails и MySQL.

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

Хранение порядка задач

Диаграмма Ганта на стороне клиента поддерживает перестановку задач с помощью перетаскивания. Если вы хотите использовать эту функцию, вам нужно будет сохранить порядок задач в базе данных. Общие сведения об этом процессе можно найти здесь.

Давайте добавим эту функциональность в приложение.

Включение перестановки задач на клиенте

Чтобы пользователи могли переставлять задачи в интерфейсе, обновите конфигурацию Ганта в представлении Index:

app/views/gantt/index.html.erb

gantt.config.order_branch = true;gantt.config.order_branch_free = true; 
gantt.init("gantt_here");

Обновление backend для порядка задач

Чтобы хранить порядок задач, добавьте поле sortorder в модель. Вы можете либо создать новую модель, либо изменить существующую.

Чтобы создать новую модель:

rails generate model Task \
    text:string \
    start_date:datetime \
    duration:integer \
    parent:integer \
    progress:decimal \ 
    sortorder:integer

Чтобы изменить существующую модель:

  1. Создайте миграцию:
rails generate migration add_sortorder_to_tasks sortorder:integer
  1. Добавьте значение по умолчанию для столбца sortorder в файл миграции:
class AddSortorderToTasks < ActiveRecord::Migration[5.1]
  def change
    add_column :tasks, :sortorder, :integer, :default=>0
  end
end
  1. Выполните миграцию:
rake db:migrate

Обновление контроллеров для порядка задач

  • Убедитесь, что действие data возвращает задачи, отсортированные по столбцу sortorder:

app/controllers/gantt_controller.rb

class GanttController < ApplicationController
  def index
  end
 
  def data 
    tasks = Task.all
    links = Link.all
 
    render :json=>{
      :data => tasks.order(:sortorder).map{|task|{         :id => task.id,
        :text => task.text,
        :start_date => task.start_date.to_formatted_s(:db),
        :duration => task.duration,
        :progress => task.progress,
        :parent => task.parent,
        :open => true
      }},
      :links => links.map{|link|{
        :id => link.id,
        :source => link.source,
        :target => link.target,
        :type => link.link_type
      }}
    }
  end
end
  • Назначьте начальное значение sortorder при добавлении новых задач:

app/controllers/task_controller.rb

class TaskController < ApplicationController
    ...
    def add
        maxOrder = Task.maximum("sortorder") || 0 
        task = Task.create( 
            :text => params["text"], 
            :start_date=> params["start_date"], 
            :duration => params["duration"],
            :progress => params["progress"] || 0, 
            :parent => params["parent"],
            :sortorder => maxOrder + 1        )
 
        render :json => {:action => "inserted", :tid => task.id}
    end
end
  • Обновите порядок задач, когда пользователь переставляет задачи:

app/controllers/task_controller.rb

class TaskController < ApplicationController
    protect_from_forgery
 
    def update
        task = Task.find(params["id"])
        task.text = params["text"]
        task.start_date = params["start_date"]
        task.duration = params["duration"]
        task.progress = params["progress"] || 0
        task.parent = params["parent"]
        task.save
 
        if(params['target'])            Task.updateOrder(task.id, params['target'])        end 
        render :json => {:action => "updated"}
    end
 
    ...
end

Реализация Task.updateOrder

app/models/task.rb

class Task < ApplicationRecord
    def self.updateOrder(taskId, target)
        nextTask = false
        targetId = target
 
        if(target.start_with?('next:'))
            targetId = target['next:'.length, target.length]
            nextTask = true;
        end
 
        if(targetId == 'null')
            return
        end
 
        targetTask = self.find(targetId)
 
        targetOrder = targetTask.sortorder
 
        if(nextTask)
            targetOrder += 1
        end
 
        self.where("sortorder >= ?", targetOrder).
            update_all('sortorder = sortorder + 1')
 
        task = self.find(taskId)
        task.sortorder = targetOrder
        task.save
    end
end

Безопасность приложения

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

Устранение неполадок

Если задачи и связи не отображаются на странице после выполнения шагов, обратитесь к руководству по устранению неполадок в Устранение проблем с интеграцией бэкенда для помощи в выявлении и решении проблем.

Что дальше

Теперь ваша диаграмма Ганта полностью функциональна. Вы можете найти полный код на GitHub. Не стесняйтесь клонировать или загружать его для своих проектов.

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

К началу