在Symfony 3中使用dhtmlxScheduler创建事件日历(计划程序)

本文概述

  • 要求
  • 1.实现预约实体
  • 2.实现调度程序控制器和路由
  • 3.实现布局和脚本结构
  • 4.编写客户端逻辑
  • 在约会表单中显示来自存储库的数据
计划程序是你在公司的软件产品上不容错过的组件。使用计划程序, 企业(或普通人)将能够计划和跟踪约会, 事件, 任务和其他事物。如我们的Top 5:Best所示, dhtmlx调度程序是最好的调度程序JavaScript库之一, 可让你在应用程序中实现此功能。 dhtmlxScheduler是类似Google的JS事件日历, 具有多种视图和功能。它具有干净的UI和可自定义的外观。
在本文中, 你将学习如何使用Symfony和dhtmlxscheduler在前端和后端创建自己的自定义事件日历(计划程序)。
要求 要创建自己的Scheduler, 你将需要在项目中准备以下库。我们将描述它们需要的内容, 如果你不能包括它们(显然很必要, 则不包括dhtmlx调度程序), 则可以编写自己的后备:
A. dhtmlx调度程序
你将需要dhtmlx调度程序库的副本(.zip文件)。该库提供2个版本, 开放源代码版本(标准版), 你可以通过阅读官方网站上的库文档自行使用;或付费版本(专业版), 在此版本中可获得支持和商业许可。
从源zip文件中, 你仅需要JavaScript代码, 因为后端将完全由Symfony实现。该调度程序非常灵活, 你可以按照自己的方式自定义许多内容, 建议你也阅读文档。你可以在此处下载任何提及的版本。
有了zip文件后, 首先需要创建一个目录来保存库。在本文中, 我们将在Symfony应用程序的/ web目录中创建库文件夹。因此, 源JavaScript将可在yourapplication / web / libraries / dhtmlx上访问。我们不会弄乱下载的zip文件的原始结构, 因此在这种情况下, 你将在dhtmlx中拥有文件夹代码库和示例, 可用于签出示例以稍后使调度程序besser运行。
B.Moment.js
Moment.js的JavaScript主文件需要在yourapplication / web / libraries / momentjs上可访问。如果你不想使用MomentJS库在需要的日期格式化我们的日期(第4步), 则可以通过使用以下代码替换getFormatedEvent来创建后备:
// Retrieve the format date method (that follows the given pattern) from the scheduler libraryvar 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 itif(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进行通信来实现。
此示例的目标是将一些约会类持久保存到数据库(MySql, MongoDB, CouchDB等)。然后, 你的第一项工作是为你的应用程序创建Appointment类。此类可以根据需要进行外观和操作, 因此可以添加任何有用的属性或方法。在此示例中, 我们的实体将从下表生成, 即约会。数据库上的约会表将具有5个字段, 即id(自动递增, 不为null), 标题(文本列), 描述(文本列), start_date(日期时间列)和end_date(日期时间列):
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

这将为Appointment表生成ORM文件, 并在AppBundle / Resources / config / doctrine / Appointments.orm.yml中显示以下结果:
AppBundle\Entity\Appointments:type: entitytable: appointmentsid:id:type: bigintnullable: falseoptions:unsigned: falseid: truegenerator:strategy: IDENTITYfields:title:type: stringnullable: falselength: 255options:fixed: falsedescription:type: textnullable: truelength: 65535options:fixed: falsestartDate:type: datetimenullable: falsecolumn: start_dateendDate:type: datetimenullable: falsecolumn: end_datelifecycleCallbacks: {}

然后, 在orm文件存在后, 你可以使用以下方法自动生成约会实体:
php bin/console doctrine:generate:entities AppBundle

