0

I'm using fullcalendar JS (6.0.1) with livewire on a laravel application (8.75). I have a calendar with some events.

When I click on event, a modal window opens with a form filled with informations like title, description, start date and allDay on inputs. Inputs are enabled and I can update events with a submit button or delete it with another button.

Update and deletion features work.

When I click on a day, the same modal window opens with an empty form (and deletion button doesn't appear of course). I can create an event by submitting this form, it works

My problem is that after a creation or an update, the modal is closed and I return to the calendar. My new event appears (or the modifications I've made) on it (time and title) but if I click on it to view the details, the form on the modal is empty for an event creation (no title, description, start date etc) ans empty for data I've just changed for an update.It's the same if I click on any other events on the calendar (events that have been created previously). I have to refresh the page to view the details when I click on it.

When I logged info.event on eventClick() I have all correct event data even if I just changed something. And datas shows on the calendar are correct too.

So I supposed that it's due to my modal form filled feature but I can't put my finger on it.

Here is my livewire component :

<div>
    <div>
        <div id='calendar-container' wire:ignore>
            <div id='calendar' class="z-10"></div>
        </div>
    </div>
    @push('scripts')
    <script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.6.0/main.min.js'></script>
    <script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.6.0/locales-all.min.js"></script>
    <script>
        document.addEventListener('livewire:load', function () {
            const Calendar = FullCalendar.Calendar;
            const calendarEl = document.getElementById('calendar');
            const calendar = new Calendar(calendarEl, {

                                    headerToolbar: {
                                        left: 'prev,next today',
                                        center: 'title',
                                        right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
                                    },

                                    locale: '{{config('app.locale')}}',

                                    events: JSON.parse(@this.events),

                                    editable: true,

                                    eventResize: info => @this.eventChange(info.event),

                                    eventDrop: info => @this.eventChange(info.event),

                                    selectable: true,

                                    unselectAuto: true,

                                    longPressDelay: 1,

                                    eventLongPressDelay: 1,

                                    selectLongPressDelay: 1,

                                    select: arg => {
                                        arg.jsEvent.preventDefault();

                                        // Select the modal
                                        let modal = document.getElementById('modal')

                                        // Empty inputs 
                                        modal.querySelectorAll('input').forEach((el) => {
                                            el.setAttribute('value', '')
                                        })
                                        
                                        // Unselect checkbox
                                        modal.querySelector('input[id="allDay"]').removeAttribute('checked')
                                        
                                        // If deletion button is present, hide it
                                        let deleteBtn = modal.querySelector('#delete')
                                        if(!deleteBtn.hasAttribute('hidden')) {
                                            deleteBtn.setAttribute('hidden', true)
                                        }

                                        // Fill the start date with the selected date
                                    modal.querySelector('input[id="start"]').setAttribute('value', (new Date((arg.start).toString().split('GMT')[0]+' UTC').toISOString()).substr(0,23))
                                        
                                        // Change modal title
                                        modal.querySelector('#modal-title').innerText = 'Create an event'
                                        
                                        // If modal is hidden
                                        if(modal.hasAttribute('hidden')) {
                                            // Display it
                                            modal.removeAttribute('hidden')
                                        }
                                        
                                        // On submitting form
                                        modal.querySelector('button[id="submitCreatingEventForm"]').addEventListener('click', (evt) => {
                                            evt.preventDefault()
                                            // evt.stopImmediatePropagation()
                                            
                                            if(title && start) {
                                                // allDay definition
                                                let allD = allDay.checked ? true : false

                                                // Adding event on db
                                                @this.eventAdd({
                                                    title: title.value,
                                                    description: description.value,
                                                    start: start.value,
                                                    end: end.value,
                                                    allDay: allD
                                                });

                                                // Adding event on calendar
                                                calendar.addEvent({
                                                    title: title.value,
                                                    description: description.value,
                                                    start: start.value,
                                                    end: end.value,
                                                    allDay: allD
                                                });
                                                
                                            }

                                        })
                                        
                                        // @this.render()
                                        // Close modal button
document.getElementById('close').addEventListener('click', (evt) => {
                                            document.getElementById('modal').setAttribute('hidden', true)
                                            @this.render()
                                            // Unselect the selection
                                            calendar.unselect();

                                        })
                                        // Unselect the selection
                                        calendar.unselect();
                                        calendar.render();
                                    },

                                    eventClick: info => {
                                        info.jsEvent.preventDefault();
                                        console.log(info.event);
                                        // Select the modal
                                        let modal = document.getElementById('modal')

                                        // Empty inputs 
                                        modal.querySelectorAll('input').forEach((el) => {
                                            el.setAttribute('value', '')
                                        })
                                        // Unselect checkbox
                                        modal.querySelector('input[id="allDay"]').removeAttribute('checked')
                                        
                                        let deleteBtn = modal.querySelector('#delete')
                                        // If deletion button is hidden
                                        if(deleteBtn.hasAttribute('hidden')) {
                                            // Display it
                                            deleteBtn.removeAttribute('hidden') 
                                        } 
                                        // If a report has already been create, disabled the deletion button
                                        if(info.event.extendedProps.report) {
                                            deleteBtn.setAttribute('disabled', '')
                                        } else {
                                            deleteBtn.removeAttribute('disabled')
                                        }
                                        // deletion button
                                        deleteBtn.addEventListener('click', (evt) => {
                                                evt.preventDefault()
                                                // Remove event form db
                                                @this.eventRemove(info.event.id)
                                                // remove event from calendar
                                                info.event.remove()
                                            })

                                        // Modify modal title
                                        modal.querySelector('#modal-title').innerText = 'Update an event'


                                        // If modal is hidden
                                        if(modal.hasAttribute('hidden')) {
                                            // Display it
                                            modal.removeAttribute('hidden')
                                        }

                                        // Fill the form inputs
                                        // Title
                                        modal.querySelector('input[id="title"]').setAttribute('value', info.event.title)

                                        // Description
                                        if(info.event.extendedProps.description != null) {
                                            modal.querySelector('input[id="description"]').setAttribute('value', info.event.extendedProps.description)
                                        }

                                        // Start date
                                        modal.querySelector('input[id="start"]').setAttribute('value', (new Date((info.event.start).toString().split('GMT')[0]+' UTC').toISOString()).substr(0,23))

                                        // allDay
                                        if(info.event.allDay) {
                                            modal.querySelector('input[id="allDay"]').setAttribute('checked', true)
                                        }

                                        // On form submitting
                                        modal.querySelector('button[id="submitCreatingEventForm"]').addEventListener('click', (evt) => {
                                            evt.preventDefault()

                                            const e = info.event

                                            if(title && start) {
                                                // Modify calendar event object
                                                if(title.value != e.title) {
                                                    e.setProp('title', title.value)
                                                }
                                                if(description.value != e.extendedProps.description) {
                                                    e.setExtendedProp( 'description', description.value )
                                                }
                                                if(start.value != (new Date((e.start).toString().split('GMT')[0]+' UTC').toISOString()).substr(0,23)) {
                                                    e.setStart(start.value)
                                                }
                                                if(allDay.checked != e.allDay) {
                                                    e.setAllDay(allDay.checked)
                                                }

                                                // Modify db event
                                                @this.eventUpdate({
                                                    id: e.id,
                                                    title: title.value,
                                                    description: description.value,
                                                    start: start.value,
                                                    end: end.value,
                                                    allDay: allDay.checked
                                                });
                    
                                                @this.render()

                                            }
                                        // deletion button
modal.querySelector('button[id="delete"]').addEventListener('click', (evt) => {
                                            // Remove event on calendar
                                            calendar.remove(e)
                                            // Remove event on db
                                            $this.eventRemove(e.id)
                                        })
                                        })
                                        // close modal button
document.getElementById('close').addEventListener('click', (evt) => {
                                            document.getElementById('modal').setAttribute('hidden', true)
                                            calendar.render()
                                        })

                                    }
                                });
            calendar.render();
        
        });


    </script>
    <link href='https://cdn.jsdelivr.net/npm/fullcalendar@5.6.0/main.min.css' rel='stylesheet' />
    @endpush
    <div id="modal" hidden>
        <div>
            <form>
                <div>
                    <h1 id="modal-title">Créer un événement</h1>
                    
                    <button id="submitCreatingEventForm" type="submit" >Enregistrer</button>
                </div>

                <div>
                    <div>
                        <label for="title">
                            Titre *
                        </label>
                        <input id="title" type="text">
                    </div>
                </div>
                <div>
                    <div>
                        <label for="description">
                            Description
                        </label>
                        <input id="description" type="text">
                    </div>
                </div>
                <div>
                    <div>
                        <label for="start">
                            Début *
                        </label>
                        <input id="start" type="datetime-local" name="start">
                    </div>
                    <div>
                        <label for="allDay">Journée entière</label>
                        <input id="allDay" type="checkbox" name="allDay" value="true">
                    </div>
                    <div hidden>
                        <label for="end">
                            Fin
                        </label>
                        <input id="end" type="datetime-local" name="end">
                    </div>
                </div>
            </form>    
            <div id="modal-buttons">
                <button id="close">Fermer</button>
                <button id="delete">Supprimer</button>
            </div>
        </div>
    </div>
</div>

Do you see anything that could be causing this?

Here is my Calendar component :

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Event;
use App\Models\Client;
use Illuminate\Support\Arr;

class Calendar extends Component
{
    public $events = [];

    public function mount($events)
    {
        $this->events = json_encode($events);
    }

    public function render()
    {
        return view('livewire.calendar');
    }

    public function eventChange($event)
    {
        $e = Event::find($event['id']);
        $e->start = $event['start'];
        if(Arr::exists($event, 'end')) {
            $e->end = $event['end'];
        }
        $e->save();
    }
    
    public function eventAdd($event) 
    {
        Event::create($event);
        
    }

    public function eventRemove($id)
    {
        Event::destroy($id);
    }

    public function eventUpdate($event)
    {
        $e = Event::find($event['id']);
        $e->title = $event['title'];
        $e->description = $event['description'];
        $e->start = $event['start'];
        $e->end = $event['end'];
        $e->allDay = $event['allDay'];

        if($e->isDirty()) {
            $e->update($event);
        }
    }

    public function reload() {
        return redirect(request()->header('Referer'));
    }
}

Despite my research, I still can't find the solution.

Does anyone have a clue on it?

Petitemo
  • 85
  • 2
  • 10
  • As per [ask] please focus the question on _one_ problem at a time, and provide a [mre] of that issue rather than a massive code dump (e.g. things like your CSS are probably not relevant). You can ask other questions about additional problems. – ADyson Jan 06 '23 at 13:01
  • 1
    Anyway it sounds like the cause of at least one of your issues is registering multiple event listeners against the same event (I can also see that this will happen by glance at the code, because you are adding new event listeners inside `eventClick` and inside `select`), so then every time you click on the button or element to which the listener is added, it will execute _all_ listeners attached to that element, not just the most recent one. – ADyson Jan 06 '23 at 13:03
  • 1
    To resolve that you can either try to remove previous listeners with [removeEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) or change your strategy so that you only ever attach one listener to the button/element, when the page is first loaded. – ADyson Jan 06 '23 at 13:04
  • As you recommended @ADyson I remove the css to clarify the code. I try using removeEventListener according to your advice but problems persists. I'm actually wondering how to take out of select and eventClick my listeners... – Petitemo Jan 06 '23 at 13:54
  • What did you try in relation to the removeEventListener? It can be tricky to use it, but the answers at https://stackoverflow.com/questions/4386300/javascript-dom-how-to-remove-all-event-listeners-of-a-dom-object should help you resolve such problems. – ADyson Jan 06 '23 at 13:59
  • 1
    I finally managed to use removeEventListener() by decompose my listeners and using callbacks functions instead of inner functions. Then I add evt.stopImmediatePropagation() on the top of my callback functions to submit forms (for creation and update) and I no longer have multiple creations. Thanks @ADyson for your advices! – Petitemo Jan 06 '23 at 15:41
  • There remains the problem of the rendering of the modifications on the modals after creation/modification. I will make another question to separate it as advise by ADyson. – Petitemo Jan 06 '23 at 15:43
  • You should add your solution fully as an Answer in the box below, and remove the additional content from the question, so you can create another question. That will make both your question and answer more likely to receive search hits and upvotes from people who find it useful in a similar scenario. Glad you solved it. – ADyson Jan 06 '23 at 15:46

1 Answers1

1

By using removeEventListener() at each of my listeners called into select and eventClick methods and adding evt.stopImmediatePropagation() on the top of my callback functions to submit forms (for creation and update) I no longer have multiple creations.

To finish and to avoid unecessary eventlisteners called on select and eventClick methods I factorize the listener to close the modal outside the calendar object (because nothing on this function use calendar properties)


select: arg => {
[...]
let submitCreationBtn = modal.querySelector('button[id="submitCreatingEventForm"]')
submitCreationBtn.addEventListener('click', handleSubmitCreationForm)

function handleSubmitCreationForm(evt) {
    evt.preventDefault()
    
    evt.stopImmediatePropagation()
    
    if(title && start) {
        
        let allD = allDay.checked ? true : false

        @this.eventAdd({
            title: title.value,
            description: description.value,
            start: start.value,
            end: end.value,
            allDay: allD
        });

        calendar.addEvent({
            title: title.value,
            description: description.value,
            start: start.value,
            end: end.value,
            allDay: allD
        });
        
    }
    submitCreationBtn.removeEventListener('click', handleSubmitCreationForm)
}

[...]
}
eventClick: info => {
[...]
let submitUpdateBtn = modal.querySelector('button[id="submitCreatingEventForm"]')
submitUpdateBtn.addEventListener('click', handleSubmitUpdateForm)
function handleSubmitUpdateForm(evt) {
    evt.preventDefault()
    evt.stopImmediatePropagation()

    const e = info.event

    if(title && start) {
        if(title.value != e.title) {
            e.setProp('title', title.value)
        }
        if(description.value != e.extendedProps.description) {
            e.setExtendedProp( 'description', description.value )
        }
        if(start.value != (new Date((e.start).toString().split('GMT')[0]+' UTC').toISOString()).substr(0,23)) {
            e.setStart(start.value)
        }
        if(allDay.checked != e.allDay) {
            e.setAllDay(allDay.checked)
        }

        @this.eventUpdate({
            id: e.id,
            title: title.value,
            description: description.value,
            start: start.value,
            end: end.value,
            allDay: allDay.checked
        });

        @this.render()
    }
    submitUpdateBtn.removeEventListener('click', handleSubmitUpdateForm)

}
[...]
}

<script>
[...]
    let closeBtn = document.getElementById('close')
    closeBtn.addEventListener('click', handleClickOnCloseBtn)
    function handleClickOnCloseBtn(evt) {
        document.getElementById('modal').setAttribute('hidden', true)
    }
[...]
</script>
Petitemo
  • 85
  • 2
  • 10