marshmallow / filament-fullcalendar
A fork of The Most Popular JavaScript Calendar integrated with Filament 💛
Fund package maintenance!
saade
Requires
- php: ^8.0
- filament/filament: ^2.0
- illuminate/contracts: ^9.0|^10.0
- spatie/laravel-package-tools: ^1.11
Requires (Dev)
- nunomaduro/collision: ^6.0|^7.0
- nunomaduro/larastan: ^1.0|^2.0
- orchestra/testbench: ^7.0|^8.0
- pestphp/pest: ^1.21
- pestphp/pest-plugin-laravel: ^1.1
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- phpunit/phpunit: ^9.5
- spatie/laravel-ray: ^1.26
This package is auto-updated.
Last update: 2024-10-31 00:18:04 UTC
README
Features
- Accepts all configurations from FullCalendar
- Event click and drop events
- Modals for creating and editing events New in v1.0
Support Filament
Table of contents
- Installation
- Usage
- Configuration
- Styling
- Listening for events
- Creating and Editing events with modals.
- Refreshing calendar events
- Filtering events based on the calendar view
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.
- First, create a Filament Widget:
php artisan make:filament-widget CalendarWidget
This will create a new
App\Filament\Widgets\CalendarWidget
class in your project.
- 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, andfetchEvents
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.