marshmallow/filament-fullcalendar

A fork of The Most Popular JavaScript Calendar integrated with Filament 💛

v1.7.0 2023-03-09 08:46 UTC

This package is auto-updated.

Last update: 2024-05-01 00:17:28 UTC


README

FullCalendar Widget

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Features

  • Accepts all configurations from FullCalendar
  • Event click and drop events
  • Modals for creating and editing events New in v1.0

Support Filament

filament-logo

Table of contents


Installation

You can install the package via composer:

composer require saade/filament-fullcalendar

You can publish the config file with:

php artisan vendor:publish --tag="filament-fullcalendar-config"

Usage

Since the package does not automatically add the FullCalendarWidget widget to your Filament panel, you are free to extend the widget and customise it yourself.

  1. First, create a Filament Widget:
php artisan make:filament-widget CalendarWidget

This will create a new App\Filament\Widgets\CalendarWidget class in your project.


  1. Your newly created widget should extends the Saade\FilamentFullCalendar\Widgets\FullCalendarWidget class of this package

Warning

Don't forget to remove protected static string $view from the generated class!

<?php

namespace App\Filament\Widgets;

use Saade\FilamentFullCalendar\Widgets\FullCalendarWidget;

class CalendarWidget extends FullCalendarWidget
{
    /**
     * Return events that should be rendered statically on calendar.
     */
    public function getViewData(): array
    {
        return [
            [
                'id' => 1,
                'title' => 'Breakfast!',
                'start' => now()
            ],
            [
                'id' => 2,
                'title' => 'Meeting with Pamela',
                'start' => now()->addDay(),
                'url' => 'https://some-url.com',
                'shouldOpenInNewTab' => true,
            ]
        ];
    }

    /**
     * FullCalendar will call this function whenever it needs new event data.
     * This is triggered when the user clicks prev/next or switches views on the calendar.
     */
    public function fetchEvents(array $fetchInfo): array
    {
        // You can use $fetchInfo to filter events by date.
        return [];
    }
}

Warning

You should use getViewData to display initial data, and fetchEvents to fetch new events while paginating the calendar.

Both methods should retun an array of EventObject.


Configuration

This is the contents of the default config file.

You can use any property that FullCalendar uses on its root object. Please refer to: FullCalendar Docs to see the available options. It supports most of them.

<?php

/**
 * Consider this file the root configuration object for FullCalendar.
 * Any configuration added here, will be added to the calendar.
 * @see https://fullcalendar.io/docs#toc
 */

return [
    'timeZone' => config('app.timezone'),

    'locale' => config('app.locale'),

    'headerToolbar' => [
        'left'   => 'prev,next today',
        'center' => 'title',
        'right'  => 'dayGridMonth,dayGridWeek,dayGridDay'
    ],

    'navLinks' => true,

    'editable' => true,

    'selectable' => false,

    'dayMaxEvents' => true
];

Styling

If you're building a custom Filament theme, you need one more step to make the calendar theme match your custom theme.

