Newer Version Available
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}