Newer Version Available

This content describes an older version of this product. View Latest

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}