dhtmlxGantt с PHP: Laravel

Это руководство объясняет, как интегрировать dhtmlxGantt в приложение Laravel.

Если вы ищете учебные материалы по интеграции с другими платформами, ознакомьтесь с:

Полный исходный код для этого руководства можно найти на GitHub.

Также доступно видео-руководство, демонстрирующее создание диаграммы Ганта с использованием PHP Laravel:


Шаг 1. Инициализация проекта

Создание проекта

Для начала создайте новое приложение Laravel с использованием Composer:

composer create-project laravel/laravel gantt-laravel-app

Этот процесс загрузит и настроит все необходимые файлы. После завершения перейдите в директорию проекта и запустите приложение:

cd gantt-laravel-app
php artisan serve

Теперь вы должны увидеть стандартную страницу Laravel при посещении приложения в вашем браузере:


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

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

Чтобы включить dhtmlxGantt, создайте новый файл представления в директории resources/views и назовите его gantt.blade.php:

resources/views/gantt.blade.php

<!DOCTYPE html>
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
 
    <script src="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script>
    <link href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css" rel="stylesheet">
 
    <style type="text/css">
        html, body {
            height: 100%;
            padding: 0px;
            margin: 0px;
            overflow: hidden;
        }
</style> </head> <body> <div id="gantt_here" style='width:100%; height:100%;'></div> <script type="text/javascript">
    gantt.init("gantt_here");
</script> </body>

Этот файл задает базовую HTML-разметку, включает dhtmlxGantt из CDN и инициализирует диаграмму Ганта. Высота для тела документа и контейнера Gantt установлена в 100%, что обеспечивает корректное отображение.

Изменение маршрута по умолчанию

Чтобы сделать страницу Gantt доступной, установите её в качестве маршрута по умолчанию в routes/web.php:

routes/web.php

<?php
 
Route::get('/', function () {
    return view('gantt');
});

Перезапустите приложение и убедитесь, что страница Gantt отображается:


Шаг 3. Создание моделей и миграций

Настройка базы данных

Обновите конфигурацию базы данных в файле .env, например:

.env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=gantt-test
DB_USERNAME=root
DB_PASSWORD=

Генерация моделей и миграций

Используйте Artisan для генерации файлов моделей и миграций для задач и связей:

php artisan make:model Task --migration
php artisan make:model Link --migration

В папке database/migrations определите схему базы данных. Ожидаемая схема для Gantt подробно описана здесь.

Миграция таблицы задач

database/migrations/_create_tasks_table.php

<?php
 
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
 
class CreateTasksTable extends Migration
{
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table){
            $table->increments('id');
            $table->string('text');
            $table->integer('duration');
            $table->float('progress');
            $table->dateTime('start_date');
            $table->integer('parent');
            $table->timestamps();
        });
    }
 
    public function down()
    {
        Schema::dropIfExists('tasks');
    }
}

Миграция таблицы связей

database/migrations/_create_links_table.php

<?php
 
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
 
class CreateLinksTable extends Migration
{
    public function up()
    {
        Schema::create('links', function (Blueprint $table) {
            $table->increments('id');
            $table->string('type');
            $table->integer('source');
            $table->integer('target');
            $table->timestamps();
        });
    }
 
    public function down()
    {
        Schema::dropIfExists('links');
    }
}

Запустите миграции:

php artisan migrate

Добавление тестовых данных

Для генерации тестовых данных создайте сидеры с помощью Artisan:

php artisan make:seeder TasksTableSeeder
php artisan make:seeder LinksTableSeeder

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

database/seeds/TasksTableSeeder.php

<?php
 
use Illuminate\Database\Seeder;
 
class TasksTableSeeder extends Seeder
{
    public function run()
    {
        DB::table('tasks')->insert([
            ['id'=>1, 'text'=>'Project #1', 'start_date'=>'2017-04-01 00:00:00', 
                'duration'=>5, 'progress'=>0.8, 'parent'=>0],
            ['id'=>2, 'text'=>'Task #1', 'start_date'=>'2017-04-06 00:00:00', 
                'duration'=>4, 'progress'=>0.5, 'parent'=>1],
            ['id'=>3, 'text'=>'Task #2', 'start_date'=>'2017-04-05 00:00:00', 
                'duration'=>6, 'progress'=>0.7, 'parent'=>1],
            ['id'=>4, 'text'=>'Task #3', 'start_date'=>'2017-04-07 00:00:00', 
                'duration'=>2, 'progress'=>0, 'parent'=>1],
            ['id'=>5, 'text'=>'Task #1.1', 'start_date'=>'2017-04-05 00:00:00', 
                'duration'=>5, 'progress'=>0.34, 'parent'=>2],
            ['id'=>6, 'text'=>'Task #1.2', 'start_date'=>'2017-04-11 00:00:00', 
                'duration'=>4, 'progress'=>0.5, 'parent'=>2],
            ['id'=>7, 'text'=>'Task #2.1', 'start_date'=>'2017-04-07 00:00:00', 
                'duration'=>5, 'progress'=>0.2, 'parent'=>3],
            ['id'=>8, 'text'=>'Task #2.2', 'start_date'=>'2017-04-06 00:00:00', 
                'duration'=>4, 'progress'=>0.9, 'parent'=>3]
        ]);
    }
}

