Создание календаря событий (планировщика) с помощью dhtmlxScheduler в Symfony 3

Планировщик — это компонент, который вы не можете пропустить в программном продукте для компании. С помощью планировщика предприятие (или обычный человек) сможет планировать и отслеживать встречи, события, задачи и другие вещи. Как показано в нашем Top 5: Best, планировщик dhtmlx является одной из лучших библиотек JavaScript планировщика, которая позволяет вам реализовать такую ​​функцию в вашем приложении. dhtmlxScheduler — это подобный Google календарь событий JS с широким спектром представлений и функций. Он имеет чистый пользовательский интерфейс и настраиваемый внешний вид.

В этой статье вы узнаете, как создать собственный календарь событий (планировщик) в интерфейсе и бэкэнде с помощью Symfony и dhtmlxscheduler.

Требования

Чтобы создать свой собственный планировщик, вам нужно подготовить следующие библиотеки в вашем проекте. Мы опишем, что нам нужно от них, и если вы не можете включить их (исключая планировщик dhtmlx, поскольку это очевидно необходимо), вы можете написать свой собственный запасной вариант:

A. Планировщик dhtmlx

Вам понадобится копия библиотеки планировщика dhtmlx (файл .zip). Эта библиотека предлагает 2 версии: версию с открытым исходным кодом (Standard Edition), где вы находитесь самостоятельно, читая документацию библиотеки на официальном сайте, или платную версию (Pro Edition), где вы получаете поддержку и коммерческую лицензию.

Из исходного zip-файла вам понадобится только код JavaScript, поскольку бэкэнд будет полностью реализован с помощью Symfony. Этот планировщик очень гибкий, и вы можете настраивать многие вещи по своему усмотрению, мы рекомендуем вам также прочитать документацию. Вы можете скачать любую из упомянутых версий здесь.

Первое, что вам нужно сделать, когда у вас есть zip-файл, — это создать каталог для сохранения библиотеки. В этой статье мы создадим папку библиотек на /web каталог приложения Symfony. Таким образом, исходный код JavaScript будет доступен на yourapplication/web/libraries/dhtmlx, Мы не будем портить исходную структуру загруженного zip-файла, поэтому в этом случае у вас будет кодовая база папок и примеры внутри dhtmlx, которые вы можете использовать для проверки примеров, чтобы ваш планировщик был позже.

B. Moment.js

Основной файл JavaScript Moment.js должен быть доступен по адресу yourapplication/web/libraries/momentjs, Если вы не хотите использовать библиотеку MomentJS для форматирования нашей даты там, где она нам нужна (шаг 4), вы можете создать запасной вариант, заменив getFormatedEvent со следующим кодом:

// Retrieve the format date method (that follows the given pattern) from the scheduler library
var formatDate = scheduler.date.date_to_str("%d-%m-%Y %H:%i:%s");
/**
* Returns an Object with the desired structure of the server.
*
* @param {*} id
* @param {*} useJavascriptDate
*/
function getFormatedEvent(id, useJavascriptDate){
var event;
// If id is already an event object, use it and don't search for it
if(typeof(id) == "object"){
event = id;
}else{
event = scheduler.getEvent(parseInt(id));
}
if(!event){
console.error("The ID of the event doesn't exist: " + id);
return false;
}
var start , end;
if(useJavascriptDate){
start = event.start_date;
end = event.end_date;
}else{
start = formatDate(event.start_date);
end = formatDate(event.end_date);
}
return {
id: event.id,
start_date : start,
end_date : end,
description : event.description,
title : event.text
};
}

C. JQuery или любой другой пользовательский, связанный Библиотека AJAX

Мы будем использовать jQuery AJAX для отправки наших встреч в представлении. В качестве альтернативы, вы можете написать свой собственный простой код XMLHttpRequest для асинхронной отправки данных на ваш сервер с помощью JavaScript или, если вы не хотите использовать jQuery, а другую библиотеку, minAjax довольно полезен и работает так же, как JQuery.

1. Внедрить объект назначения

Заметка

Если у вас уже есть собственный дизайн таблицы для ваших «встреч», пропустите этот шаг и следуйте структуре контроллера на шаге 2.

С помощью планировщика вы сможете планировать события графически на стороне клиента, однако они также должны храниться в некоторой базе данных для вашего пользователя. Это может быть достигнуто с помощью связи с AJAX между клиентом и сервером.

