В этой статье описывается процесс создания диаграммы Gantt с использованием backend на Ruby on Rails. В примере используются Ruby 2.4.1, Rails 5.1.3 и MySQL. Предполагается, что у вас уже установлены все необходимые компоненты. Если нет, рекомендуем ознакомиться с официальными учебниками.
Если вы работаете с другим технологическим стеком, другие варианты интеграции можно найти здесь:
Демо-проект также доступен на GitHub: https://github.com/DHTMLX/gantt-howto-rails.
Чтобы создать новый проект, выполните в терминале следующую команду:
rails new gantt-app -d mysql
Начнем с создания контроллера и главной страницы приложения. Перейдите в папку приложения и создайте новый контроллер с действием index:
cd gantt-app
rails generate controller gantt index
Вы увидите подтверждение о создании новых файлов.
Для настройки маршрутизации откройте файл config/routes.rb и измените маршрут по умолчанию, чтобы он указывал на действие "index" нового контроллера:
config/routes.rb
Rails.application.routes.draw do
root :to => "gantt#index"
end
Теперь проверьте работу сервера, выполнив команду:
rails server
Затем откройте в браузере http://localhost:3000/. Вы увидите пустую страницу, как показано ниже:
Когда приложение работает и главная страница готова, следующим шагом будет добавление диаграммы Gantt.
Теперь нужно встроить диаграмму Gantt на страницу.
Откройте файл layout и вставьте yield внутрь тега head. Это позволит подключать файлы dhtmlxGantt:
app/views/layouts/application.html.erb
<!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>
Далее откройте view gantt/index и добавьте на страницу Gantt:
app/views/gantt/index.html.erb
<% 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>
Здесь файлы dhtmlxGantt загружаются с CDN, а не локально. Для разработки вы можете использовать исходные файлы из дистрибутива.
Теперь снова откройте http://localhost:3000/ в браузере. Вы увидите:
У вас появилась диаграмма Gantt, в которой можно добавлять и редактировать задачи, но функционал сохранения пока отсутствует. Это будет реализовано на следующем этапе с помощью моделей.
Поскольку используется MySQL, убедитесь, что настройки подключения в config/database.yml указаны верно, например:
config/database.yml
development:
adapter: mysql2
encoding: utf8
host: localhost
database: gantt-app
username: root
password:
Далее необходимо создать модели для задач и связей.
Для создания модели Task с нужными свойствами выполните команду:
rails generate model Task \
text:string \
start_date:datetime \
duration:integer \
parent:integer \
progress:decimal
Аналогично создайте модель Link:
rails generate model Link \
source:integer \
target:integer \
link_type:string:limit1
Обратите внимание, что объект связи в dhtmlxGantt требует свойства type для указания типа связи (начало-к-началу, конец-к-концу и т.д.).
Поскольку имя "type" зарезервировано в ActiveRecord, здесь используется свойство link_type, а необходимое сопоставление будет реализовано в контроллере.
Полный список обязательных и дополнительных свойств смотрите в документации по объекту Task и объекту Link.
Затем выполните миграцию для обновления базы данных:
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" для выхода из консоли.
Далее реализуем загрузку и сохранение данных в контроллере.
Когда модели и миграции готовы, можно загрузить данные из базы в диаграмму Gantt.
Поскольку dhtmlxGantt ожидает данные в формате JSON, добавьте новое действие в GanttController, которое будет читать, форматировать и выдавать данные:
app/controllers/gantt_controller.rb
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:
config/routes.rb
Rails.application.routes.draw do
root :to => "gantt#index"
scope '/api' do get "/data", :to => "gantt#data" endend
На клиенте вызовите это действие с помощью метода gantt.load:
app/views/gantt/index.html.erb
gantt.config.date_format = "%Y-%m-%d %H:%i:%s";
gantt.init("gantt_here");
gantt.load("/api/data");
Конфигурация date_format определяет формат дат (например, start_date задачи), получаемых с сервера, и соответствует форматированию дат в Rails.
Если вы запустите сервер и откроете http://localhost:3000/, вы увидите диаграмму Gantt с задачами и связями из базы данных. Однако изменения пока не сохраняются — это будет реализовано далее.
dhtmlxGantt может отправлять все изменения пользователя на RESTful API backend, где они сохраняются в базе данных. Подробнее о протоколе читайте здесь.
Чтобы включить сохранение, сначала активируйте отправку изменений на клиенте:
app/views/gantt/index.html.erb
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- предотвращают создание лишних файлов.
Реализуйте действия для создания, обновления и удаления:
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
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
Несколько замечаний по коду:
Наконец, добавьте маршруты для этих действий, чтобы пользователи могли просматривать, создавать, обновлять и удалять задачи в диаграмме Gantt:
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" end
end
Следующим шагом будет настройка аналогичного функционала для связей.
Создайте контроллер Link с помощью следующей команды:
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
Вот и всё. После запуска приложения у вас будет интерактивная диаграмма Gantt на базе Rails и MySQL:
Чтобы узнать больше о возможностях dhtmlxGantt, ознакомьтесь с нашими руководствами.
Клиентская часть Gantt поддерживает изменение порядка задач с помощью drag and drop. Если вы используете эту функцию, порядок задач потребуется сохранять в базе данных. Общий обзор доступен здесь.
Давайте добавим эту возможность в приложение.
Сначала включите изменение порядка задач в интерфейсе, обновив конфигурацию Gantt во вьюхе Index:
app/views/gantt/index.html.erb
gantt.config.order_branch = true;gantt.config.order_branch_free = true;
gantt.init("gantt_here");
Теперь обновите серверную часть, чтобы учесть эти изменения. Необходимо добавить в модель поле для порядка, которое мы назовём 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
2. Отредактируйте сгенерированную миграцию, чтобы задать значение по умолчанию для столбца "sortorder":
class AddSortorderToTasks < ActiveRecord::Migration[5.1]
def change
add_column :tasks, :sortorder, :integer, :default=>0
end
end
Затем примените миграцию:
rake db:migrate
Далее обновите CRUD-операции в контроллерах:
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-атаки. Разработчики должны самостоятельно обеспечивать защиту серверной части своих приложений. Подробнее об этом читайте в этой статье.
Если вы выполнили шаги по интеграции Gantt с Ruby on Rails, но задачи и связи не отображаются на странице, обратитесь к руководству по устранению неполадок: Устранение проблем интеграции с backend. Там вы найдёте рекомендации по диагностике частых проблем.
Теперь, когда ваша диаграмма Gantt полностью функционирует, вы можете ознакомиться с полным кодом на GitHub, где его можно клонировать или скачать для использования в своих проектах.
Дополнительно изучите руководства по различным возможностям Gantt или обучающие материалы по интеграции Gantt с другими серверными фреймворками.
К началу