Note: This release is in preview. Features described here don’t become generally available until the latest general availability date that Salesforce announces for this release. Before then, and where features are noted as beta, pilot, or developer preview, we can’t guarantee general availability within any particular time frame or at all. Make your purchase decisions only on the basis of generally available products and features.

CalendarService Example

Here’s a basic example of a Lightning web component that displays calendar events and allows the user to perform basic calendar-related functions.

The component’s HTML template is minimal, with a “main” display view that lists calendar events and a “detail” display view that shows an event’s details.

1<template>
2     <!-- Main View -->
3     <template if:false={detailViewIsOpen}>
4         <lightning-card title="Today's Events" icon-name="utility:dayview">
5             <template for:each={todayEvents} for:item="item">
6                 <div class="slds-var-p-horizontal_medium slds-var-p-vertical_x-small" key={item.id} onclick={showDetailView} data-id={item.id}>
7                     <p class="slds-text-heading_small"><b>{item.title}</b></p>
8                     <p class="slds-text-heading_small">{item.startTimeDisplay} — {item.endTimeDisplay}</p>
9                 </div>
10             </template>
11         </lightning-card>
12     </template>
13         
14         <!-- Detail View -->
15     <template if:true={detailViewIsOpen}>
16         <div class="slds-var-p-around_medium" style="background-color: white; border-radius: 4px;">
17             <table>
18                 <tbody>
19                     <tr>
20                         <td class="slds-align-top" width="1">
21                             <lightning-icon icon-name="utility:chevronleft" onclick={hideDetailView}></lightning-icon>
22                         </td>
23                         <td class="sldx-align-top slds-text-heading_medium slds-var-p-bottom_medium slds-align_absolute-center">Event Details</td>
24                         <td class="slds-align-top" width="1">
25                             <lightning-button-menu alternative-text="Show menu" variant="border-filled" menu-alignment="auto">
26                                 <lightning-menu-item prefix-icon-name="utility:add" value="Add" label="Add to Device Calendar" onclick={addCalendarEvent}></lightning-menu-item>
27                                 <lightning-menu-item prefix-icon-name="utility:edit" value="Update" label="Update in Device Calendar" onclick={updateCalendarEvent}></lightning-menu-item>
28                                 <lightning-menu-item prefix-icon-name="utility:delete" value="Delete" label="Remove from Device Calendar" onclick={deleteCalendarEvent}></lightning-menu-item>
29                             </lightning-button-menu>
30                         </td>
31                     </tr>
32                     <tr>
33                         <td colspan="3">
34                             <ul class="slds-has-dividers_bottom-space">
35                                 <li class="slds-item">
36                                     <span class="slds-text-heading_small"><b>{selectedItem.title}</b></span><br>
37                                     <span class="slds-text-heading_small">{selectedItem.startTimeDisplay} — {selectedItem.endTimeDisplay}</span>
38                                 </li>
39                                 <li class="slds-item">
40                                     <span class="slds-text-heading_small">Reminders</span><br>
41                                         <template for:each={selectedItem.alarmsDisplay} for:item="alarm">
42                                             <span class="slds-text-body_regular" key={alarm}>{alarm}<br></span>
43                                         </template>
44                                 </li>
45                                 <li class="slds-item">
46                                     <span class="slds-text-heading_small">Location</span><br>
47                                     <span class="slds-text-body_regular">{selectedItem.location}</span>
48                                 </li>
49                                 <li class="slds-item">
50                                     <span class="slds-text-heading_small">Attendees</span><br>
51                                         <template for:each={selectedItem.attendees} for:item="attendee">
52                                             <span class="slds-text-body_regular" key={attendee.name}>{attendee.name} ({attendee.email})<br></span>
53                                         </template>
54                                 </li>
55                                 <li class="slds-item">
56                                     <span class="slds-text-heading_small">Notes</span><br>
57                                     <span class="slds-text-body_regular">{selectedItem.notes}</span>
58                                 </li>
59                             </ul>
60                         </td>
61                     </tr>
62                 </tbody>
63             </table>
64             </div>
65         </template>
66     </template>