Add this line to your resources/css/filament.css file (or whatever file you're using).

@import '../../vendor/saade/filament-fullcalendar/resources/css/filament-fullcalendar.css';

the final contents of this file should look simmilar to this

@import '../../vendor/filament/forms/dist/module.esm.css';
+ @import '../../vendor/saade/filament-fullcalendar/resources/css/filament-fullcalendar.css';
@import 'tippy.js/dist/tippy.css';

@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

Listening for events

The only event-related events supported right now are: EventClick and EventDrop

They're commented out by default so livewire does not spam requests without they being used. You are free to paste them in your CalendarWidget class. See: FiresEvents

Since v1.0.0 we use onEventClick to open the edit modal. If you need to hook up into this event, be sure to call the original method using parent::onEventClick() to keep the modal opening as it should.

/**
 * Triggered when the user clicks an event.
 */
public function onEventClick($event): void
{
    parent::onEventClick($event);

    // your code
}

/**
 * Triggered when dragging stops and the event has moved to a different day/time.
 */
public function onEventDrop($newEvent, $oldEvent, $relatedEvents): void
{
    // your code
}

/**
 * Triggered when event's resize stops.
 */
public function onEventResize($event, $oldEvent, $relatedEvents): void
{
    // your code
}

Creating and Editing events with modals.

Since v1.0.0 you can create and edit events using a modal.

To customise the modal, override the following properties in your widget:

  • protected string $modalWidth
  • protected string $modalLabel

The process of saving and editing the event is up to you, since this plugin does not rely on a Model to save the calendar events.

Creating Events

Events can be created in two ways.

  • Clicking on a day (default)
  • Selecting a date range (click and drag across calendar days) (you need to opt-in for this, set selectable => true in the config file.)

This will open the Create Event modal.

When the create form gets submitted, it will call the createEvent function on your widget. Be sure to add the snippet below to your calendar class.

public function createEvent(array $data): void
{
    // Create the event with the provided $data.
}

If the default form does not fullfills your needs, you can override the getCreateEventFormSchema and use it like a normal Filament form.

protected static function getCreateEventFormSchema(): array
{
    return [
        Forms\Components\TextInput::make('title')
            ->required(),
        Forms\Components\DatePicker::make('start')
            ->required(),
        Forms\Components\DatePicker::make('end')
            ->default(null),
    ];
}

You can override the getCreateEventModalTitle() method to change the modal title to a custom one:

public function getCreateEventModalTitle(): string 
{
    return __('filament::resources/pages/create-record.title', ['label' => $this->getModalLabel()]);
}

You can override the getCreateEventModalSubmitButtonLabel() and getCreateEventModalCloseButtonLabel() methods to change the modal button labels to custom labels:

public function getCreateEventModalSubmitButtonLabel(): string 
{
    return __('filament::resources/pages/create-record.form.actions.create.label');
}

public function getCreateEventModalCloseButtonLabel(): string 
{
    return __('filament::resources/pages/create-record.form.actions.cancel.label');
}

Editing Events

Events can be edited by clicking on an event on the calendar.

This will open the Edit Event modal.

When the edit form gets submitted, it will call the editEvent function on your widget. Be sure to add the snippet below to your calendar class.

public function editEvent(array $data): void
{
    // Edit the event with the provided $data.

    /**
     * here you can access to 2 properties to perform update
     * 1. $this->event_id
     * 2. $this->event
    */

    # $this->event_id
    // the value is retrieved from event's id key
    // eg: Appointment::find($this->event);

    # $this->event
    // model instance is resolved by user defined resolveEventRecord() funtion. See example below
    // eg: $this->event->update($data);

}

// Resolve Event record into Model property
public function resolveEventRecord(array $data): Model
{
    // Using Appointment class as example
    return Appointment::find($data['id']);
}

If the default form does not fullfills your needs, you can override the getEditEventFormSchema and use it like a normal Filament form.

protected static function getEditEventFormSchema(): array
{
    return [
        Forms\Components\TextInput::make('title')
            ->required(),
        Forms\Components\DatePicker::make('start')
            ->required(),
        Forms\Components\DatePicker::make('end')
            ->default(null),
    ];
}

You can override the getEditEventModalTitle() method to change the modal title to a custom one:

public function getCreateEventModalTitle(): string 
{
    return __('filament::resources/pages/create-record.title', ['label' => $this->getModalLabel()]);
}

You can override the getEditEventModalSubmitButtonLabel() and getEditEventModalCloseButtonLabel() methods to change the modal button labels to custom labels:

public function getEditEventModalSubmitButtonLabel(): string 
{
    return __('filament::resources/pages/edit-record.form.actions.save.label');
}

public function getEditEventModalCloseButtonLabel(): string 
{
    return $this->editEventForm->isDisabled()
        ? __('filament-support::actions/view.single.modal.actions.close.label')
        : __('filament::resources/pages/edit-record.form.actions.cancel.label');
}

Authorizing actions

If you want to authorize the view action, you can override the default authorization methods that comes with this package.

public static function canView(?array $event = null): bool
{
    // When event is null, MAKE SURE you allow View otherwise the entire widget/calendar won't be rendered
    if ($event === null) {
        return true;
    }
    
    // Returning 'false' will not show the event Modal.
    return true;
}

If you want to authorize the edit or create action, you can override the default authorization methods that comes with this package.

public static function canCreate(): bool
{
    // Returning 'false' will remove the 'Create' button on the calendar.
    return true;
}

public static function canEdit(?array $event = null): bool
{
    // Returning 'false' will disable the edit modal when clicking on a event.
    return true;
}

If you want to disable all actions or keep the calendar as it was before v1.0.0, you can return false for all the methods above, or use the convenient concern CantManageEvents. It will disable all calendar modals.

class CalendarWidget extends FullCalendarWidget
{
    use CantManageEvents;

    // ...
}

Listening for cancelled modal

If you want to know when a modal has been cancelled, you can add for the following Livewire events to your widgets $listener array:

    protected $listeners = [
        'cancelledFullcalendarCreateEventModal' => 'onCreateEventCancelled',
        'cancelledFullcalendarEditEventModal' => 'onEditEventCancelled',
    ];

Refreshing calendar events

If you want to refresh the calendar events, you can call $this->refreshEvents() inside your widget class. This will call getViewData() and re-render the events on the calendar.

public function yourMethod(): void
{
    $this->refreshEvents();
}

Filtering events based on the calendar view

If you want to filter your events based on the days that are currently shown in the calendar, you can implement the fetchInfo() method from the CanFetchEvents trait. Add the following code to your calendar widget:

/**
 * FullCalendar will call this function whenever it needs new event data.
 * This is triggered when the user clicks prev/next or switches views.
 *
 * @see https://fullcalendar.io/docs/events-function
 * @param array $fetchInfo start and end date of the current view
 */
public function fetchEvents(array $fetchInfo): array
{
    return [];
}

you can filter events based on the timespan $fetchInfo['start'] and $fetchInfo['end'].

example:

public function fetchEvents(array $fetchInfo): array
{
    $schedules = Appointment::query()
        ->where([
            ['start_at', '>=', $fetchInfo['start']],
            ['end_at', '<', $fetchInfo['end']],
        ])
        ->get();

    $data = $schedules->map( ... );

    return $data;
}

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.