you need to wire:ignore on the dom node that holds the calendar
fullcalendar disappears when using Livewire wire:poll #7741
I am working on a project that make use of fullcalendar for appointments. What I'm trying to accomplish is to keep the events in the calendar uptodate.
I decided to use livewire which makes things easy. Everything is working fine. But becausue I am using wire:poll in my livewire component, the contents of the component get's refreshed to keep things updated all the time.
The problem is, immediately the component refreshes, fullcalendar completely vanishes from the page.
ofcourse other data on the page gets updated without an issue. How do I solve this. What am I doing wrong. Or is there a better way to handle this?.
Here my Code:
Livewire component class:
<?php
namespace Modules\Visitor\Livewire;
use Livewire\Component;
use Illuminate\Support\Carbon;
use Modules\Visitor\app\Models\Appointment;
class FrontendCalendar extends Component
{
public $events;
public function render()
{
$today_appointments = Appointment::where('status', 0)->whereDate('created_at','>=', Carbon::today())->latest()->get();
$pending_appointments = Appointment::where('status', 0)->whereDate('created_at','<', Carbon::today())->latest()->get();
$appointments_events = Appointment::where('status', 0)->latest()->get()->map(function($item){
return [
'id' => $item->id,
'title' => $item->name,
'start' => format_date($item->start_date),
'end' => format_date($item->end_date),
'extendedProps' => [
'calendar' => $item->color ?? 'primary',
'fullname' => $item->name,
'phone' => $item->phone,
'email' => $item->email,
'address' => $item->address,
'gender' => $item->gender,
'purpose' => $item->purpose,
'status' => $item->status,
'start_date' => $item->start_date,
'end_date' => $item->end_date,
'color' => $item->color?? 'primary',
]
];
});
$this->dispatch('refreshCalender', ($appointments_events));
return view('visitor::livewire.frontend-calendar',compact(
'today_appointments','pending_appointments','appointments_events'
));
}
}
Livewire view: livewire.frontend-calendar
<div wire:poll.keep-alive>
<div class="card app-calendar-wrapper">
<div class="row g-0">
<!-- Calendar -->
<div class="col app-calendar-content">
<div class="card shadow-none border-0 border-start rounded-0">
<div class="card-body pb-0">
<!-- FullCalendar -->
<div id="calendar"></div>
</div>
</div>
<div class="app-overlay"></div>
<!-- FullCalendar Offcanvas -->
@include('visitor::offcanvas')
</div>
<!-- /Calendar -->
<!-- Calendar Sidebar -->
<div class="col app-calendar-sidebar pt-1" id="app-calendar-sidebar">
{{-- <div class="p-3 pb-2 my-sm-0 mb-3">
<div class="d-grid">
<button class="btn btn-primary btn-toggle-sidebar" data-bs-toggle="offcanvas" data-bs-target="#addEventSidebar" aria-controls="addEventSidebar">
<i class="mdi mdi-plus me-1"></i>
<span class="align-middle">Add Appointment</span>
</button>
</div>
</div> --}}
<div class="p-4">
@if (!empty($today_appointments) && ($today_appointments->count() > 0))
<h4>Today</h4>
<hr class="container-m-nx my-4">
<ul style="overflow-y: scroll;height: 200px;" class="p-0 m-0">
@foreach ($today_appointments as $item)
<li class="d-flex shadow-sm p-1 rounded mb-4">
<div class="avatar flex-shrink-0 me-3">
@if (substr($item->gender,0,1) == 'M')
<img src="{{asset('assets/img/avatars/1.png')}}" alt="avatar" class="rounded">
@else
<img src="{{asset('assets/img/avatars/8.png')}}" alt="avatar" class="rounded">
@endif
</div>
<div class="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div class="me-2">
<h6 class="mb-0 fw-semibold text-nowrap"><small><span class="badge bg-label-success rounded-pill"><i class="tf-icons mdi mdi-circle mdi-14px"></i></span> <span>{{$item->name}}</span></small></h6>
<small class="text-muted">
<i class="mdi mdi-calendar-blank-outline mdi-14px"></i>
<span>{{$item->start_date}} |{{$item->end_date}}</span>
</small>
<p>{{$item->purpose}}</p>
</div>
</div>
</li>
<hr>
@endforeach
</ul>
<hr class="container-m-nx my-4">
@endif
@if (!empty($pending_appointments) && ($pending_appointments->count() > 0))
<h4>Upcoming</h4>
<hr class="container-m-nx my-4">
<ul style="overflow-y: scroll;height: 300px; overflow-x:hidden" class="p-0 m-0">
@foreach ($pending_appointments as $item)
<li class="d-flex shadow-sm p-1 rounded mb-4">
<div class="avatar flex-shrink-0 me-3">
@if (substr($item->gender,0,1) == 'M')
<img src="{{asset('assets/img/avatars/1.png')}}" alt="avatar" class="rounded">
@else
<img src="{{asset('assets/img/avatars/8.png')}}" alt="avatar" class="rounded">
@endif
</div>
<div class="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div class="me-2">
<h6 class="mb-0 fw-semibold text-nowrap"><small><span class="badge bg-label-success rounded-pill"><i class="tf-icons mdi mdi-circle mdi-14px"></i></span> <span>{{$item->name}}</span></small></h6>
<small class="text-muted">
<i class="mdi mdi-calendar-blank-outline mdi-14px"></i>
<span>{{$item->start_date}} |{{$item->end_date}}</span>
</small>
<p>{{$item->purpose}}</p>
</div>
</div>
</li>
<hr>
@endforeach
</ul>
@endif
</div>
</div>
<!-- /Calendar Sidebar -->
</div>
</div>
</div>
@push('page-script')
<script>
'use strict';
document.addEventListener('DOMContentLoaded', function () {
(function () {
let calendarEl = document.getElementById('calendar'),
appCalendarSidebar = document.querySelector('.app-calendar-sidebar'),
offcanvasAddAppointment = document.getElementById('addEventSidebar'),
appOverlay = document.querySelector('.app-overlay'),
offcanvasTitle = document.querySelector('.offcanvas-title'),
btnToggleSidebar = document.querySelector('.btn-toggle-sidebar'),
btnSubmit = document.querySelector('button[type="submit"]'),
btnCancel = document.querySelector('.btn-cancel'),
selectAll = document.querySelector('.select-all'),
inlineCalendar = document.querySelector('.inline-calendar');
let eventToUpdate, inlineCalInstance;
const bsoffcanvasAddAppointment = new bootstrap.Offcanvas(offcanvasAddAppointment);
// Inline sidebar calendar (flatpicker)
if (inlineCalendar) {
inlineCalInstance = inlineCalendar.flatpickr({
monthSelectorType: 'static',
inline: true
});
}
function eventClick(info) {
eventToUpdate = info.event;
var appointment_id = appointment_id;
bsoffcanvasAddAppointment.show();
if (offcanvasTitle) {
offcanvasTitle.innerHTML = 'Update Appointment';
}
btnSubmit.innerHTML = 'Update';
btnSubmit.classList.add('btn-update-event');
btnSubmit.classList.remove('btn-add-event');
var data = eventToUpdate.extendedProps;
$('#appointment_id').val(eventToUpdate.id);
$('#fullname').val(data.fullname);
$('#phone').val(data.phone);
$('#email').val(data.email);
$('#address').val(data.address);
$('#gender').val(data.gender);
$('#purpose').val(data.purpose);
$('#startDate').val(data.start_date);
$('#endDate').val(data.end_date);
$('#status').val(data.status).trigger('change');
}
function modifyToggler() {
const fcSidebarToggleButton = document.querySelector('.fc-sidebarToggle-button');
if (fcSidebarToggleButton) {
fcSidebarToggleButton.classList.remove('fc-button-primary');
fcSidebarToggleButton.classList.add('d-lg-none', 'd-inline-block', 'ps-0');
while (fcSidebarToggleButton.firstChild) {
fcSidebarToggleButton.firstChild.remove();
}
fcSidebarToggleButton.setAttribute('data-bs-toggle', 'sidebar');
fcSidebarToggleButton.setAttribute('data-overlay', '');
fcSidebarToggleButton.setAttribute('data-target', '#app-calendar-sidebar');
fcSidebarToggleButton.insertAdjacentHTML('beforeend', '<i class="mdi mdi-menu mdi-24px text-body"></i>');
}
}
let eventsData = @json($appointments_events)
Livewire.on('refreshCalender', (e) => {
eventsData = { ...e }[0]
console.log(eventsData);
})
let calendar = new Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: eventsData,
plugins: [dayGridPlugin, interactionPlugin, listPlugin, timegridPlugin],
editable: true,
dragScroll: true,
eventResizableFromStart: true,
customButtons: {
sidebarToggle: {
text: 'Sidebar'
}
},
headerToolbar: {
start: 'sidebarToggle, prev,next, title',
end: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
loading: function(isLoading) {
if (!isLoading) {
this.getEvents().forEach(function(e){
if (e.source === null) {
e.remove();
}
});
}
},
initialDate: new Date(),
navLinks: true, // can click day/week names to navigate views
eventClassNames: function ({ event: calendarEvent }) {
const colorName = calendarEvent._def.extendedProps.color;
// Background Color
return ['fc-event-' + colorName];
},
dateClick: function (info) {
let date = moment(info.date).format('YYYY-MM-DD');
bsoffcanvasAddAppointment.show();
if (offcanvasTitle) {
offcanvasTitle.innerHTML = 'Add Appointment';
}
btnSubmit.innerHTML = 'Add';
btnSubmit.classList.remove('btn-update-event');
btnSubmit.classList.add('btn-add-event');
},
eventClick: function (info) {
eventClick(info);
},
datesSet: function () {
modifyToggler();
},
viewDidMount: function () {
modifyToggler();
}
});
calendar.render();
// Modify sidebar toggler
modifyToggler();
// Hide left sidebar if the right sidebar is open
if (btnToggleSidebar) {
btnToggleSidebar.addEventListener('click', e => {
if (offcanvasTitle) {
offcanvasTitle.innerHTML = 'Add Appointment';
}
btnSubmit.innerHTML = 'Add';
btnSubmit.classList.remove('btn-update-event');
btnSubmit.classList.add('btn-add-event');
appCalendarSidebar.classList.remove('show');
appOverlay.classList.remove('show');
});
}
if(inlineCalInstance){
inlineCalInstance.config.onChange.push(function (date) {
calendar.changeView(calendar.view.type, moment(date[0]).format('YYYY-MM-DD'));
modifyToggler();
appCalendarSidebar.classList.remove('show');
appOverlay.classList.remove('show');
});
}
})();
});
</script>
@endpush
Main page that displays the calendar
@extends('layouts.layoutMaster')
@section('title', 'Appointments - Calendar View')
@section('vendor-style')
<link rel="stylesheet" href="{{asset('assets/vendor/libs/fullcalendar/fullcalendar.css')}}" />
<link rel="stylesheet" href="{{asset('assets/vendor/libs/flatpickr/flatpickr.css')}}" />
@endsection
@section('page-style')
<link rel="stylesheet" href="{{asset('assets/vendor/css/pages/app-calendar.css')}}" />
@endsection
@section('content')
<livewire:visitor::frontend-calendar />
@endsection
@section('vendor-script')
<script src="{{asset('assets/vendor/libs/fullcalendar/fullcalendar.js')}}"></script>
<script src="{{asset('assets/vendor/libs/flatpickr/flatpickr.js')}}"></script>
<script src="{{asset('assets/vendor/libs/moment/moment.js')}}"></script>
@endsection
I should mention that, the @Push('page-script') is rendered under the @section('vendor-script') in the page. Thanks in advance I've also tried adding wire:ignore to the parent div of my calendar div . This prevented the calendar from disappearing but the events in the calendar didn't update whiles the other cards beside the calendar got updated
Please or to participate in this conversation.