forceCalendar
Salesforce

LWC Integration

Lightning Web Component wrapper -- @api properties, @wire, lifecycle, and event handling.

Component Definition

The forceCalendar LWC wraps forcecal-main and bridges it with Salesforce.

@api Properties

PropertyTypeDefaultDescription
currentViewstring'month'Initial view type
heightstring'600px'Calendar height
recordIdstringnullRelated record ID for context-filtered events
readOnlybooleanfalseDisable event creation/editing
<c-force-calendar
  current-view="week"
  record-id={recordId}
  height="700px"
  read-only
></c-force-calendar>

@api Methods

MethodDescription
refreshEvents()Force reload events from Salesforce
addEvent(event)Programmatically add an event
setView(view)Change the view
goToDate(date)Navigate to a date
// From a parent component
this.template.querySelector('c-force-calendar').refreshEvents();
this.template.querySelector('c-force-calendar').setView('week');

Reactive Data Loading

Events are loaded via @wire:

import getEvents from '@salesforce/apex/ForceCalendarController.getEvents';

@wire(getEvents, {
  startDateTime: '$startDate',
  endDateTime: '$endDate',
  recordId: '$recordId'
})
wiredEvents({ error, data }) {
  if (data) {
    this.events = data;
    this.updateCalendarEvents();
  } else if (error) {
    this.error = error;
  }
}

The $startDate and $endDate reactive properties are updated when the user navigates, triggering automatic data refresh.

Dynamic Element Creation

Due to Locker Service restrictions on static ES module imports of custom elements, the LWC creates forcecal-main dynamically:

renderedCallback() {
  if (this.calendarInitialized) return;

  const container = this.template.querySelector('.calendar-container');
  if (!container) return;

  // Create the element dynamically to bypass static analysis
  const calendarEl = document.createElement('forcecal-main');
  calendarEl.setAttribute('view', this.currentView);
  calendarEl.setAttribute('height', this.height);

  // Attach event listeners
  calendarEl.addEventListener('calendar-navigate', (e) => {
    this.handleNavigate(e.detail);
  });
  calendarEl.addEventListener('calendar-event-add', (e) => {
    this.handleEventAdd(e.detail);
  });
  // ... more listeners

  container.appendChild(calendarEl);
  this.calendarElement = calendarEl;
  this.calendarInitialized = true;
}

The lwc:dom="manual" directive on the container allows direct DOM manipulation:

<div class="calendar-container" lwc:dom="manual"></div>

Event Handling

When the user navigates in the calendar:

handleNavigate(detail) {
  const { date, view } = detail;
  // Update wire parameters to trigger new data fetch
  this.startDate = this.getViewStart(date, view);
  this.endDate = this.getViewEnd(date, view);
}

CRUD Operations

Event creation, update, and deletion use imperative Apex calls:

import createEvent from '@salesforce/apex/ForceCalendarController.createEvent';

async handleEventAdd(detail) {
  try {
    const result = await createEvent({
      title: detail.event.title,
      startDateTime: detail.event.start,
      endDateTime: detail.event.end,
      isAllDay: detail.event.allDay || false,
      description: detail.event.description || '',
      location: detail.event.location || '',
    });
    // Refresh to pick up the new event
    this.refreshEvents();
  } catch (error) {
    this.showError(error);
  }
}

Error Handling

The component displays errors using SLDS styling:

<template if:true={error}>
  <div class="slds-notify slds-notify_alert slds-alert_error" role="alert">
    <span class="slds-assistive-text">error</span>
    <span class="slds-icon_container slds-icon-utility-error slds-m-right_x-small"></span>
    <h2>{errorMessage}</h2>
    <button class="slds-button slds-button_neutral" onclick={handleRetry}>
      Retry
    </button>
  </div>
</template>

Template Structure

<template>
  <div class="slds-card">
    <!-- Loading spinner -->
    <template if:true={isLoading}>
      <lightning-spinner alternative-text="Loading" size="medium">
      </lightning-spinner>
    </template>

    <!-- Error display -->
    <template if:true={error}>
      <!-- Error alert with retry -->
    </template>

    <!-- Calendar container (manual DOM) -->
    <div class="calendar-container" lwc:dom="manual"></div>

    <!-- Refresh button -->
    <div class="slds-p-around_x-small">
      <lightning-button
        label="Refresh"
        onclick={handleRefresh}
        icon-name="utility:refresh"
      ></lightning-button>
    </div>
  </div>
</template>