We needed a calendar to show information like event information, opportunity closing deadlines, customer verification deadline dates, etc. We can use Salesforce Calendars to show these data when information is available in Salesforce objects. If we have temporary data to show then we need some components.
We have a few limitations in Salesforce Calendars as well like the maximum number of events on the calendar is 150 and we can not hide weekends from it. See all limits here Considerations for Using Events and Calendars in Lightning Experience.
Let us create Lightning Web Component which is reusable and we can use it inside other lightning components as well as in lightning experiences like Lightning App Builder.
We are going to use the FullCalendar Js library to create a reusable lightning web component. The below steps are required to implement this library in LWC.
- Download and add JS library in Static Resource
- Create reusable LWC
- Test reusable LWC
1. Download and add JS library in Static Resource
The latest build of the FullCalendar JS library is not compatible with LWC. We can use versions 3 and 4 to implement it. I have used v3.10.0 and it is available at FullCalendar
Upload this JS library in Static resource with the name fullcalendarv3. It should be available to the public.
2. Create reusable LWC
Let us create a reusable component that will get records/data from the caller component and Lightning App Builder page. For this, we need public properties which will set data in the calendar component.
We can also raise events to be handled by caller components like event click, event selection, etc. In this component, I have raised eventclicked event which will be handled in the child/caller component. Similarly, you can raise other events as well for the caller component. They can handle that event based on their requirement.
Properties events and eventDataString are created for assigning event data in the calendar. Property events is assigning event data in calendar events. This will be used by the caller component to pass array data. As this property is reactive it will update event data whenever events data are changed in the caller component.
Property eventDataString will be used to pass a string into the calendar. This property will convert the string to a JS array and then assign it to the calendar’s event property. This will be used in the Lightning App Builder page to pass event data in string format.
See block code below to better understand programatically
<template>
<div style="background:#ffffff;" class="slds-grid">
<div id="calendar" class="fullcalendarjs"></div>
</div>
</template>
import { LightningElement,api,track } from 'lwc';
import FullCalendarJS from '@salesforce/resourceUrl/fullcalendarv3';
import { loadStyle, loadScript } from 'lightning/platformResourceLoader';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class Calender extends LightningElement {
jsInitialised = false;
@track _events;
@api
get events() {
return this._events;
}
set events(value) {
this._events=[...value];
}
@api
get eventDataString() {
return this.events;
}
set eventDataString(value) {
try
{
this.events=eval(value);
}
catch{
this.events=[];
}
}
renderedCallback() {
// Performs this operation only on first render
if (this.jsInitialised) {
return;
}
this.jsInitialised = true;
Promise.all([
loadScript(this, FullCalendarJS + '/FullCalenderV3/jquery.min.js'),
loadScript(this, FullCalendarJS + '/FullCalenderV3/moment.min.js'),
loadScript(this, FullCalendarJS + '/FullCalenderV3/fullcalendar.min.js'),
loadStyle(this, FullCalendarJS + '/FullCalenderV3/fullcalendar.min.css')
])
.then(() => {
this.initialiseCalendarJs();
})
.catch(error => {
alert(error);
new ShowToastEvent({
title: 'Error!',
message: error,
variant: 'error'
})
})
}
initialiseCalendarJs() {
var that=this;
const ele = this.template.querySelector('div.fullcalendarjs');
//Use jQuery to instantiate fullcalender JS
$(ele).fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
defaultDate: new Date(),
navLinks: true,
editable: true,
eventLimit: true,
events: this.events,
dragScroll:true,
droppable:true,
weekNumbers:true,
selectable:true,
//eventClick: this.eventClick,
eventClick: function (info) {
const selectedEvent = new CustomEvent('eventclicked', { detail: info.Id });
// Dispatches the event.
that.dispatchEvent(selectedEvent);
}
});
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Generic Calender</masterLabel>
<targets>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__AppPage, lightning__HomePage, lightning__RecordPage">
<property name="eventDataString" type="String" />
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
targetConfig is required to pass data from the lightning app builder so create it in the meta.xml file.
3. Test reusable LWC
As we have built our reusable LWC component, let us consume this component. As mentioned, We can consume this component from another component as well as the lightning app builder page.
1. Test by using it in another LWC
Let us consume created calendar component in another component which is showing event records. Based on our requirement we can retrieve data from any object and show it on the calendar.
LWC Code
<template>
<c-calender events={events} oneventclicked={handleEvent}></c-calender>
<!-- Open a modal with event data -->
<template if:true={openModal}>
<div data-modal="custom-modal" class="modalclass">
<section
role="dialog"
tabindex="-1"
aria-labelledby="modal-heading-01"
aria-modal="true"
aria-describedby="modal-content-id-1"
class="slds-modal slds-fade-in-open">
<div class="slds-modal__container">
<header class="slds-modal__header">
<lightning-button-icon icon-name="utility:close"
class="slds-modal__close "
alternative-text="Close"
title="Close"
size="large"
variant="bare-inverse"
onclick={handleCancel} >
</lightning-button-icon>
</header>
<div class="slds-modal__content slds-p-around_medium"
id="modal-content-id-1">
<lightning-input label="Title" name="title" type="text" required value={title} onkeyup={handleKeyup}></lightning-input>
<lightning-input label="Start Date" name="start" type="datetime" required value={startDate}></lightning-input>
<lightning-input label="End Date" name="end" type="datetime" required value={endDate}></lightning-input>
</div>
<footer class="slds-modal__footer">
<lightning-button-group>
<lightning-button label="Close" title="Close" icon-name="utility:close" onclick={handleCancel}></lightning-button>
</lightning-button-group>
</footer>
</div>
</section>
<div class="slds-backdrop slds-backdrop_open"></div>
</div>
</template>
</template>
import { LightningElement,track,wire } from 'lwc';
import getEvents from '@salesforce/apex/EventController.getEvents';
export default class CalenderTest extends LightningElement {
@track startDate=new Date();
@track endDate;
error;
openModal = false;
@track events=[];
@wire(getEvents)
eventObj(value){
const {data, error} = value;
if(data){
//format as fullcalendar event object
let records = data.map(event => {
return { Id : event.Id,
title : event.Subject,
start : event.StartDateTime,
end : event.EndDateTime,
allDay : event.IsAllDayEvent};
});
this.events = JSON.parse(JSON.stringify(records));
this.error = undefined;
}else if(error){
this.events = [];
this.error = 'No events are found';
}
}
handleEvent(event) {
var id=event.detail;
let task = this.events.find(x=>x.Id=id);
this.startDate=task.start;
this.title=task.title;
this.endDate=task.end;
this.openModal = true;
}
handleCancel(event) {
this.openModal = false;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Calender Test</masterLabel>
<targets>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>
Apex Code
public class EventController {
@AuraEnabled(cacheable=true)
public static List<Event> getEvents() {
return [SELECT Id, Subject, StartDateTime, IsAllDayEvent, EndDateTime
FROM Event
ORDER BY CreatedDate DESC
LIMIT 100];
}
}
I am getting event data using LWC apex, these data can be taken from any object. After retrieving apex data create a JS array in the format of FullCalendar event data.
2. Test by using it on Lightning App Builder Page
We can add the above-created generic component to the lightning app builder age and put calendar events as string. This will render event detail on the component. This will be useful when you have fixed events for any work. Below is a sample JSON string.
[
{
title: 'Front-End Conference',
start: '2022-09-16',
end: '2022-09-18'
},
{
title: 'Hair stylist with Mikes',
start: '2022-09-20',
allDay: true
},
{
title: 'Car mechanic',
start: '2022-09-14T09:00:00',
end: '2022-09-14T11:00:00'
},
{
title: 'Dinner with Garav',
start: '2022-09-21T19:00:00',
end: '2022-09-21T22:00:00'
},
{
title: 'Chillout',
start: '2022-09-15',
allDay: true
}
]
Add this JSON string into the eventStringData property to show events as a calendar.