Целью этого примера будет сохранение некоторых Appointment класс для базы данных (MySql, MongoDB, CouchDB и т. д.). Ваша первая задача — создать Appointment класс для вашего приложения. Этот класс может выглядеть и действовать так, как вы хотите, поэтому добавьте любые свойства или методы, которые вы считаете полезными. В этом примере наша сущность будет сгенерирована из следующей таблицы, а именно appointments, Таблица встреч в вашей базе данных будет иметь 5 полей, а именно: id (автоинкремент, не нуль), заголовок (текстовый столбец), описание (текстовый столбец), start_date (столбец datetime) и end_date (столбец datetime):

CREATE TABLE `YourExistentTable`.`appointments`
(
`id`          BIGINT NOT NULL auto_increment,
`title`       VARCHAR(255) NOT NULL,
`description` TEXT NULL,
`start_date`  DATETIME NOT NULL,
`end_date`    DATETIME NOT NULL,
PRIMARY KEY (`id`)
)
engine = innodb; 

В зависимости от того, как вы работаете, вы можете следовать процессу для генерации файлов orm и сущности вручную или из вашей базы данных. Если вы генерируете сущность из существующей базы данных, вы можете запустить следующую команду для генерации файлов ORM:

php bin/console doctrine:mapping:import --force AppBundle yml

Это создаст наш файл ORM для таблицы назначений со следующим результатом в AppBundle/Resources/config/doctrine/Appointments.orm.yml:

AppBundle\Entity\Appointments:
type: entity
table: appointments
id:
id:
type: bigint
nullable: false
options:
unsigned: false
id: true
generator:
strategy: IDENTITY
fields:
title:
type: string
nullable: false
length: 255
options:
fixed: false
description:
type: text
nullable: true
length: 65535
options:
fixed: false
startDate:
type: datetime
nullable: false
column: start_date
endDate:
type: datetime
nullable: false
column: end_date
lifecycleCallbacks: {  }

Затем, когда файл orm существует, вы можете автоматически сгенерировать объект назначений, используя:

php bin/console doctrine:generate:entities AppBundle

Сгенерированный объект в AppBundle/Entity/Appointments будет выглядеть так:

id;
}
/**
* Set title
*
* @param string $title
*
* @return Appointments
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set description
*
* @param string $description
*
* @return Appointments
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set startDate
*
* @param \DateTime $startDate
*
* @return Appointments
*/
public function setStartDate($startDate)
{
$this->startDate = $startDate;
return $this;
}
/**
* Get startDate
*
* @return \DateTime
*/
public function getStartDate()
{
return $this->startDate;
}
/**
* Set endDate
*
* @param \DateTime $endDate
*
* @return Appointments
*/
public function setEndDate($endDate)
{
$this->endDate = $endDate;
return $this;
}
/**
* Get endDate
*
* @return \DateTime
*/
public function getEndDate()
{
return $this->endDate;
}
}

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

2. Реализация планировщика контроллера и маршрутов

Контроллер для планировщика будет иметь только 4 маршрута. Маршруты, которые мы определим, должны быть доступны на /scheduler маршрут вашего проекта, поэтому измените основной routing.yml файл вашего проекта Symfony и зарегистрируйте другой файл маршрутизации, который обрабатывает маршруты для планировщика:

# Create route for scheduler in your app
app_scheduler:
resource: "@AppBundle/Resources/config/routing/scheduler.yml"
prefix:   /scheduler

Обратите внимание, что мы будем хранить новый файл маршрутизации в config/routing папка основного комплекта. scheduler.yml Файл маршрутизации имеет следующий вид:

# app/config/routing.yml
scheduler_index:
path:      /
defaults:  { _controller: AppBundle:Scheduler:index }
methods:  [GET]
scheduler_create:
path:      /appointment-create
defaults:  { _controller: AppBundle:Scheduler:create }
methods:  [POST]
scheduler_update:
path:      /appointment-update
defaults:  { _controller: AppBundle:Scheduler:update }
methods:  [POST]
scheduler_delete:
path:      /appointment-delete
defaults:  { _controller: AppBundle:Scheduler:delete }
methods:  [DELETE]

Каждый маршрут обрабатывается функцией в контроллере планировщика, расположенного в AppBundle (который мы сейчас создадим). Только 3 из них будут использоваться для создания, удаления и изменения назначений через AJAX. Индекс маршрута (yourwebsite/scheduler) отобразит Планировщик в браузере.