在AppBundle / Entity / Appointments处生成的实体如下所示:
< ?phpnamespace AppBundle\Entity; /** * Appointments */class Appointments{/*** @var integer*/private $id; /*** @var string*/private $title; /*** @var string*/private $description; /*** @var \DateTime*/private $startDate; /*** @var \DateTime*/private $endDate; /*** Get id** @return integer*/public function getId(){return $this-> 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路由上是可访问的, 因此请修改Symfony项目的main routing.yml文件, 并注册另一个为调度程序处理路由的路由文件:
# Create route for scheduler in your appapp_scheduler:resource: "@AppBundle/Resources/config/routing/scheduler.yml"prefix:/scheduler

请注意, 我们会将新的路由文件存储在主捆绑包的config / routing文件夹中。 scheduler.yml路由文件如下:
# app/config/routing.ymlscheduler_index:path:/defaults:{ _controller: AppBundle:Scheduler:index }methods:[GET]scheduler_create:path:/appointment-createdefaults:{ _controller: AppBundle:Scheduler:create }methods:[POST]scheduler_update:path:/appointment-updatedefaults:{ _controller: AppBundle:Scheduler:update }methods:[POST]scheduler_delete:path:/appointment-deletedefaults:{ _controller: AppBundle:Scheduler:delete }methods:[DELETE]

每个路由都由位于AppBundle(我们现在将创建)中的Scheduler Controller中的函数处理。其中只有3个将通过AJAX用于创建, 删除和修改约会。索引路由(你的网站/调度程序)将在浏览器中呈现调度程序。
现在已经注册了路由, 你将需要创建用于处理路由的控制器以及每个路由上的逻辑。由于逻辑根据你处理实体的方式而有所不同, 因此以下控制器显示了如何通过与约会实体一起处理每个事件。所有响应均以JSON格式(索引除外)给出, 以提供有关操作状态的信息:
< ?phpnamespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; // Include the used classes as JsonResponse and the Request objectuse Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; // The entity of your Appointmentuse AppBundle\Entity\Appointments as Appointment; class SchedulerController extends Controller{/*** View that renders the scheduler.**/public function indexAction(){// Retrieve entity manager$em = $this-> 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 schedulerreturn $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 #}< !DOCTYPE html> < html> < head> < meta charset="UTF-8" /> < title> {% block title %}Welcome!{% endblock %}< /title> {% block stylesheets %}{% endblock %}< /head> < body> {% block body %}{% endblock %}{% block javascripts %}{% endblock %}< /body> < /html>

由于你的项目可能会遵循其他模式, 因此请确保将我们将添加的内容包括在你各自的模块中。
然后, 按照控制器中的定义, 我们的scheduler.html.twig文件将位于目录app / resources / views / default中, 因此请确保在上述路径中创建该文件(或在控制器中进行更改)。调度程序的布局将如下所示:
{# default/scheduler.html.twig #}{% extends "base.html.twig" %}{% block stylesheets %}< !-- Include the flat style of the scheduler --> < link rel='stylesheet' type='text/css' href='/uploads/allimg/220521/142F06046-0.jpg asset("libraries/dhtmlx/codebase/dhtmlxscheduler_flat.css") }}' charset="utf-8"/> < !-- If you won't use full screen mode, ignore the following style --> < style type="text/css" media="screen"> html, body{margin:0px; padding:0px; height:100%; overflow:hidden; }< /style> {% endblock %}{% block body -%}< div id="scheduler_element" class="dhx_cal_container" style='width:100%; height:100%; '> < div class="dhx_cal_navline"> < div class="dhx_cal_prev_button"> & nbsp; < /div> < div class="dhx_cal_next_button"> & nbsp; < /div> < div class="dhx_cal_today_button"> < /div> < div class="dhx_cal_date"> < /div> < div class="dhx_cal_tab" name="day_tab" style="right:204px; "> < /div> < div class="dhx_cal_tab" name="week_tab" style="right:140px; "> < /div> < div class="dhx_cal_tab" name="month_tab" style="right:76px; "> < /div> < /div> < div class="dhx_cal_header"> < /div> < div class="dhx_cal_data"> < /div> < /div> {% endblock %}{% block javascripts %}< !-- Include the scheduler library --> < script src='/uploads/allimg/220521/142F06046-0.jpg asset("libraries/dhtmlx/codebase/dhtmlxscheduler.js") }}' type='text/javascript' charset="utf-8"> < /script> < !-- Include jQuery to handle AJAX Requests --> < script src="http://img.readke.com/220521/142F04934-1.jpg"> < /script> < !-- Include Momentjs to play with the dates --> < script src="http://img.readke.com/220521/142F06046-0.jpg asset("libraries/momentjs/moment.js") }}"> < /script> < script> // 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 toowindow.GLOBAL_SCHEDULER_ROUTES = {create: '{{ path("scheduler_create") }}', update: '{{ path("scheduler_update") }}', delete: '{{ path("scheduler_delete") }}'}; < /script> < !-- Include the schedulerScripts that you will need to write in the next step --> < script src='/uploads/allimg/220521/142F06046-0.jpg asset("libraries/schedulerScripts.js") }}' type='text/javascript' charset="utf-8"> < /script> {% endblock %}

在样式表中, 它包括Scheduler的平面样式和一些规则, 以使其在全屏模式下看起来不错。然后, 在代码块主体, Scheduler所需的标记以及JavaScripts代码块中, 我们将按以下顺序包括库:dhtmlxscheduler, 用于AJAX的jQuery, 可轻松操纵日期的MomentJS。
原始脚本标签在窗口(全局)中声明2个变量, 即GLOBAL_APPOINTMENTS和GLOBAL_SCHEDULER_ROUTES。约会对象将索引视图中的约会(请参阅索引控制器以获取更多信息)以JSON格式存储(但在JS中解释为对象), 因此我们需要使用Twig的原始过滤器。路线对象存储Twig生成的路线, 这些路线将用于更新, 创建和删除约会。由于处理调度程序的逻辑将写在另一个JavaScript文件中, 因此我们不能在其中使用twig, 因此建议在可用twig的位置生成它们, 然后使用窗口访问它们。
现在, 我们将编写schedulerScripts.js文件的内容, 该文件将包含用于处理视图中的调度程序逻辑的代码。
4.编写客户端逻辑 对于我们的Scheduler, 我们将允许用户借助对话框(即dhtmlx Scheduler的默认Lightbox)在日历上创建约会。你首先要做的是通过修改调度程序的config对象来配置调度程序的默认行为。至少你需要提供xml_date格式, 其余的纯粹是可选的。
然后配置表单的各个部分以插入和编辑约会。在这种情况下, 因为我们只有2个字段, 即标题和描述, 所以标题将映射到Scheduler的默认文本字段。灯箱上必须存在默认的时间和文本字段, 时间会自动指定开始和结束字段。然后在DIV元素中以某种模式(日, 周或月)初始化调度程序, 并可选地指定调度程序应开始的日期。然后解析从索引控制器返回的事件(所有约会存储在window.GLOBAL_APPOINTMENTS数组中。最后, 你可以附加事件以处理用户对Scheduler的处理。
schedulerScripts.js的代码如下:
// 1. Configure Scheduler Basic Settingsscheduler.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 clickscheduler.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) sectionsscheduler.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 settingsvar initSettings = {// Element where the scheduler will be startedelementId: "scheduler_element", // Date object where the scheduler should be startedstartDate: new Date(), // Start modemode: "week"}; scheduler.init(initSettings.elementId, initSettings.startDate , initSettings.mode); // 4. Parse the initial (From index controller) appointmentsscheduler.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 itif(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关系, 其中一个表即类别, 其结构如下所示:
AppBundle\Entity\Categories:type: entitytable: categoriesid:id:type: bigintnullable: falseoptions:unsigned: falseid: truegenerator:strategy: IDENTITYfields:name:type: stringnullable: falselength: 255options:fixed: falselifecycleCallbacks: {}

类别表的orm文件存在后, 你可以使用以下方法自动生成类别实体:
php bin/console doctrine:generate:entities AppBundle

在AppBundle / Entity / Categories中生成的实体如下所示:
< ?php// AppBundle\Entity\Categories.phpnamespace AppBundle\Entity; /** * Categories */class Categories{/*** @var integer*/private $id; /*** @var string*/private $name; /*** Get id** @return integer*/public function getId(){return $this-> 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: entitytable: appointmentsindexes:category:columns:- categoryid:id:type: bigintnullable: falseoptions:unsigned: falseid: truegenerator:strategy: IDENTITYfields:title:type: stringnullable: falselength: 255options:fixed: falsedescription:type: textnullable: truelength: 65535options:fixed: falsestartDate:type: datetimenullable: falsecolumn: start_dateendDate:type: datetimenullable: falsecolumn: end_datemanyToOne:category:targetEntity: Categoriescascade: {}fetch: LAZYmappedBy: nullinversedBy: nulljoinColumns:category:referencedColumnName: idorphanRemoval: falselifecycleCallbacks: {}

如果再次生成该实体, 它将添加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创建的” 表单” , 因此, 如果要添加选择输入以列出数据库中所有类别的行, 以便你的用户可以选择约会的类别, 你将需要使用与约会相同的方式, 将Categories存储库中的行转换为JSON, 以便可由调度程序处理。
在你的Scheduler Controller中, 创建一个新方法, 将你的Categories格式化为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, 在这里, 将Categories存储库的数据中的JSON结构作为变量发送给twig, 即category:
/** * 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 schedulerreturn $this-> render("default/scheduler.html.twig", ['appointments' => $formatedAppointments, 'categories' => $formatedCategories]); }

现在Twig可以将字符串作为字符串访问类别, 但是对于JavaScript而言尚不可用, 因此你需要在Twig视图中全局公开它, 以便schedulerScripts文件可以访问该类别, 在这种情况下, 我们将通过窗口进行操作。 GLOBAL_CATEGORIES:
{% block javascripts %}< !-- Include the scheduler library --> < script src='/uploads/allimg/220521/142F06046-0.jpg asset("libraries/dhtmlx/codebase/dhtmlxscheduler.js") }}' type='text/javascript' charset="utf-8"> < /script> < !-- Include jQuery to handle AJAX Requests --> < script src="http://img.readke.com/220521/142F04934-1.jpg"> < /script> < !-- Include Momentjs to play with the dates --> < script src="http://img.readke.com/220521/142F06046-0.jpg asset("libraries/momentjs/moment.js") }}"> < /script> < script> // 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 toowindow.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 selectwindow.GLOBAL_CATEGORIES = {{ categories|raw }}; < /script> < !-- Include the schedulerScripts that you will need to write in the next step --> < script src='/uploads/allimg/220521/142F06046-0.jpg asset("libraries/schedulerScripts.js") }}' type='text/javascript' charset="utf-8"> < /script> {% endblock %}

现在, 需要在日历中以约会的形式呈现category对象, 这意味着你需要修改schedulerScripts.js文件并修改定义灯箱部分的步骤2:
// 2. Configure Lightbox (form) sectionsscheduler.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属性将具有此值的事件映射为category属性, 该属性存储一个简单数字, 该数字指示正在使用的类别。你还需要修改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 itif(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 IDcategory: event.category}; }

最后, 你需要处理后端的事件(创建和更新), 以便它们可以成为类别类型的对象, 并且可以保留约会实体:
注意 此修改也需要在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行, 即医疗约会和空闲时间约会。
在Symfony 3中使用dhtmlxScheduler创建事件日历(计划程序)

文章图片
【在Symfony 3中使用dhtmlxScheduler创建事件日历(计划程序)】编码愉快!

    推荐阅读