Вызовите сидеры в DatabaseSeeder.php:

database/seeds/DatabaseSeeder.php

<?php
 
use Illuminate\Database\Seeder;
 
class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(TasksTableSeeder::class);
        $this->call(LinksTableSeeder::class);
    }
}

Заполните базу данных:

php artisan db:seed

Определение классов моделей

Сгенерированные модели для задач и связей готовы к использованию. Однако вы можете добавить атрибут open к модели Task, чтобы дерево проекта было развернуто при загрузке:

/app/Task.php

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Task extends Model
{
    protected $appends = ["open"];
 
    public function getOpenAttribute()
    {
        return true;
    }
}

Модель Link остается без изменений:

/app/Link.php

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Link extends Model
{
}

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

Для загрузки данных в Gantt создайте контроллер, который будет возвращать данные в формате JSON:

app/Http/Controllers/GanttController.php

<?php
namespace App\Http\Controllers;
 
use App\Task;
use App\Link;
 
class GanttController extends Controller
{
    public function get()
    {
        $tasks = new Task();
        $links = new Link();
 
        return response()->json([
            "data" => $tasks->all(),
            "links" => $links->all()
        ]);
    }
}

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

routes/api.php

<?php
 
use App\Http\Controllers\GanttController;
 
Route::get('/data', 'GanttController@get');

Обновите представление для загрузки данных:

resources/views/gantt.blade.php

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

Метод gantt.load отправляет AJAX-запрос для получения данных в формате JSON. date_format указывает ожидаемый формат для дат.

Проверьте приложение снова, и вы увидите задачи, отображенные в диаграмме Ганта:


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

Теперь, когда данные могут быть считаны с сервера, следующим шагом является возможность сохранения изменений обратно в базу данных. Клиент будет отправлять RESTful-запросы (POST/PUT/DELETE) для действий с задачами и связями. Подробности об этих запросах можно найти здесь.

Следующий шаг включает создание контроллеров для обработки этих действий и активацию сохранения данных на стороне клиента.

Добавление контроллеров

Создайте RESTful-контроллеры ресурсов для задач и связей для обработки действий, таких как добавление, обновление и удаление.

Контроллер для задач

TaskController обрабатывает создание, обновление и удаление задач. Вот код:

app/Http/Controllers/TaskController.php

<?php
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\Task;
 
class TaskController extends Controller
{
    public function store(Request $request){
        $task = new Task();
 
        $task->text = $request->text;
        $task->start_date = $request->start_date;
        $task->duration = $request->duration;
        $task->progress = $request->has("progress") ? $request->progress : 0;
        $task->parent = $request->parent;
 
        $task->save();
 
        return response()->json([
            "action"=> "inserted",
            "tid" => $task->id
        ]);
    }
 
    public function update($id, Request $request){
        $task = Task::find($id);
 
        $task->text = $request->text;
        $task->start_date = $request->start_date;
        $task->duration = $request->duration;
        $task->progress = $request->has("progress") ? $request->progress : 0;
        $task->parent = $request->parent;
 
        $task->save();
 
        return response()->json([
            "action"=> "updated"
        ]);
    }
 
    public function destroy($id){
        $task = Task::find($id);
        $task->delete();
 
        return response()->json([
            "action"=> "deleted"
        ]);
    }
}

Маршрут для этих действий определяется следующим образом:

routes/api.php

<?php
 
use Illuminate\Http\Request;
 
Route::get('/data', 'GanttController@get');
Route::resource('task', 'TaskController');

Заметки

  • Когда задача добавляется, её ID отправляется обратно в свойстве tid ответа.
  • Если запрос не содержит параметра progress, он по умолчанию равен 0.
  • Ответ в формате JSON может содержать дополнительные свойства, которые могут быть доступны в обработчике на стороне клиента. Более подробная информация доступна здесь.

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

Контроллер для связей

LinkController управляет созданием, обновлением и удалением связей. Вот код:

app/Http/Controllers/LinkController.php

<?php
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\Link;
 