Теперь, когда маршруты зарегистрированы, вам нужно будет создать контроллер, который будет обрабатывать маршруты и логику каждого из них. Поскольку логика может варьироваться в зависимости от способа обработки сущностей, следующий контроллер показывает, как обрабатывать каждое событие, работая с сущностью Назначение. Все ответы даны в формате JSON (кроме индекса), чтобы предоставить информацию о состоянии действия:

getDoctrine()->getManager();
// Get repository of appointments
$repositoryAppointments = $em->getRepository("AppBundle:Appointments");
// Note that you may want to filter the appointments that you want to send
// by dates or something, otherwise you will send all the appointments to render
$appointments = $repositoryAppointments->findAll();
// Generate JSON structure from the appointments to render in the start scheduler.
$formatedAppointments = $this->formatAppointmentsToJson($appointments);
// Render scheduler
return $this->render("default/scheduler.html.twig", [
'appointments' => $formatedAppointments
]);
}
/**
* Handle the creation of an appointment.
*
*/
public function createAction(Request $request){
$em = $this->getDoctrine()->getManager();
$repositoryAppointments = $em->getRepository("AppBundle:Appointments");
// Use the same format used by Moment.js in the view
$format = "d-m-Y H:i:s";
// Create appointment entity and set fields values
$appointment = new Appointment();
$appointment->setTitle($request->request->get("title"));
$appointment->setDescription($request->request->get("description"));
$appointment->setStartDate(
\DateTime::createFromFormat($format, $request->request->get("start_date"))
);
$appointment->setEndDate(
\DateTime::createFromFormat($format, $request->request->get("end_date"))
);
// Create appointment
$em->persist($appointment);
$em->flush();
return new JsonResponse(array(
"status" => "success"
));
}
/**
* Handle the update of the appointments.
*
*/
public function updateAction(Request $request){
$em = $this->getDoctrine()->getManager();
$repositoryAppointments = $em->getRepository("AppBundle:Appointments");
$appointmentId = $request->request->get("id");
$appointment = $repositoryAppointments->find($appointmentId);
if(!$appointment){
return new JsonResponse(array(
"status" => "error",
"message" => "The appointment to update $appointmentId doesn't exist."
));
}
// Use the same format used by Moment.js in the view
$format = "d-m-Y H:i:s";
// Update fields of the appointment
$appointment->setTitle($request->request->get("title"));
$appointment->setDescription($request->request->get("description"));
$appointment->setStartDate(
\DateTime::createFromFormat($format, $request->request->get("start_date"))
);
$appointment->setEndDate(
\DateTime::createFromFormat($format, $request->request->get("end_date"))
);
// Update appointment
$em->persist($appointment);
$em->flush();
return new JsonResponse(array(
"status" => "success"
));
}
/**
* Deletes an appointment from the database
*
*/
public function deleteAction(Request $request){
$em = $this->getDoctrine()->getManager();
$repositoryAppointments = $em->getRepository("AppBundle:Appointments");
$appointmentId = $request->request->get("id");
$appointment = $repositoryAppointments->find($appointmentId);
if(!$appointment){
return new JsonResponse(array(
"status" => "error",
"message" => "The given appointment $appointmentId doesn't exist."
));
}
// Remove appointment from database !
$em->remove($appointment);
$em->flush();
return new JsonResponse(array(
"status" => "success"
));
}
/**
* Returns a JSON string from a group of appointments that will be rendered on the calendar.
* You can use a serializer library if you want.
*
* The dates need to follow the format d-m-Y H:i e.g : "13-07-2017 09:00"
*
*
* @param $appointments
*/
private function formatAppointmentsToJson($appointments){
$formatedAppointments = array();
foreach($appointments as $appointment){
array_push($formatedAppointments, array(
"id" => $appointment->getId(),
"description" => $appointment->getDescription(),
// Is important to keep the start_date, end_date and text with the same key
// for the JavaScript area
// altough the getter could be different e.g:
// "start_date" => $appointment->getBeginDate();
"text" => $appointment->getTitle(),
"start_date" => $appointment->getStartDate()->format("Y-m-d H:i"),
"end_date" => $appointment->getEndDate()->format("Y-m-d H:i")
));
}
return json_encode($formatedAppointments);
}
}

