I needed an interactable calendar, so I used the new list controller popup mode, removed the list and replaced it with the calendar from fullcalendar.io
An Event model with title, start_at, end_at, all_day properties
| <div class="row"> | |
| <div id='list-calendar' class="col-10 offset-1"></div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function () { | |
| var calendarEl = document.getElementById('list-calendar'); | |
| var calendar = new FullCalendar.Calendar(calendarEl, { | |
| initialView: 'dayGridMonth', | |
| themeSystem: 'bootstrap', | |
| timeZone: 'UTC', | |
| eventSources: [ | |
| '<?= $this->actionUrl('json') ?>' | |
| ], | |
| locale: 'fr', | |
| eventSourceFailure(error) { | |
| if (error.status === 401) { | |
| window.location.reload(); | |
| } | |
| }, | |
| headerToolbar: { | |
| left: '', | |
| center: '', | |
| right: '' | |
| }, | |
| selectable: true, | |
| eventClick: function (info) { | |
| if (info.event.extendedProps.form_record_id) { | |
| oc.listOnLoadForm(info.event.extendedProps.form_record_id); | |
| } | |
| }, | |
| select: function (info) { | |
| $('<a />').popup({ | |
| handler: 'onLoadPopupForm', | |
| extraData: { | |
| 'start-at': info.startStr, | |
| 'end-at': info.endStr | |
| }, | |
| success: function() { | |
| calendar.refetchEvents(); | |
| } | |
| }); | |
| }, | |
| height: 'auto', | |
| editable: true, | |
| eventResizableFromStart: true, | |
| eventDrop: function(info) { | |
| oc.ajax('onMoveEvent', { | |
| data: { | |
| id: info.event.extendedProps.form_record_id, | |
| start_at: info.event.start.toISOString(), | |
| end_at: info.event.end.toISOString() | |
| }, | |
| error: function () {info.revert()}, | |
| success: function () {}, | |
| }); | |
| }, | |
| eventResize: function (info) { | |
| oc.ajax('onMoveEvent', { | |
| data: { | |
| id: info.event.extendedProps.form_record_id, | |
| start_at: info.event.start.toISOString(), | |
| end_at: info.event.end.toISOString() | |
| }, | |
| error: function () {info.revert()}, | |
| success: function () {}, | |
| }); | |
| }, | |
| }); | |
| calendar.render(); | |
| document.getElementById('calendar-prev-button').addEventListener('click', function () { | |
| calendar.prev(); | |
| }) | |
| document.getElementById('calendar-today-button').addEventListener('click', function () { | |
| calendar.today(); | |
| }); | |
| document.getElementById('calendar-next-button').addEventListener('click', function () { | |
| calendar.next(); | |
| }) | |
| document.querySelectorAll('a[data-bs-toggle="tab"]').forEach(e => e.addEventListener('shown.bs.tab', function (event) { | |
| calendar.render(); | |
| }) | |
| ) | |
| document.getElementById('calendar-view-week-button').addEventListener('click', function () { | |
| calendar.changeView('timeGridWeek'); | |
| }) | |
| document.getElementById('calendar-view-month-button').addEventListener('click', function () { | |
| calendar.changeView('dayGridMonth'); | |
| }) | |
| document.getElementById('calendar-view-year-button').addEventListener('click', function () { | |
| calendar.changeView('multiMonthYear'); | |
| }) | |
| addEventListener('ajax:update', function() { | |
| calendar.refetchEvents() | |
| }); | |
| }); | |
| </script> |
| <div data-control="toolbar loader-container"> | |
| <div class="btn-group"> | |
| <button id="calendar-prev-button" class="btn btn-default oc-icon-chevron-left"></button> | |
| <button id="calendar-today-button" class="btn btn-default">Today</button> | |
| <button id="calendar-next-button" class="btn btn-default oc-icon-chevron-right"></button> | |
| </div> | |
| <div class="btn-group"> | |
| <button id="calendar-view-week-button" class="btn btn-default">Week</button> | |
| <button id="calendar-view-month-button" class="btn btn-default">Month</button> | |
| <button id="calendar-view-year-button" class="btn btn-default">Year</button> | |
| </div> | |
| </div> |
| .clickable-event:hover { | |
| cursor: pointer; | |
| } |
| <?php namespace Inetis\Demo\Controllers; | |
| use BackendMenu; | |
| use Backend\Classes\Controller; | |
| use Carbon\Carbon; | |
| use Illuminate\Support\Facades\Response; | |
| use Inetis\Demo\Models\Event; | |
| use Redirect; | |
| class Events extends Controller | |
| { | |
| public $implement = [ | |
| \Backend\Behaviors\FormController::class, | |
| \Backend\Behaviors\ListController::class, | |
| ]; | |
| public $formConfig = 'config_form.yaml'; | |
| public $listConfig = 'config_list.yaml'; | |
| public function __construct() | |
| { | |
| parent::__construct(); | |
| $this->addJs('/plugins/inetis/demo/assets/vendor/fullcalendar-6.1.11/dist/index.global.js'); | |
| $this->addCss('/plugins/inetis/demo/assets/css/calendar.css'); | |
| BackendMenu::setContext('Inetis.Demo', 'demo', 'events'); | |
| } | |
| public function json() | |
| { | |
| $start = Carbon::make(input('start')); | |
| $end = Carbon::make(input('end')); | |
| // Add buffer for smoother navigation | |
| $start = $start->subMonth(); | |
| $end = $end->addMonth(); | |
| $events = Event::where('start_at', '<=', $end) | |
| ->where('end_at', '>=', $start) | |
| ->get(); | |
| return Response::json( | |
| $events->map(function (Event $event) { | |
| return [ | |
| 'start' => $event->start_at->toIso8601String(), | |
| 'end' => $event->end_at->toIso8601String(), | |
| 'title' => $event->title, | |
| 'backgroundColor' => '#ff0000', | |
| 'borderColor' => '#ff0000', | |
| 'form_record_id' => $event->id, | |
| 'classNames' => ['clickable-event'], | |
| 'allDay' => $event->all_day, | |
| ]; | |
| }) | |
| ); | |
| } | |
| public function formExtendFields($form, $fields) | |
| { | |
| if ($form->context == 'create') { | |
| if (input('start-at')) { | |
| $form->addFields([ | |
| 'start_at' => [ | |
| 'label' => 'De', | |
| 'type' => 'datepicker', | |
| 'mode' => 'datetime', | |
| 'span' => 'auto', | |
| 'default' => input('start-at'), | |
| ], | |
| ]); | |
| } | |
| if (input('end-at')) { | |
| $form->addFields([ | |
| 'end_at' => [ | |
| 'label' => 'À', | |
| 'type' => 'datepicker', | |
| 'mode' => 'datetime', | |
| 'span' => 'auto', | |
| 'default' => input('end-at'), | |
| ], | |
| ]); | |
| } | |
| } | |
| } | |
| public function onMoveEvent() | |
| { | |
| if (!input('id') || !input('start_at') || !input('end_at')) { | |
| return false; | |
| } | |
| $event = Event::find(input('id')); | |
| if (!$event) { | |
| return false; | |
| } | |
| $event->start_at = Carbon::make(input('start_at')); | |
| $event->end_at = Carbon::make(input('end_at')); | |
| $event->save(); | |
| return true; | |
| } | |
| public function index_onPopupSave() | |
| { | |
| $this->asExtension('FormController')->index_onPopupSave(); | |
| return ['success' => true]; | |
| } | |
| public function index_onPopupDelete() | |
| { | |
| $this->asExtension('FormController')->index_onPopupDelete(); | |
| return ['success' => true]; | |
| } | |
| } |
| <div class="container-fluid"> | |
| <div class="row"> | |
| <div class="col-12"> | |
| <div class="control-toolbar"> | |
| <?= $this->makePartial('list_toolbar') ?> | |
| </div> | |
| </div> | |
| </div> | |
| <?= $this->makePartial('calendar') ?> | |
| </div> |
@OlinB Nice !! 🤩