This example simply uses CalendarService to display events, and allows you to perform simple actions on calendar items. A status message is returned when there’s an error. In this example, the events are hard-coded, rather than fetched via API calls from a Salesforce org. You’ll need to build functionality to fetch event data from your Salesforce org as part of your component.

1import { api, LightningElement } from 'lwc';
2import { getCalendarService } from 'lightning/mobileCapabilities';
3import LightningAlert from 'lightning/alert'
4import LightningConfirm from 'lightning/confirm';
5
6export default class CalendarForToday extends LightningElement {
7
8    todayEvents = [];
9    detailViewIsOpen = false;
10    selectedItem = null;
11    selectedItemIndex = -1;
12    calendarPermissionRationaleText = "Allow access to your calendar to enable calendar event processing.";
13    calendarService;
14
15    connectedCallback() {
16        console.log("Start connected callback");
17
18        try {
19            this.calendarService = getCalendarService();
20            this.todayEvents = this.getTodayEvents();
21            this.todayEvents.forEach(item => this.generateDisplayFields(item));
22            console.log(`End connected callback with ${this.todayEvents.length} events for today.`);
23        } catch (err) {
24            console.log(`connectedCallback failed with error: ${err}`);
25        }
26    }
27
28    showDetailView(event) {
29        const id = event.currentTarget.dataset.id;
30        this.selectedItemIndex = this.todayEvents.findIndex(item => item.id === id);
31        if (this.selectedItemIndex != -1) {
32            this.selectedItem = this.todayEvents[this.selectedItemIndex];
33        } else {
34            this.selectedItem = null;
35        }
36        this.detailViewIsOpen = this.selectedItem != null;
37    }
38
39    hideDetailView() {
40        this.detailViewIsOpen = false;
41        this.selectedItem = null;
42        this.selectedItemIndex = -1;
43    }
44
45    addCalendarEvent() {
46        if (this.calendarService.isAvailable() && this.selectedItemIndex != -1 && this.selectedItem) {
47            const options = {
48                "permissionRationaleText" : this.calendarPermissionRationaleText
49            };
50
51            console.log(`options: ${JSON.stringify(options)}`);
52            console.log(`Adding selectedItem: ${JSON.stringify(this.selectedItem)}`);
53
54            this.calendarService.addEvent(this.selectedItem, options)
55            .then((sanitizedEvent) => {
56                this.generateDisplayFields(sanitizedEvent);
57                this.selectedItem = sanitizedEvent;
58                this.todayEvents[this.selectedItemIndex] = sanitizedEvent;
59                this.showSuccessAlert("Add Event", "Event was added successfully to the device default calendar.");
60                console.log(`sanitizedEvent: ${JSON.stringify(sanitizedEvent)}`);
61            })
62            .catch((error) => {
63                console.error(error);
64                this.showFailureAlert("Add Event", `There was a problem adding the event to the device default calendar: ${error.message}`);
65            });
66        } else {
67            console.log("Calendar Service Is Not Available");
68            this.showFailureAlert("Add Event", "Calendar Service is not available.");
69        }
70    }
71
72    updateCalendarEvent() {
73        if (this.calendarService.isAvailable() && this.selectedItemIndex != -1 && this.selectedItem) {
74
75            // For this sample code, we've hard-coded some trivial changes
76
77            this.selectedItem.title += " - Updated";
78            this.selectedItem.notes += " - Updated";
79
80            const options = {
81                "permissionRationaleText" : this.calendarPermissionRationaleText,
82                "span" : "ThisEvent"
83            };
84
85            console.log(`options: ${JSON.stringify(options)}`);
86            console.log(`Updating selectedItem: ${JSON.stringify(this.selectedItem)}`);
87
88            this.calendarService.updateEvent(this.selectedItem, options)
89            .then((sanitizedEvent) => {
90                this.generateDisplayFields(sanitizedEvent);
91                this.selectedItem = sanitizedEvent;
92                this.todayEvents[this.selectedItemIndex] = sanitizedEvent;
93                this.showSuccessAlert("Update Event", "Event was updated successfully in the device default calendar.");
94                console.log(`sanitizedEvent: ${JSON.stringify(sanitizedEvent)}`);
95            })
96            .catch((error) => {
97                console.error(error);
98                this.showFailureAlert("Update Event", `There was a problem updating the event in the device default calendar: ${error.message}`);
99            });
100        } else {
101            console.log("Calendar Service Is Not Available");
102            this.showFailureAlert("Update Event", "Calendar Service is not available.");
103        }
104    }
105
106    deleteCalendarEvent() {
107        if (this.calendarService.isAvailable() && this.selectedItemIndex != -1 && this.selectedItem) {
108            LightningConfirm.open(
109                {
110                    label: "Delete Event",
111                    message: "Are you sure you want to delete this event?",
112                    theme: "warning"
113                }
114            ).then((response) => {
115                if (response === true) {
116                    const options = {
117                        "permissionRationaleText" : this.calendarPermissionRationaleText,
118                        "span" : "ThisEvent"
119                    };
120
121                    console.log(`options: ${JSON.stringify(options)}`);
122                    console.log(`Deleting selectedItem: ${JSON.stringify(this.selectedItem)}`);
123
124                    this.calendarService.removeEvent(this.selectedItem, options)
125                    .then(() => {
126                        this.todayEvents.splice(this.selectedItemIndex, 1);
127                        this.hideDetailView();
128                        this.showSuccessAlert("Delete Event", "Event was removed successfully from the device default calendar.");
129                    })
130                    .catch((error) => {
131                        console.error(error);
132                        this.showFailureAlert("Delete Event", `There was a problem removing the event from the device default calendar: ${error.message}`);
133                    });
134                }
135            });
136        } else {
137            console.log("Calendar Service Is Not Available");
138            this.showFailureAlert("Delete Event", "Calendar Service is not available.");
139        }
140    }
141
142    getTodayEvents() {
143        // For this sample code, we've hard-coded some made-up values.
144        // Your component should fetch events from your SF org and convert them to the following object format.
145        const events = [
146            {
147                id: "event_id_1", // will be overwritten after a call to calendarService.addEvent()
148                isAllDay: false,
149                startDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(8, 0, 0), // 8 AM
150                endDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(10, 0, 0), // 10 AM
151                availability: "Busy",
152                status: "Confirmed",
153                calendarId: null, // will be assigned to the default device calendar
154                title: "Team Meeting",
155                location: "3514 Ruckman Road, San Francisco, CA 94105",
156                notes: "Discussing customer request for new calendar feature",
157                alarms: [{relativeOffsetSeconds: 600}], // 10 mins before event
158                attendees: [
159                    { name: "Jamal Booker", email: "jbooker_fake_email@email.com", role: "Required", status: "Accepted" },
160                    { name: "Robert Bullard", email: "bob.bullard.fake.email@email.com", role: "Required", status: "Pending" },
161                    { name: "Gordon Chu", email: "gordonchu736251_email@email.com", role: "Optional", status: "Declined" },
162                ],
163                recurrenceRules: null
164            },
165            {
166                id: "event_id_2", // will be overwritten after a call to calendarService.addEvent()
167                isAllDay: false,
168                startDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(10, 30, 0), // 10:30 AM
169                endDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(11, 0, 0), // 11 AM
170                availability: "Busy",
171                status: "Confirmed",
172                calendarId: null, // will be assigned to the default device calendar
173                title: "Quarterly Review",
174                location: "2135 Alpha Avenue, Fernandina Beach, FL 32034",
175                notes: "Reviewing results of Q2 and planning Q3",
176                alarms: [{relativeOffsetSeconds: 1800}], // 30 mins before event
177                attendees: [
178                    { name: "Alex Driskel", email: "adriskell_fake_email@email.com", role: "Required", status: "Accepted" },
179                    { name: "Kim Friedman", email: "nothing_is_kimpossible_fake_email@email.com", role: "Required", status: "Tentative" },
180                    { name: "April Guthman", email: "guthman.april.fake@email.com", role: "Required", status: "Pending" },
181                    { name: "Leif Hansen", email: "leifhansenfakeemail@email.com", role: "Required", status: "Accepted" },
182                ],
183                recurrenceRules: null
184            },
185            {
186                id: "event_id_3", // will be overwritten after a call to calendarService.addEvent()
187                isAllDay: false,
188                startDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(11, 0, 0), // 11 AM
189                endDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(12, 0, 0), // 12 PM
190                availability: "Busy",
191                status: "Confirmed",
192                calendarId: null, // will be assigned to the default device calendar
193                title: "Portfolio Checklist",
194                location: "2281 Radford Street, Louisville, KY 40291",
195                notes: "Creating a guide to help sales in compiling a strong portfolio",
196                alarms: [{relativeOffsetSeconds: 600}], // 10 mins before event
197                attendees: [
198                    { name: "Marie Hill", email: "marieriefake@email.com", role: "Required", status: "Accepted" },
199                    { name: "Foua Khang", email: "khanghangemail@email.com", role: "Required", status: "Accepted" },
200                    { name: "Mindy Lee", email: "mindyfakeemaillee@email.com", role: "Required", status: "Accepted" },
201                ],
202                recurrenceRules: null
203            },
204            {
205                id: "event_id_4", // will be overwritten after a call to calendarService.addEvent()
206                isAllDay: false,
207                startDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(12, 30, 0), // 12:30 PM
208                endDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(13, 30, 0), // 1:30 PM
209                availability: "Tentative",
210                status: "Tentative",
211                calendarId: null, // will be assigned to the default device calendar
212                title: "Lunch with Jennifer West",
213                location: "171 2nd St, San Francisco, CA 94105",
214                notes: "Bring latest version of contract",
215                alarms: [{relativeOffsetSeconds: 1800}], // 30 mins before event
216                attendees: [
217                    { name: "Awanasa Locklear", email: "this_is_awanasa_fake@email.com", role: "Required", status: "Tentative" },
218                    { name: "Elena Nieto", email: "elenasfakeemail@email.com", role: "Required", status: "Tentative" },
219                ],
220                recurrenceRules: null
221            },
222            {
223                id: "event_id_5", // will be overwritten after a call to calendarService.addEvent()
224                isAllDay: false,
225                startDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(14, 30, 0), // 2:30 PM
226                endDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(15, 30, 0), // 3:30 PM
227                availability: "Tentative",
228                status: "Tentative",
229                calendarId: null, // will be assigned to the default device calendar
230                title: "Sales Workgroup",
231                location: "3270 Armbrester Drive, Gardena, CA 90248",
232                notes: "Discuss the new customer opportunities",
233                alarms: [{relativeOffsetSeconds: 1800}], // 30 mins before event
234                attendees: [
235                    { name: "Raul Nieto", email: "raul.fake.nieto@email.com", role: "Required", status: "Accepted" },
236                    { name: "Salome Ofodu", email: "salomesalomefakeemail@email.com", role: "Required", status: "Tentative" },
237                    { name: "Justus Pardo", email: "justuspardofake@email.com", role: "Required", status: "Tentative" },
238                    { name: "Gorav Patel", email: "gpatel.fake.email@email.com", role: "Required", status: "Pending" },
239                ],
240                recurrenceRules: null
241            },
242            {
243                id: "event_id_6", // will be overwritten after a call to calendarService.addEvent()
244                isAllDay: false,
245                startDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(15, 30, 0), // 3:30 PM
246                endDateSecondsUTC: this.getTodayTimestampAtTimeOfDay(17, 30, 0), // 5:30 PM
247                availability: "Busy",
248                status: "Confirmed",
249                calendarId: null, // will be assigned to the default device calendar
250                title: "Executive Team",
251                location: "56 Main Street, Seattle, WA 98119",
252                notes: "Report on Q2 sales and new leads",
253                alarms: [{relativeOffsetSeconds: 600}, {relativeOffsetSeconds: 3600}], // 10 mins and 1 hour before event
254                attendees: [
255                    { name: "Florentina Perez", email: "florperfakeemail@email.com", role: "Required", status: "Accepted" },
256                    { name: "Harryette Randall", email: "doubleletteremailfake@email.com", role: "Required", status: "Accepted" },
257                    { name: "Sofia Rivera", email: "fakeemailforsofie@email.com", role: "Required", status: "Accepted" },
258                ],
259                recurrenceRules: null
260            }
261        ];
262
263        return events;
264    }
265
266    getTodayTimestampAtTimeOfDay(hours, minutes, seconds) {
267        let d = new Date();
268        d.setHours(hours, minutes, seconds, 0);
269        return d.getTime() / 1000; // milliseconds to seconds
270    }
271
272    timeOfDayToString(dateSecondsUTC) {
273        let d = new Date(dateSecondsUTC * 1000); // seconds to milliseconds
274        let ampm = "AM";
275        let str = "";
276
277        if (d.getHours() > 12) {
278            ampm = "PM";
279            str += `${d.getHours() - 12}`;
280        } else {
281            str += `${d.getHours()}`;
282        }
283
284        if (d.getMinutes() > 0) {
285            if (d.getMinutes() < 10) {
286                str += `:0${d.getMinutes()}`;
287            } else {
288                str += `:${d.getMinutes()}`;
289            }
290        }
291
292        str += ` ${ampm}`;
293
294        return str;
295    }
296
297    alarmsToString(alarms) {
298        let results = [];
299
300        if (alarms) {
301            alarms.forEach(alarm => {
302                if (alarm.relativeOffsetSeconds == 0) {
303                    results.push("At time of event");
304                } else {
305                    const mins = parseInt(Math.ceil(Math.abs(alarm.relativeOffsetSeconds) / 60.0));
306                    if (mins == 1) {
307                        results.push("1 minute before");
308                    } else if (mins < 60) {
309                        results.push(`${mins} minutes before`);
310                    } else {
311                        const hours = parseInt(Math.ceil(mins / 60.0));
312                        if (hours == 1) {
313                            results.push("1 hour before");
314                        } else if (hours < 24) {
315                            results.push(`${hours} hours before`);
316                        } else {
317                            const days = parseInt(Math.ceil(hours / 24.0));
318                            if (days == 1) {
319                                results.push("1 day before");
320                            } else {
321                                results.push(`${days} days before`);
322                            }
323                        }
324                    }
325                }
326            });
327        } else {
328            results.push("None");
329        }
330
331        return results;
332    }
333
334    generateDisplayFields(calendarEvent) {
335        // these are used for display purpose only
336        calendarEvent.startTimeDisplay = this.timeOfDayToString(calendarEvent.startDateSecondsUTC);
337        calendarEvent.endTimeDisplay = this.timeOfDayToString(calendarEvent.endDateSecondsUTC);
338        calendarEvent.alarmsDisplay = this.alarmsToString(calendarEvent.alarms);
339    }
340
341    showSuccessAlert(title, message) {
342        LightningAlert.open(
343            {
344                message: message,
345                theme: "success",
346                label: title,
347            }
348        );
349    }
350
351    showFailureAlert(title, message) {
352        console.log(`calendarService.isAvailable(): ${this.calendarService.isAvailable()}`);
353        console.log(`selectedItemIndex: ${this.selectedItemIndex}`);
354        console.log(`selectedItem: ${this.selectedItem.id}`);
355        
356        LightningAlert.open(
357            {
358                message: message,
359                theme: "error", // a red theme intended for error states
360                label: title,
361            }
362        );
363    }
364}