Поскольку планировщик dhtmlx требует start_date, end_date а также text ключи в событии, вам нужно будет предоставить их на каждом событии, это означает, что вы не можете изменить их имя.

3. Реализация макета и структуры скриптов

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

Мы будем использовать следующий базовый файл для нашего макета в Twig (base.html.twig):

{# application/resources/views/base.html.twig #}
{% block title %}Welcome!{% endblock %}
{% block stylesheets %}{% endblock %}
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}

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

Тогда, как определено в нашем контроллере, наш scheduler.html.twig Файл будет находиться в каталоге app / resources / views / default, поэтому обязательно создайте его по указанному пути (или измените его в контроллере). Макет планировщика будет выглядеть так:

{# default/scheduler.html.twig #}
{% extends "base.html.twig" %}
{% block stylesheets %}
html, body{
margin:0px;
padding:0px;
height:100%;
overflow:hidden;
}
{% endblock %}
{% block body -%}
 
 
{% endblock %}
{% block javascripts %}
// Expose the appointments globally by printing the JSON string with twig and the raw filter
// so they can be accesible by the schedulerScripts.js the controller
window.GLOBAL_APPOINTMENTS = {{ appointments|raw }};
// As the scheduler scripts will be in other files, the routes generated by twig
// should be exposed in the window too
window.GLOBAL_SCHEDULER_ROUTES = {
create: '{{ path("scheduler_create") }}',
update: '{{ path("scheduler_update") }}',
delete: '{{ path("scheduler_delete") }}'
};
{% endblock %}

Он включает в себя блок стилей, блокирует плоский стиль планировщика и некоторые правила, чтобы он выглядел хорошо в полноэкранном режиме. Затем в теле блока, требуемой разметки для планировщика и в блоке JavaScripts, мы будем включать в следующем порядке библиотеки: dhtmlxscheduler, jQuery для AJAX, MomentJS для удобного управления датами.

Необработанный тег сценария объявляет 2 переменные в окне (глобальные), а именно GLOBAL_APPOINTMENTS и GLOBAL_SCHEDULER_ROUTES. Объект назначений сохраняет Назначения из представления индекса (см. Контроллер индекса для получения дополнительной информации) в формате JSON (но интерпретируется как объект в JS), поэтому нам нужно использовать необработанный фильтр Twig. Объект маршрутов хранит маршруты, сгенерированные Twig, которые будут использоваться для обновления, создания и удаления встреч. Поскольку логика управления планировщиком будет записана в другом файле JavaScript, мы не можем использовать веточку внутри, поэтому рекомендуется генерировать их там, где ветка доступна, и затем обращаться к ним в файлах с помощью окна.

Теперь мы собираемся написать содержание schedulerScripts.js файл, который будет содержать код для обработки логики планировщика в представлении.

4. Напишите клиентскую логику

Для нашего планировщика мы позволим пользователю создавать встречи в календаре с помощью диалогового окна, а именно, Lightbox по умолчанию для планировщика dhtmlx. Первое, что вам нужно сделать, это настроить поведение вашего планировщика по умолчанию, изменив объект конфигурации планировщика. По крайней мере, вы должны предоставить формат xml_date, остальные являются необязательными.

Затем настройте разделы формы для вставки и редактирования встреч. В этом случае, поскольку у нас есть только 2 поля, а именно Заголовок и Описание, заголовок будет сопоставлен с текстовым полем Планировщика по умолчанию. Поля времени и текста по умолчанию должны существовать на лайтбоксе, время автоматически определяет начальное и конечное поля. Затем перейдите к инициализации планировщика в каком-либо режиме (день, неделя или месяц) в элементе DIV и при необходимости укажите дату, когда планировщик должен начаться. Затем выполните синтаксический анализ событий, возвращаемых контроллером индекса (все встречи хранятся в массиве window.GLOBAL_APPOINTMENTS. В качестве последнего вы можете прикрепить события для обработки действий пользователя с планировщиком.

Код schedulerScripts.js будет следующим:

// 1. Configure Scheduler Basic Settings
scheduler.config.xml_date="%Y-%m-%d %H:%i";
scheduler.config.first_hour = 6;
scheduler.config.last_hour = 24;
scheduler.config.limit_time_select = true;
scheduler.config.details_on_create = true;
// Disable event edition with single click
scheduler.config.select = false;
scheduler.config.details_on_dblclick = true;
scheduler.config.max_month_events = 5;
scheduler.config.resize_month_events = true;
// 2. Configure Lightbox (form) sections
scheduler.config.lightbox.sections = [
// If you have another field on your Appointment entity (e.g example_field column), you would add it like
// {name:"Example Field", height:30, map_to:"example_field", type:"textarea"},
{name:"Title", height:30, map_to:"text", type:"textarea"},
{name:"Description", height:30, map_to:"description", type:"textarea"},
{name:"time", height:72, type:"time", map_to:"auto"}
];
// 3. Start calendar with custom settings
var initSettings = {
// Element where the scheduler will be started
elementId: "scheduler_element",
// Date object where the scheduler should be started
startDate: new Date(),
// Start mode
mode: "week"
};
scheduler.init(initSettings.elementId, initSettings.startDate , initSettings.mode);
// 4. Parse the initial (From index controller) appointments
scheduler.parse(window.GLOBAL_APPOINTMENTS, "json");
// 5. Function that formats the events to the expected format in the server side
/**
* Returns an Object with the desired structure of the server.
*
* @param {*} id
* @param {*} useJavascriptDate
*/
function getFormatedEvent(id, useJavascriptDate){
var event;
// If id is already an event object, use it and don't search for it
if(typeof(id) == "object"){
event = id;
}else{
event = scheduler.getEvent(parseInt(id));
}
if(!event){
console.error("The ID of the event doesn't exist: " + id);
return false;
}
var start , end;
if(useJavascriptDate){
start = event.start_date;
end = event.end_date;
}else{
start = moment(event.start_date).format('DD-MM-YYYY HH:mm:ss');
end = moment(event.end_date).format('DD-MM-YYYY HH:mm:ss');
}
return {
id: event.id,
start_date : start,
end_date : end,
description : event.description,
title : event.text
};
}
// 6. Attach Event Handlers !
/**
* Handle the CREATE scheduler event
*/
scheduler.attachEvent("onEventAdded", function(id,ev){
var schedulerState = scheduler.getState();
$.ajax({
url:  window.GLOBAL_SCHEDULER_ROUTES.create,
data: getFormatedEvent(ev),
dataType: "json",
type: "POST",
success: function(response){
// Very important:
// Update the ID of the scheduler appointment with the ID of the database
// so we can edit the same appointment now !
scheduler.changeEventId(ev.id , response.id);
alert('The appointment '+ev.text+ " has been succesfully created");
},
error:function(error){
alert('Error: The appointment '+ev.text+' couldnt be created');
console.log(error);
}
});
});
/**
* Handle the UPDATE event of the scheduler on all possible cases (drag and drop, resize etc..)
*
*/
scheduler.attachEvent("onEventChanged", function(id,ev){
$.ajax({
url:  window.GLOBAL_SCHEDULER_ROUTES.update,
data: getFormatedEvent(ev),
dataType: "json",
type: "POST",
success: function(response){
if(response.status == "success"){
alert("Event succesfully updated !");
}
},
error: function(err){
alert("Error: Cannot save changes");
console.error(err);
}
});
return true;
});
/**
* Handle the DELETE appointment event
*/
scheduler.attachEvent("onConfirmedBeforeEventDelete",function(id,ev){
$.ajax({
url: window.GLOBAL_SCHEDULER_ROUTES.delete,
data:{
id: id
},
dataType: "json",
type: "DELETE",
success: function(response){
if(response.status == "success"){
if(!ev.willDeleted){
alert("Appointment succesfully deleted");
}
}else if(response.status == "error"){
alert("Error: Cannot delete appointment");
}
},
error:function(error){
alert("Error: Cannot delete appointment: " + ev.text);
console.log(error);
}
});
return true;
});
/**
* Edit event with the right click too
*
* @param {type} id
* @param {type} ev
* @returns {Boolean}
*/
scheduler.attachEvent("onContextMenu", function (id, e){
scheduler.showLightbox(id);
e.preventDefault();
});

Наконец сохранить изменения, доступ к URL вашего проекта http://yourproject/scheduler и теперь вы можете проверить планировщик. В качестве окончательной рекомендации ознакомьтесь с документацией по планировщику dhtmlx, чтобы найти больше удивительных утилит, которые позволят вам создать лучшее приложение планировщика для ваших клиентов.

Показать данные из хранилища в форме встречи

В соответствии со структурой вашего проекта ваши встречи не будут просто заголовком, описанием и временем, но они могут иметь тип, который зависит от значений другой таблицы (внешних ключей). В следующем примере наша таблица встреч будет иметь ManyToOne отношение в столбце category со столом а именно categoriesчья структура выглядит так:

AppBundle\Entity\Categories:
type: entity
table: categories
id:
id:
type: bigint
nullable: false
options:
unsigned: false
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
nullable: false
length: 255
options:
fixed: false
lifecycleCallbacks: {  }

Когда существует файл orm таблицы Categories, вы можете автоматически сгенерировать объект Categories, используя:

php bin/console doctrine:generate:entities AppBundle

Сгенерированный объект в AppBundle/Entity/Categories будет выглядеть так:

id;
}
/**
* Set name
*
* @param string $name
*
* @return Categories
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
}

Теперь у вас есть новый репозиторий, к которому можно получить доступ с AppBundle:Categories, Конфигурируя новое поле в категории таблицы встреч, чтобы иметь ManyToOne связь с другой таблицей, наш оригинальный файл ORM из Деловое свидание, встреча Таблица, очевидно, тоже изменится:

AppBundle\Entity\Appointments:
type: entity
table: appointments
indexes:
category:
columns:
- category
id:
id:
type: bigint
nullable: false
options:
unsigned: false
id: true
generator:
strategy: IDENTITY
fields:
title:
type: string
nullable: false
length: 255
options:
fixed: false
description:
type: text
nullable: true
length: 65535
options:
fixed: false
startDate:
type: datetime
nullable: false
column: start_date
endDate:
type: datetime
nullable: false
column: end_date
manyToOne:
category:
targetEntity: Categories
cascade: {  }
fetch: LAZY
mappedBy: null
inversedBy: null
joinColumns:
category:
referencedColumnName: id
orphanRemoval: false
lifecycleCallbacks: {  }

И если вы снова сгенерируете объект, он добавит 2 новых метода:

// project/AppBundle/Entity/Appointments.php
/**
* @var \AppBundle\Entity\Categories
*/
private $category;
/**
* Set category
*
* @param \AppBundle\Entity\Categories $category
*
* @return Appointments
*/
public function setCategory(\AppBundle\Entity\Categories $category = null)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* @return \AppBundle\Entity\Categories
*/
public function getCategory()
{
return $this->category;
}

Теперь вы можете вставить новое поле в объект встреч в бэкэнде.

Поскольку наша форма — это не просто форма Symfony, а «форма», созданная с помощью JavaScript библиотекой планировщика, если вы хотите добавить вход выбора, в котором перечислены все строки категорий из базы данных, чтобы ваш пользователь мог выбрать категорию для встречи Вам нужно будет точно так же, как вы делали встречи, преобразовать строки репозитория категорий в JSON, чтобы они могли обрабатываться планировщиком.

В вашем контроллере планировщика создайте новый метод, который отформатирует ваши категории в JSON:

/**
* Returns a JSON string from data of a repository. The structure may vary according to the
* complexity of your forms.
*
* @param $categories
*/
private function formatCategoriesToJson($categories){
$formatedCategories = array();
foreach($categories as $categorie){
array_push($formatedCategories, array(
// Important to set an object with the 2 following properties !
"key" => $categorie->getId(),
"label" => $categorie->getName()
));
}
return json_encode($formatedCategories);
}

Важно отправить объект со структурным ключом и меткой и больше ничего. Затем вам нужно изменить ваш indexAction, который отображает планировщик, здесь отправьте структуру JSON из данных вашего хранилища категорий в качестве переменной для ветки, а именно categories:

/**
* View that renders the scheduler.
*
*/
public function indexAction()
{
// Retrieve entity manager
$em = $this->getDoctrine()->getManager();
// Get repository of appointments
$repositoryAppointments = $em->getRepository("AppBundle:Appointments");
// Get repository of categories
$repositoryCategories = $em->getRepository("AppBundle:Categories");
// Note that you may want to filter the appointments that you want to send
// by dates or something, otherwise you will send all the appointments to render
$appointments = $repositoryAppointments->findAll();
// Generate JSON structure from the appointments to render in the start scheduler.
$formatedAppointments = $this->formatAppointmentsToJson($appointments);
// Retrieve the data from the repository categories
$categories = $repositoryCategories->findAll();
// Generate JSON structure from the data of the repository (in this case the categories)
// so they can be rendered inside a select on the lightbox
$formatedCategories = $this->formatCategoriesToJson($categories);
// Render scheduler
return $this->render("default/scheduler.html.twig", [
'appointments' => $formatedAppointments,
'categories' => $formatedCategories
]);
}

Категории будут теперь доступны для Twig в виде строки, но еще не для JavaScript, поэтому вам нужно будет предоставить его глобально в представлении Twig, чтобы он мог быть доступен для файла schedulerScripts, в этом случае мы сделаем это через window.GLOBAL_CATEGORIES:

{% block javascripts %}
// Expose the appointments globally by printing the JSON string with twig and the raw filter
// so they can be accesible by the schedulerScripts.js the controller
window.GLOBAL_APPOINTMENTS = {{ appointments|raw }};
// As the scheduler scripts will be in other files, the routes generated by twig
// should be exposed in the window too
window.GLOBAL_SCHEDULER_ROUTES = {
create: '{{ path("scheduler_create") }}',
update: '{{ path("scheduler_update") }}',
delete: '{{ path("scheduler_delete") }}'
};
// Important:
// Expose the categories of the Appointments so they can be shown in the select
window.GLOBAL_CATEGORIES = {{ categories|raw }};
{% endblock %}

Теперь объект категорий должен отображаться в нашей форме для встречи в календаре, это означает, что вам нужно изменить файл schedulerScripts.js и изменить шаг 2, который определяет разделы лайтбокса:

// 2. Configure Lightbox (form) sections
scheduler.config.lightbox.sections = [
// If you have another field on your Appointment entity (e.g example_field column), you would add it like
// {name:"Example Field", height:30, map_to:"example_field", type:"textarea"},
{name:"Title", height:30, map_to:"text", type:"textarea"},
{name:"Description", height:30, map_to:"description", type:"textarea"},
// Add a select that allow you to select the category of the appointment according to a table
// "categories" from the database :)
{name:"Category", options: window.GLOBAL_CATEGORIES , map_to: "category", type: "select", height:30 },
// Add the time field
{name:"time", height:72, type:"time", map_to:"auto"},
];

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

/**
* Returns an Object with the desired structure of the server.
*
* @param {*} id
* @param {*} useJavascriptDate
*/
function getFormatedEvent(id, useJavascriptDate){
var event;
// If id is already an event object, use it and don't search for it
if(typeof(id) == "object"){
event = id;
}else{
event = scheduler.getEvent(parseInt(id));
}
if(!event){
console.error("The ID of the event doesn't exist: " + id);
return false;
}
var start , end;
if(useJavascriptDate){
start = event.start_date;
end = event.end_date;
}else{
start = formatDate(event.start_date);
end = formatDate(event.end_date);
}
return {
id: event.id,
start_date : start,
end_date : end,
description : event.description,
title : event.text,
// Important add the category ID
category: event.category
};
}

Наконец, вам нужно обработать события в бэкэнде (создать и обновить), чтобы они могли стать объектом типа категории и сущность Appointment могла быть сохранена:

Заметка

Это изменение должно быть сделано в updateAction тоже.

/**
* Handle the creation of an appointment.
*
*/
public function createAction(Request $request){
$em = $this->getDoctrine()->getManager();
$repositoryAppointments = $em->getRepository("AppBundle:Appointments");
// Use the same format used by Moment.js in the view
$format = "d-m-Y H:i:s";
// Create appointment entity and set fields values
$appointment = new Appointment();
$appointment->setTitle($request->request->get("title"));
$appointment->setDescription($request->request->get("description"));
$appointment->setStartDate(
\DateTime::createFromFormat($format, $request->request->get("start_date"))
);
$appointment->setEndDate(
\DateTime::createFromFormat($format, $request->request->get("end_date"))
);
// Don't forget to update the create or update controller with the new field
$repositoryCategories = $em->getRepository("AppBundle:Categories");
// Search in the repository for a category object with the given ID and
// set it as value !
$appointment->setCategory(
$repositoryCategories->find(
$request->request->get("category")
)
);
// Create appointment
$em->persist($appointment);
$em->flush();
return new JsonResponse(array(
"status" => "success"
));
}

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

Заметка

В нашей базе данных таблица категорий содержит только 2 строки, а именно Медицинское назначение и Свободное время.

Планировщик данных из репозитория внутри select

Ссылка на основную публикацию
Adblock
detector