class LinkController extends Controller
{
    public function store(Request $request){
        $link = new Link();
 
        $link->type = $request->type;
        $link->source = $request->source;
        $link->target = $request->target;
 
        $link->save();
 
        return response()->json([
            "action"=> "inserted",
            "tid" => $link->id
        ]);
    }
 
    public function update($id, Request $request){
        $link = Link::find($id);
 
        $link->type = $request->type;
        $link->source = $request->source;
        $link->target = $request->target;
 
        $link->save();
 
        return response()->json([
            "action"=> "updated"
        ]);
    }
 
    public function destroy($id){
        $link = Link::find($id);
        $link->delete();
 
        return response()->json([
            "action"=> "deleted"
        ]);
    }
}

Маршруты для этого контроллера следующие:

routes/api.php

<?php
 
use Illuminate\Http\Request;
 
Route::get('/data', 'GanttController@get');
Route::resource('task', 'TaskController');
Route::resource('link', 'LinkController');

Включение сохранения данных на стороне клиента

Чтобы API работало на стороне клиента, необходимо внести несколько настроек:

resources/views/gantt.blade.php

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");

С этой настройкой диаграмма Ганта теперь полностью интерактивна, позволяя добавлять, обновлять и удалять задачи и связи.

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

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

Если задачи изменяют порядок с помощью перетаскивания, новый порядок необходимо сохранить в базе данных. Процесс описан здесь.

Включение изменения порядка на клиенте

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

resources/views/gantt.blade.php

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

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

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

Schema::create('tasks', function (Blueprint $table){
    $table->increments('id');
    $table->string('text');
    $table->integer('duration');
    $table->float('progress');
    $table->dateTime('start_date');
    $table->integer('parent');
    $table->integer('sortorder')->default(0);
    $table->timestamps();
});

Или добавьте миграцию для включения столбца:

php artisan make:migration add_sortorder_to_tasks_table --table=tasks

Файл миграции:

database/migrations/_add_sortorder_to_tasks_table.php

<?php
 
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
 
class AddSortorderToTasksTable extends Migration
{
    public function up()
    {
        Schema::table('tasks', function (Blueprint $table) {
            $table->integer('sortorder')->default(0);
        });
    }
 
    public function down()
    {
        Schema::table('tasks', function (Blueprint $table) {
            $table->dropColumn('sortorder');
        });
    }
}

Примените миграцию:

php artisan migrate

Обновление контроллеров

  1. Конечная точка GET /data должна возвращать задачи, отсортированные по sortorder:

app/Http/Controllers/GanttController.php

<?php
namespace App\Http\Controllers;
use App\Task;
use App\Link;
 
class GanttController extends Controller
{
    public function get(){
        $tasks = new Task();
        $links = new Link();
 
        return response()->json([
            "data" => $tasks->orderBy('sortorder')->get(),            "links" => $links->all()
        ]);
    }
}
  1. Новые задачи должны иметь начальное значение sortorder:

app/Http/Controllers/TaskController.php

public function store(Request $request){
    $task = new Task();
 
    $task->text = $request->text;
    $task->start_date = $request->start_date;
    $task->duration = $request->duration;
    $task->progress = $request->has("progress") ? $request->progress : 0;
    $task->parent = $request->parent;
    $task->sortorder = Task::max("sortorder") + 1; 
    $task->save();
 
    return response()->json([
        "action"=> "inserted",
        "tid" => $task->id
    ]);
}
  1. Обработка обновлений при изменении порядка задач:

app/Http/Controllers/TaskController.php

public function update($id, Request $request){
    $task = Task::find($id);
 
    $task->text = $request->text;
    $task->start_date = $request->start_date;
    $task->duration = $request->duration;
    $task->progress = $request->has("progress") ? $request->progress : 0;
    $task->parent = $request->parent;
 
    $task->save();
 
    if($request->has("target")){        $this->updateOrder($id, $request->target);    } 
    return response()->json([
        "action"=> "updated"
    ]);
}
 
private function updateOrder($taskId, $target){
    $nextTask = false;
    $targetId = $target;
 
    if(strpos($target, "next:") === 0){
        $targetId = substr($target, strlen("next:"));
        $nextTask = true;
    }
 
    if($targetId == "null")
        return;
 
    $targetOrder = Task::find($targetId)->sortorder;
    if($nextTask)
        $targetOrder++;
 
    Task::where("sortorder", ">=", $targetOrder)->increment("sortorder");
 
    $updatedTask = Task::find($taskId);
    $updatedTask->sortorder = $targetOrder;
    $updatedTask->save();
}

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

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

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

Если задачи и связи не отображаются после завершения настройки, обратитесь к руководству по устранению неполадок здесь.

Что дальше

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

К началу