В этом руководстве описывается интеграция dhtmlxScheduler в приложение на Laravel.
Также доступны руководства по серверной интеграции с использованием других платформ:
Вы можете ознакомиться с полной демонстрацией на GitHub или следовать пошаговым инструкциям ниже.
Полный исходный код размещён на GitHub.
Начните с создания нового приложения Laravel с помощью Composer:
composer create-project laravel/laravel scheduler-howto-laravel
Этот процесс займет некоторое время для загрузки и настройки всех необходимых файлов. После завершения вы можете проверить установку, выполнив:
cd scheduler-howto-laravel
php artisan serve
На этом этапе вы должны увидеть стандартную приветственную страницу Laravel:
Далее добавьте новую страницу с dhtmlxScheduler в приложение. Создайте новый файл представления с именем scheduler.blade.php в директории resources/views:
resources/views/scheduler.blade.php
<!DOCTYPE html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<script src="https://cdn.dhtmlx.com/scheduler/edge/dhtmlxscheduler.js"></script>
<link href="https://cdn.dhtmlx.com/scheduler/edge/dhtmlxscheduler.css"
rel="stylesheet">
<style type="text/css"> html, body{
height:100%;
padding:0px;
margin:0px;
overflow: hidden;
}
</style>
</head>
<body>
<div id="scheduler_here" class="dhx_cal_container" style='width:100%; height:100%;'>
<div class="dhx_cal_navline">
<div class="dhx_cal_prev_button"> </div>
<div class="dhx_cal_next_button"> </div>
<div class="dhx_cal_today_button"></div>
<div class="dhx_cal_date"></div>
<div class="dhx_cal_tab" name="day_tab"></div>
<div class="dhx_cal_tab" name="week_tab"></div>
<div class="dhx_cal_tab" name="month_tab"></div>
</div>
<div class="dhx_cal_header"></div>
<div class="dhx_cal_data"></div>
</div>
<script type="text/javascript"> scheduler.init("scheduler_here");
</script>
</body>
Это создаёт базовую HTML-структуру, подключает ресурсы dhtmlxScheduler с CDN и инициализирует scheduler с помощью метода init.
Обратите внимание, что и body, и контейнер scheduler имеют высоту 100%. Поскольку scheduler подстраивается под размер контейнера, необходимо явно задать эти размеры.
Чтобы сделать новую страницу доступной, обновите маршрут по умолчанию, чтобы при открытии приложения отображался scheduler.
Измените файл routes/web.php следующим образом:
routes/web.php
<?php
Route::get('/', function () {
return view('scheduler');
});
Перезапустите приложение и убедитесь, что страница scheduler загружается:
В данный момент scheduler пуст. Далее подключим его к базе данных и заполним данными.
Убедитесь, что вы настроили подключение к базе данных в файле .env, например:
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=scheduler-test
DB_USERNAME=root
DB_PASSWORD=
После этого создайте классы моделей и миграции с помощью Artisan:
php artisan make:model Event --migration
Эта команда создаст файлы миграций в папке database/migrations
. Определите схему таблицы на основе ожидаемой структуры таблицы для Scheduler.
Пример кода миграции для таблицы Events:
database/migrations/_create_events_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateEventsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('events', function (Blueprint $table) {
$table->increments('id');
$table->string('text');
$table->dateTime('start_date');
$table->dateTime('end_date');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('events');
}
}
Выполните миграцию для создания таблицы:
php artisan migrate
Если вы столкнулись с ошибкой "Syntax error or access violation: 1071 Specified key was too long; max key length is 1000 bytes" в старых версиях MySQL, выполните следующие шаги.
Для исправления откройте app/Providers/AppServiceProvider.php
и добавьте следующий код в класс AppServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Schema::defaultStringLength(191); }
...
}
Подробнее об этой ошибке здесь.
Далее создайте тестовые данные с помощью класса seeder:
php artisan make:seeder EventsTableSeeder
Добавьте тестовые события в EventsTableSeeder:
database/seeds/EventsTableSeeder.php
<?php
use Illuminate\Database\Seeder;
class EventsTableSeeder extends Seeder
{
public function run()
{
DB::table('events')->insert([
['id'=>1, 'text'=>'Event #1', 'start_date'=>'2018-12-05 08:00:00',
'end_date'=>'2018-12-05 12:00:00'],
['id'=>2, 'text'=>'Event #2', 'start_date'=>'2018-12-06 15:00:00',
'end_date'=>'2018-12-06 16:30:00'],
['id'=>3, 'text'=>'Event #3', 'start_date'=>'2018-12-04 00:00:00',
'end_date'=>'2018-12-20 00:00:00'],
['id'=>4, 'text'=>'Event #4', 'start_date'=>'2018-12-01 08:00:00',
'end_date'=>'2018-12-01 12:00:00'],
['id'=>5, 'text'=>'Event #5', 'start_date'=>'2018-12-20 08:00:00',
'end_date'=>'2018-12-20 12:00:00'],
['id'=>6, 'text'=>'Event #6', 'start_date'=>'2018-12-25 08:00:00',
'end_date'=>'2018-12-25 12:00:00']
]);
}
}
Далее вызовите этот seeder из DatabaseSeeder.php:
database/seeds/DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call(EventsTableSeeder::class);
}
}
Наконец, заполните базу данных:
php artisan db:seed
Данные обрабатываются через Eloquent model. Ранее созданная модель Event готова к использованию и не требует дополнительных изменений для работы с scheduler.
Когда база данных подготовлена и модели определены, следующим шагом будет загрузка данных в scheduler. Поскольку клиент ожидает даты в определённом формате, создайте метод контроллера, который будет возвращать JSON в нужной структуре.
Создайте контроллер командой:
php artisan make:controller EventController
Откройте app/Http/Controllers/EventController.php и добавьте метод index
:
app/Http/Controllers/EventController.php
<?php
namespace App\Http\Controllers;
use App\Event;
class EventController extends Controller
{
public function index(){ $events = new Event();
return response()->json([
"data" => $events->all()
]);
}
}
Зарегистрируйте маршрут, чтобы клиент мог обращаться к этому методу. Добавьте в файл маршрутов api.php:
routes/api.php
<?php
use Illuminate\Http\Request;
Route::get('/data', 'EventController@index');
Теперь обновите представление scheduler для загрузки данных с этого endpoint:
resources/views/scheduler.blade.php
scheduler.config.date_format = "%Y-%m-%d %H:%i:%s";
scheduler.init("scheduler_here", new Date(2018, 11, 3), "week");
scheduler.load("/api/data", "json");
Метод scheduler.load отправляет AJAX-запрос по указанному URL и ожидает ответ в формате JSON, как показано выше.
Указание значения date_format сообщает scheduler, в каком формате ожидать даты и позволяет правильно их парсить на клиенте.
Теперь scheduler должен отображать события, загруженные из базы данных:
В текущей реализации все события загружаются сразу при запуске scheduler. Это подходит для небольших объёмов данных. Однако для приложений типа бронирования или планирования, где данные накапливаются со временем, загрузка всех записей сразу может стать неэффективной и замедлить работу.
Динамическая загрузка решает эту проблему, запрашивая только события, видимые в текущем диапазоне дат. При переходе пользователя к другим датам scheduler будет запрашивать только соответствующие данные.
Чтобы включить динамическую загрузку, обновите resources/views/scheduler.blade.php
так:
resources/views/scheduler.blade.php
scheduler.config.date_format = "%Y-%m-%d %H:%i:%s";
scheduler.setLoadMode("day");
scheduler.init("scheduler_here", new Date(2018, 5, 6), "week");
scheduler.load("/api/events", "json");
Измените контроллер для фильтрации событий по запрошенному диапазону дат:
app/Http/Controllers/EventController.php
class EventController extends Controller
{
public function index(Request $request){
$events = new Event();
$from = $request->from;
$to = $request->to;
return response()->json([
"data" => $events->
where("start_date", "<", $to)->
where("end_date", ">=", $from)->get()
]);
}
}
На данный момент scheduler может только читать данные с backend. Следующий шаг — реализовать сохранение изменений обратно в базу данных.
Клиент работает в REST-режиме, отправляя запросы POST, PUT и DELETE для операций с событиями. Подробнее о форматах запросов и маршрутов.
Теперь необходимо создать контроллер для обработки этих действий, определить маршруты и включить сохранение данных на клиенте.
Начнем с настройки контроллеров. Для каждой модели мы создадим RESTful resource controller, который будет включать методы для добавления, удаления и обновления модели.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Event;
class EventController extends Controller
{
public function index(Request $request){
$events = new Event();
$from = $request->from;
$to = $request->to;
return response()->json([
"data" => $events->
where("start_date", "<", $to)->
where("end_date", ">=", $from)->get()
]);
}
public function store(Request $request){
$event = new Event();
$event->text = strip_tags($request->text);
$event->start_date = $request->start_date;
$event->end_date = $request->end_date;
$event->save();
return response()->json([
"action"=> "inserted",
"tid" => $event->id
]);
}
public function update($id, Request $request){
$event = Event::find($id);
$event->text = strip_tags($request->text);
$event->start_date = $request->start_date;
$event->end_date = $request->end_date;
$event->save();
return response()->json([
"action"=> "updated"
]);
}
public function destroy($id){
$event = Event::find($id);
$event->delete();
return response()->json([
"action"=> "deleted"
]);
}
}
А вот соответствующий маршрут:
routes/api.php
<?php
use Illuminate\Http\Request;
Route::resource('events', 'EventController');
Несколько моментов по этому коду:
Далее мы настроим клиентскую часть для работы с только что созданным API:
resources/views/scheduler.blade.php
scheduler.config.date_format = "%Y-%m-%d %H:%i:%s";
scheduler.setLoadMode("day");
scheduler.init("scheduler_here", new Date(2018, 11, 3), "week");
scheduler.load("/api/events", "json"); var dp = scheduler.createDataProcessor("/api/events"); dp.init(scheduler);
dp.setTransactionMode("REST");
Теперь у вас есть полностью интерактивный Scheduler, в котором можно просматривать, добавлять, изменять и удалять события.
Для получения дополнительных возможностей ознакомьтесь с нашими руководствами.
Для поддержки повторяющихся событий (например, ежедневных) потребуется добавить расширение в scheduler.blade.php, обновить модель и внести изменения в контроллер Events.
Начните с включения расширения recurring в scheduler.blade.php:
resources\views\scheduler.blade.php
<!DOCTYPE html>
...
<body>
...
<script type="text/javascript"> scheduler.plugins({
recurring: true });
scheduler.config.date_format = "%Y-%m-%d %H:%i:%s";
scheduler.init("scheduler_here", new Date(2018, 11, 3), "week");
</script>
</body>
Далее обновите модель.
Если начинаете с нуля, вот полная схема:
Schema::create('events', function (Blueprint $table) {
$table->increments('id');
$table->string('text');
$table->dateTime('start_date');
$table->dateTime('end_date');
$table->string('rec_type')->nullable();
$table->bigInteger('event_length')->nullable();
$table->string('event_pid')->nullable();
$table->timestamps();
});
Либо можно создать такую миграцию:
php artisan make:migration add_recurrings_to_events_table --table=events
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddRecurringsToEventsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('events', function (Blueprint $table) {
$table->string('rec_type')->nullable();
$table->bigInteger('event_length')->nullable()->default(null);
$table->string('event_pid')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('events', function (Blueprint $table) {
$table->dropColumn('rec_type');
$table->dropColumn('event_length');
$table->dropColumn('event_pid');
});
}
}
Затем выполните миграцию:
php artisan migrate
Теперь обновите контроллер.
Для загрузки данных изменений не требуется, но действия записи нужно обновить, поскольку редактирование серий повторяющихся событий требует некоторых особенностей.
Сначала убедитесь, что новые свойства модели Event добавлены в методы "store" и "update":
public function store(Request $request){
$event = new Event();
$event->text = strip_tags($request->text);
$event->start_date = $request->start_date;
$event->end_date = $request->end_date;
$event->rec_type = $request->rec_type;
$event->event_length = $request->event_length;
$event->event_pid = $request->event_pid;
$event->save();
return response()->json([
"action"=> "inserted",
"tid" => $event->id
]);
}
public function update($id, Request $request){
$event = Event::find($id);
$event->text = strip_tags($request->text);
$event->start_date = $request->start_date;
$event->end_date = $request->end_date;
$event->rec_type = $request->rec_type;
$event->event_length = $request->event_length;
$event->event_pid = $request->event_pid;
$event->save();
return response()->json([
"action"=> "updated"
]);
}
Есть ещё три случая, которые нужно обработать.
Сама серия повторяющихся событий хранится как одна запись, а удалённые экземпляры внутри серии — как отдельные записи, связанные с серией и помеченные как 'deleted'. Когда сервер встречает такой элемент, он должен вернуть статус "deleted". Такие записи можно определить, если $event->rec_type == "none":
public function store(Request $request){
$event = new Event();
$event->text = strip_tags($request->text);
$event->start_date = $request->start_date;
$event->end_date = $request->end_date;
$event->rec_type = $request->rec_type;
$event->event_length = $request->event_length;
$event->event_pid = $request->event_pid;
$event->save();
$status = "inserted";
if($event->rec_type == "none"){
$status = "deleted";
}
return response()->json([
"action"=> $status,
"tid" => $event->id
]);
}
Изменённые экземпляры также сохраняются как отдельные записи, связанные с серией, и имеют отметку времени, чтобы не отображать исходный экземпляр. Когда пользователь удаляет изменённый экземпляр, вместо удаления нужно установить rec_type в "none":
public function destroy($id){
$event = Event::find($id);
// удалить изменённый экземпляр серии
if($event->event_pid){
$event->rec_type = "none";
$event->save();
}else{
// удалить обычный экземпляр
$event->delete();
}
$this->deleteRelated($event);
return response()->json([
"action"=> "deleted"
]);
}
Наконец, при обновлении или удалении серии повторяющихся событий все её изменённые экземпляры также должны быть удалены. Так как изменённые экземпляры связаны с оригиналом через временные метки, этот шаг обязателен:
private function deleteRelated($event){
if($event->event_pid && $event->event_pid !== "none"){
Event::where("event_pid", $event->id)->delete();
}
}
public function update($id, Request $request){
$event = Event::find($id);
$event->text = strip_tags($request->text);
$event->start_date = $request->start_date;
$event->end_date = $request->end_date;
$event->rec_type = $request->rec_type;
$event->event_length = $request->event_length;
$event->event_pid = $request->event_pid;
$event->save();
$this->deleteRelated($event); return response()->json([
"action"=> "updated"
]);
}
public function destroy($id){
$event = Event::find($id);
// удалить изменённый экземпляр серии
if($event->event_pid){
$event->rec_type = "none";
$event->save();
}else{
// удалить обычный экземпляр
$event->delete();
}
$this->deleteRelated($event); return response()->json([
"action"=> "deleted"
]);
}
Повторяющееся событие хранится как одна запись в базе данных, но может быть разбито на отдельные экземпляры на клиентской стороне Scheduler. Если вам нужно получить даты отдельных событий на сервере, рассмотрите возможность использования вспомогательной библиотеки для парсинга повторяющихся событий на PHP.
Готовую библиотеку можно найти на GitHub.
Scheduler сам по себе не обеспечивает защиту от угроз, таких как SQL-инъекции, XSS или CSRF-атаки. Защита приложения — ответственность бэкенд-разработчиков. Подробнее смотрите в соответствующей статье.
Если вы выполнили все шаги по интеграции Scheduler с PHP, но события не отображаются, обратитесь к статье Устранение проблем с интеграцией Backend. В ней даны рекомендации по поиску и устранению наиболее частых проблем.
Теперь у вас есть полностью рабочий Scheduler. Полный код доступен на GitHub, откуда вы можете клонировать или скачать его для своих проектов.
Также изучите руководства по дополнительным возможностям Scheduler или уроки по интеграции Scheduler с другими backend-фреймворками.
Наверх