Using Bryntum Scheduler Pro as a Salesforce Lightning Web Component
Salesforce Lightning Web Components (LWC) is a modern framework for building web applications on the Salesforce platform using standard web technologies like JavaScript, HTML, and CSS.
In this tutorial, we’ll create a location-based work task scheduler using Bryntum Scheduler Pro in Salesforce. We’ll do the following:
- Create custom objects in Salesforce to store the scheduler tasks and resources data.
- Create a Bryntum Scheduler Pro Lightning web component.
- Customize the Bryntum Scheduler Pro task editor by adding a custom address search field that uses the Nominatim API.
- Add the Bryntum Scheduler Pro Lightning web component to a Salesforce Lightning page.
- Get data from the custom objects into the Bryntum Scheduler Pro.
- Save Bryntum Scheduler Pro updates to the custom objects.
Prerequisites
If you don’t have a Salesforce account or want a Salesforce Platform playground, sign up for the Salesforce Developer Edition, a free, full-featured copy of the Salesforce Platform.
You’ll need to install the Salesforce CLI to create a Lightning web component and deploy it to your Salesforce organization. Install the CLI from here. Visual Studio Code (VS Code) is the recommended IDE for Salesforce development. Install the recommended VS Code extension: Salesforce Extension Pack. Please be aware that the extension pack relies on the presence of Java on your system. For guidance on configuring Java JDK with VS Code, you can find more information here.
We’ll create a Salesforce scratch organization to develop and test the Bryntum Scheduler Pro LWC. A scratch org is a disposable deployment of Salesforce code and metadata. Note that you should not use personal data in a scratch org. The Salesforce CLI and scratch orgs are part of the Salesforce Developer Experience (DX) product suite. To create a scratch org, you’ll need to log in to your Salesforce developer environment and enable Dev Hub features in your org.
Create a Salesforce DX project
Follow these steps to create a Salesforce DX project:
- Create a base directory for your project.
- In VS Code, open the command palette by pressing Ctrl+Shift+P (Windows) or Cmd+Shift+P (macOS).
- Type
SFDX
and selectSFDX: Create Project
. - Select the default Standard Project Template.
- Give your project a name, for example,
BryntumSchedulerPro
. - Select the base directory you created to store your project in.
Your Salesforce DX project will include various folders and files for developing with Salesforce, such as .sfdx
, config
, and force-app
.
Authorize a Dev Hub
Open the VS Code command palette, then type and select SFDX: Authorize a Dev Hub
. Use the default alias. The Salesforce login page will open in a new browser window. Log in using your Salesforce Developer Edition account. Click the “Allow” button when prompted to allow the Salesforce CLI access to your org. The Salesforce CLI remembers your credentials once you’ve authenticated your Dev Hub in the browser.
Create a scratch org
In the VS Code command palette, type and select the following:
SFDX: Create a Default Scratch Org
You can choose the defaults when prompted.
Now, open the VS Code command palette again, then type and select the following:
SFDX: Open Default Org
In the Salesforce Platform UI, do the following:
- Click on the “Setup gear” icon on the top right and click “Setup” in the popup menu.
- In the “Quick Find” input on the top left, search for “My Domain” and select it.
- Under My Domain Details, copy the “Current My Domain URL”.
In your Salesforce code in VS Code, open the sfdx-project.json
file and set sfdcLoginUrl
to your current My Domain URL.
Create the static resources for the Bryntum Scheduler Pro Lightning web component
The Bryntum Scheduler Pro LWC will need the library code for the Bryntum Scheduler Pro. We’ll upload the library code to Salesforce as static resources accessible to your Bryntum Scheduler Pro LWC.
The resources required for the Bryntum Scheduler Pro LWC are:
- JavaScript bundle for the Bryntum Scheduler Pro LWC.
- Bryntum Scheduler Pro LWC CSS files.
- Bryntum Scheduler Pro locales.
- Bryntum Scheduler Pro fonts.
To get these static resources, download the free trial version distribution folder of the Bryntum Scheduler Pro here. If you have already bought the licensed version, you can log in here to download the Bryntum Scheduler Pro distribution folder.
In the /force-app/main/default/staticresources
folder in your Salesforce DX project, create a static resource metadata file named bryntum_schedulerpro.resource-meta.xml
, and add the following lines of code to it:
<?xml version="1.0" encoding="UTF-8"?>
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
<cacheControl>Private</cacheControl>
<contentType>application/zip</contentType>
</StaticResource>
In the /force-app/main/default/staticresources
folder, create a folder called bryntum_schedulerpro
.
Now copy the following files and folders from the /build
folder in the Bryntum Scheduler Pro distribution folder:
fonts
locales
schedulerpro.lwc.module.js
schedulerpro.stockholm.css
Add them to your /force-app/main/default/staticresources/bryntum_schedulerpro
folder.
Add the Bryntum Scheduler Pro static resources to your Salesforce org by right-clicking on the staticresources
folder in the VS Code Explorer pane and selecting:
SFDX: Deploy Source to Org
In your Salesforce scratch org, click the setup gear icon on the top right and click “Setup” in the popup menu. Go to the “Quick Find” search input and search for “Static Resources”. You should see a folder named “bryntum_schedulerpro” in the table.
Create a custom object in Salesforce to store resources data
We’ll create two custom Salesforce objects to store our tasks and resources data. Let’s create the custom object for resources first.
Import resources data from a CSV file
We’ll create most of the fields for the custom objects and add some initial data by importing data from spreadsheets, as this is faster than creating each field manually in the Salesforce UI.
In your Salesforce scratch org:
- Open “Setup” by clicking the gear icon on the top right of the page.
- Navigate to the “Object Manager” tab.
- At the top of this tab, right-click the “Create” button.
- In the dropdown menu, click “Custom Object from Spreadsheet”.
- In the new tab, click the “Login with Sandbox” button.
- In the login form, click “Use Custom Domain”. Use the domain URL you added to the
sfdcLoginUrl
property in yoursfdx-project.json
file.
To get the username for your scratch org, run the following command in your VS Code terminal:
sf org:list --all
To generate a password for the username, run the following command:
sf org generate password --target-org <alias>
Replace <alias>
with your scratch org alias.
Once you’ve been authenticated, copy and paste the following data into a bryntum-schedulerpro-resources-data.csv
file on your computer:
Name, Role, Event Color
James Smith, Service technician, #3183fe
Lisa van Wyk, IT technician, #0076f8z
Jane Hansen, Field technician, #9e25c3
Mark May, Plumber, #2055a5
Peter Freeman, Electrician, #1fba5e
Kate Davies, IT technician, #fab007
The headings in this CSV text represent some of the fields for a Bryntum Scheduler Pro resource.
In the “SALESFORCE FIELD TYPE” column, change the field types as follows:
- Name:
Text
- Event Color:
Text
- Role:
Text
Click the “Next” button at the bottom right.
Set the labels and API name for the custom resources object
For the Object Properties, set “Label” to bryntum-schedulerpro-resources-data
, “Plural Label” to bryntum-schedulerpro-resources-data
, and “API Name” to bryntum_schedulerpro_resources_data
. Click the “Finish” button at the bottom right.
Once the object is created, go to the “Object Manager” tab in “Setup” and search for your new custom object using the “Quick Find” input at the top right of the “Object Manager” tab. You’ll notice that the API Name has a __c
added to the end. This indicates that it’s a custom object.
Create a custom object in Salesforce to store tasks data
Now we’ll create a custom object for the tasks.
Import tasks data from a CSV file
Create a new custom object from a spreadsheet in the same way you created the resources custom object. The CSV file you use should be named bryntum-schedulerpro-tasks-data.csv
and contain the following CSV values:
Name, Read Only, Time Zone, Draggable, Resizable, Children, All Day, Duration, Duration Unit, Start Date, Exception Dates, Recurrence Rule, Cls, Event Color, Event Style, Icon Cls, Style, Preamble, Postamble, Address, Percent Done, Effort, Effort Unit
Fix server, 0, , 0, , , 0, 2.0, h, 2024-12-15T08:30:00Z, , , , , , , , 10min, 10min, "{}", 2, 2 ,hour
Unblock drain, 0, , 0, , , 0, 2.0, h, 2024-12-15T14:00:00Z, , , , , , , , 1h, 30min, "{}", 0, 0 , hour
Update software, 0, , 0, , , 0, 3.0, h, 2024-12-15T11:00:00Z, , , , , , , , 20min, 20min, "{}", 0, 0 , hour
Inspect cables, 0, , 0, , , 0, 3.0, h, 2024-12-15T15:00:00Z, , , , , , , , 1h, 1h, "{}", 0, 0 , hour
Maintenance, 0, , 0, , , 0, 3.0, h, 2024-12-15T12:00:00Z, , , , , , , , 20min, 30min, "{}", 0, 0 , hour
Repair cables, 0, , 0, , , 0, 2.5, h, 2024-12-15T11:30:00Z, , , , , , , , 30min, 30min, "{}", 0, 0 , hour
The headings in this CSV text represent fields for a Bryntum Scheduler Pro event (or “task”). In the “SALESFORCE FIELD TYPE” column, change the field types as follows:
- Name:
Text
- Read Only:
Integer
- Time Zone:
Text
- Draggable:
Integer
- Resizable:
Text
- Children:
Text
- All Day:
Integer
- Duration:
Decimal
- Duration Unit:
Text
- StartDate:
Date/Time
- Exception Dates:
Text
- Recurrence Rule:
Text
- Cls:
Text
- Event Color:
Text
- Event Style:
Text
- Icon Cls:
Text
- Style:
Text
- Preamble:
Text
- Postamble:
Text
- Address:
Text
- Percent Done:
Decimal
- Effort:
Decimal
- Effort Unit:
Text
Set the labels and API name for the custom tasks object
For the Object Properties, set “Label” to bryntum-schedulerpro-tasks-data
, “Plural Label” to bryntum-schedulerpro-tasks-data
, and “API Name” to bryntum_schedulerpro_tasks_data
.
Add the resource ID field as a lookup field to connect tasks to resources.
We’ll now add the resource ID field as a lookup field. The special lookup field type allows you to connect two Salesforce objects. The resource ID field connects a task to a resource. Follow these steps to create the lookup field:
- In the Object Manager tab in Setup, search for the
bryntum-schedulerpro-tasks-data
object using the “Quick Find” input at the top right of the tab. You’ll notice that the API Name has a__c
added to the end. This indicates that it’s a custom object. - In the “Fields and Relationships” tab on the left, click the “New” button.
- Select the data type “Lookup Relationship” and click “Next”.
- In the “Related To” dropdown list, select “bryntum_schedulerpro_resources_data”, and click “Next”.
- Set the “Field Label” to
Resource Id
and the “Field Name” toResource_Id
. - Click “Next” three times and then click “Save”.
Link example tasks to resources using the Salesforce UI
You can link the example tasks to resources using the Salesforce UI by doing the following:
- Click on the “App Launcher” icon at the top left of the page and search for “bryntum-schedulerpro-tasks-data”. A table containing the tasks data you imported from the CSV file will open.
- Click on the “Recently Viewed” dropdown list at the top left and select “All Records”.
- Click the dropdown arrow button at the end of one of the task rows and select “Edit” in the popup menu.
- In the “Resource Id” field, select the resource the task will link to. In this case, the resource is a person. You’ll need to input the resource ID to select a resource. You can find the resource IDs in the “bryntum-schedulerpro-resources-data” custom object.
Add the custom resources and tasks objects to your Salesforce DX project
In your VS Code terminal, run the following command to add your custom tasks object to your Salesforce DX project:
sf project retrieve start -m CustomObject:bryntum_schedulerpro_tasks_data__c
Now run the following command to add your custom resources object to your Salesforce DX project:
sf project retrieve start -m CustomObject:bryntum_schedulerpro_resources_data__c
Create a Bryntum Scheduler Pro Lightning web component
Now let’s create a Bryntum Scheduler Pro LWC. We’ll define some custom classes for the Bryntum Scheduler Pro, including one to add a custom address search field to the task editor. We’ll also add a custom event renderer to the task bar.
Create a basic Lightning web component skeleton
Open the VS Code command palette, then type and select SFDX: Create Lightning Web Component
. Enter schedulerpro
as the name of the new component. Press Enter to accept the default directory: /force-app/main/default/lwc
. This creates a schedulerpro
folder in the /force-app/main/default/lwc
folder. The new folder contains an HTML file and a JavaScript file for the Lightning web component, and an XML configuration file named schedulerpro.js-meta.xml
that defines the component’s metadata values.
Replace the code in the schedulerpro.js-meta.xml
with the following:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Bryntum Scheduler Pro</masterLabel>
<description>Bryntum Scheduler Pro sample</description>
<targets>
<target>lightning__AppPage</target>
<target>lightning__Tab</target>
<target>lightningCommunity__Page</target>
</targets>
</LightningComponentBundle>
We expose the LWC so that it can be used in the Lightning App Builder in your Salesforce scratch org. We also set the targets specifying where the LWC can be added. The LWC can be added to a Lightning App Page.
Replace the code in the schedulerpro.html
file with the following lines of code:
<template>
<div class="container" lwc:dom="manual"></div>
</template>
This HTML file uses an LWC HTML template to render components efficiently using a virtual DOM. In most cases, it’s best to let the LWC manipulate the DOM instead of using JavaScript. For the Bryntum Scheduler Pro LWC, we set lwc:dom
to "manual"
as the Bryntum Scheduler Pro JavaScript code will need to manipulate the DOM by adding the Scheduler Pro component to the <div>
with a class of "container"
.
Add CSS styles to the Lightning web component
Now create a schedulerpro.css
file and add the following lines of code to it:
.container {
height: 45em;
}
.b-float-root {
z-index: 100;
}
.address-results .b-list-item i {
font-size: 1.5em;
margin: 0 0.7em 0 0.2em;
}
.address-container {
display: flex;
flex-direction: column;
}
.address-name {
flex: 1;
margin-bottom: 0.4em;
font-size: 1.1em;
}
.lat-long {
flex: 1;
color: #bbb;
font-size: 0.9em;
}
.b-sch-event {
border-radius: 3px;
box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.4);
border: 1px solid rgba(0, 0, 0, 0.2);
}
.b-theme-material .widget-title {
color: #555;
}
.b-sch-event-content {
display: flex;
height: 100%;
flex-direction: column;
justify-content: space-evenly;
}
.event-name {
font-size: 1.3em;
}
.location {
display: block;
margin-top: 0.3em;
font-weight: normal;
}
.b-fa-map-marker-alt {
margin-inline: 1px 0.4em;
}
The CSS styles we add here will size the Scheduler Pro in the <div>
with a class of "container"
, style the task bar, and style the custom address input that we’ll add to the task editor.
Create a Bryntum Scheduler Pro Lightning web component
In the schedulerpro.js
file, the LightningElement
class is the base class for Lightning Web Components. We’ll extend it to create the Schedulerpro
class that will create the Bryntum Scheduler Pro. Replace the code in the schedulerpro.js
file with the following lines of code:
/* globals bryntum : true */
import { LightningElement } from "lwc";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import { loadScript, loadStyle } from "lightning/platformResourceLoader";
import SCHEDULERPRO from "@salesforce/resourceUrl/bryntum_schedulerpro";
import ScheduleMixin from "./lib/Schedule";
import TaskMixin from "./lib/Task";
export default class Schedulerpro extends LightningElement {
schedulerproData = null;
renderedCallback() {
if (this.bryntumInitialized) {
return;
}
this.bryntumInitialized = true;
Promise.all([
loadScript(this, SCHEDULERPRO + "/schedulerpro.lwc.module.js"),
loadStyle(this, SCHEDULERPRO + "/schedulerpro.stockholm.css"),
])
.then(() => {
this.loadAndPrepareData();
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: "Error loading Bryntum Scheduler Pro",
message: error,
variant: "error",
})
);
});
}
async loadAndPrepareData() {
let data;
try {
data = await getData();
} catch (error) {
console.error("Error getting events and resources data: ", { error });
return; // If there's an error, we don't want to proceed
}
this.schedulerproData = mapData(data);
this.createScheduler();
}
createScheduler() {
const container = this.template.querySelector(".container");
const { SchedulerPro, EventModel, ProjectModel } = bryntum.schedulerpro;
const Schedule = ScheduleMixin(SchedulerPro);
const Task = TaskMixin(EventModel);
const project = new ProjectModel({
eventModelClass: Task,
eventsData: this.schedulerproData.tasks,
resourcesData: this.schedulerproData.resources,
});
const schedule = window.schedule = new Schedule({
ref: "schedule",
appendTo: container,
startDate: new Date(2024, 11, 15, 8),
endDate: new Date(2024, 11, 15, 20),
project,
});
}
}
The schedulerproData
 property will store the data we get from our custom Salesforce objects. The renderedCallback()
 method is called after every render of the Lightning web component. Once the Bryntum Scheduler Pro JavaScript and CSS static resources are loaded using the Lightning Web Components Platform Resource Loader loadStyle()
and loadScript()
methods, the loadAndPrepareData()
 method is called. This method will get the Bryntum Scheduler Pro data and then create the Bryntum Scheduler Pro instance. The getData
and mapData
methods will be defined later.
In the createScheduler()
method, we define the project model that will store the tasks and resources data and link them together. We use a custom event model for tasks so that we can add an address
field to the tasks. We also create a Bryntum Scheduler Pro instance using a custom Scheduler Pro class.
Create a custom Task class
Now let’s define the custom classes. Create a lib
folder in the schedulerpro
LWC folder and create a file called Task.js
. Add the following lines of code to it:
export default (EventModelClass) =>;
class Task extends EventModelClass {
// Custom Task model, based on EventModel with additional fields and changed defaults
static get fields() {
return [
{ name: "address", defaultValue: {} },
{ name: "duration", defaultValue: 1 },
{ name: "durationUnit", defaultValue: "hour" },
];
}
get shortAddress() {
return (this.address?.display_name || "").split(",")[0];
}
};
The custom Task
model extends the Scheduler Pro EventModel
. We wrap the class in a function so that we can pass in the EventModel
as an argument from where the class is instantiated. The custom Task
model sets some field value defaults and adds a custom address
field to show location data. The address field will be an object containing a display name, latitude, and longitude for a location.
Create a custom Address
class
Now create a file called Address.js
in the lib
folder and add the following lines of code to it:
export default (EventModelClass) =>
class Address extends EventModelClass {
static get fields() {
return ["display_name", "lat", "lon"];
}
};
We create another custom EventModel
to define the data fields of the custom Address
field that we’ll add to the task editor.
Create custom Schedule
and AddressSearchField
classes
Let’s define the custom Schedule
class. Create a file called Schedule.js
in the lib
folder and add the following lines of code to it:
import AddressMixin from "./Address.js";
export default (SchedulerProClass) => {
const { DateHelper, StringHelper, Combo, EventModel } =
window.bryntum.schedulerpro;
class AddressSearchField extends Combo {
// Factoryable type name
static get type() {
return "addresssearchfield";
}
static get $name() {
return "AddressSearchField";
}
static get configurable() {
const Address = AddressMixin(EventModel);
return {
clearable: true,
displayField: "display_name",
// Setting the value field to null indicates we want the Combo to get/set address *records* as opposed to the
// id of an address record.
valueField: null,
filterOnEnter: true,
minChars: 4,
store: {
modelClass: Address,
readUrl: "https://nominatim.openstreetmap.org/?format=json&q=",
restfulFilter: true,
fetchOptions: {
// Please see MDN for fetch options: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
credentials: "omit",
},
},
// Addresses can be long
pickerWidth: 450,
validateFilter: false,
listCls: "address-results",
// Custom list item template to show a map icon with lat + lon
listItemTpl: (address) => {
return `
${address.display_name}
${address.lat}°, ${address.lon}°
`;
},
};
}
}
AddressSearchField.initClass();
return class Schedule extends SchedulerProClass {
// Customized scheduler with address search field
static get $name() {
return "Schedule";
}
static get configurable() {
return {
features: {
stripe: true,
eventBuffer: true,
taskEdit: {
saveAndCloseOnEnter: false,
editorConfig: {
autoUpdateRecord: false,
},
items: {
generalTab: {
items: {
resourcesField: {
required: true,
},
// For this demo we add an extra remote address search field
addressField: {
type: "addresssearchfield",
label: "Address",
name: "address",
weight: 100,
},
preambleField: {
label: "Travel to",
},
postambleField: {
label: "Travel from",
},
},
},
},
},
},
rowHeight: 80,
barMargin: 4,
eventColor: null,
eventStyle: null,
columns: [
{
type: "resourceInfo",
text: "Name",
width: 200,
showEventCount: true,
showRole: true,
showImage: false,
},
],
// Custom view preset with header configuration
viewPreset: "hourAndDay",
tbar: [
{
type: "widget",
cls: "widget-title",
html: "Scheduler View",
flex: 1,
},
{
type: "datefield",
ref: "dateField",
width: 190,
editable: false,
step: 1,
onChange: "up.onDateFieldChange",
},
{
type: "textfield",
ref: "filterByName",
placeholder: "Filter tasks",
clearable: true,
keyStrokeChangeDelay: 100,
triggers: {
filter: {
align: "start",
cls: "b-fa b-fa-filter",
},
},
onChange: "up.onFilterChange",
},
],
};
}
construct() {
super.construct(...arguments);
this.widgetMap.dateField.value = this.startDate;
}
onFilterChange({ value }) {
value = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// Replace all previous filters and set a new filter
this.taskStore.filter({
filters: (event) => new RegExp(value, "i").test(event.name),
replace: true,
});
}
onDateFieldChange({ value, userAction }) {
userAction &&
this.setTimeSpan(
DateHelper.add(value, 8, "hour"),
DateHelper.add(value, 20, "hour")
);
}
onPrevious() {
this.shiftPrevious();
}
onNext() {
this.shiftNext();
}
// Custom event renderer showing the task name + location icon with a shortened address text
eventRenderer({ eventRecord }) {
return [
{
tag: "span",
className: "event-name",
html: StringHelper.encodeHtml(eventRecord.name),
},
{
tag: "span",
className: "location",
children: [
eventRecord.shortAddress
? {
tag: "i",
className: "b-fa b-fa-map-marker-alt",
}
: null,
eventRecord.shortAddress || "â €",
],
},
];
}
};
};
The wrapper function for this file has two classes: AddressSearchField
and Schedule
, which are returned from the wrapper function.
The custom AddressSearchField
is a customized Combo
(dropdown) widget that uses the Nominatim API to query for location data by address. The minChars
property is the minimum string length that triggers filtering when the enter button is pressed. We add this custom field to our Scheduler Pro task editor by adding it to the taskEdit
feature in the defaultConfig()
static getter method of the custom Schedule
class.
The Schedule
class has a custom eventRenderer that displays the task name and shortened address name on the task bar.
Add the nominatim API URL to the CSP Trusted Sites in Salesforce
In the Salesforce Platform UI, click on the setup gear icon on the top right and click “Setup” in the popup menu. In the “Quick Find” input on the top left, search for “Trusted URLs”. Add “https://nominatim.openstreetmap.org” as a trusted site. Under the “CSP Directives” header, check the “connect-src (scripts)” checkbox and then click “Save”.
Now let’s load our custom Salesforce object data into our Bryntum Scheduler Pro LWC by defining the getData
function.
Query events and resources data from the custom objects
When working with Salesforce data, you can use the Lightning Data Service to load, create, edit, or delete a Salesforce record. For more complex data operations, such as making a Salesforce Object Query Language (SOQL) query to select certain records, you can use Apex to write server-side controller classes. Let’s create an Apex class to get our tasks and resources data from our custom Salesforce objects.
- In the VS Code explorer pane, right-click on the
classes
folder and selectSFDX: Create Apex Class
. - Set the class name as
SchedulerproDataLoadController
and press “Enter”. - Press “Enter” again to accept the default directory.
- Replace the contents of the created
SchedulerproDataLoadController.cls
file with the following code:
public with sharing class SchedulerproDataLoadController {
@AuraEnabled(cacheable=true)
public static Map<String, Object> getData() {
// fetching the Records via SOQL
List<bryntum_schedulerpro_tasks_data__c> Tasks = new List<bryntum_schedulerpro_tasks_data__c>();
Tasks = [
SELECT Address__c,
All_day__c,
Children__c,
Cls__c,
Draggable__c,
Duration__c,
Duration_Unit__c,
Event_Color__c,
Event_Style__c,
Exception_Dates__c,
Icon_Cls__c,
Name__c,
Postamble__c,
Preamble__c,
Percent_Done__c,
Effort__c,
Effort_Unit__c,
Read_Only__c,
Recurrence_Rule__c,
Resizable__c,
Resource_Id__c,
Start_Date__c,
Style__c,
Time_Zone__c
FROM bryntum_schedulerpro_tasks_data__c WITH SECURITY_ENFORCED
];
List<bryntum_schedulerpro_resources_data__c> Resources = new List<bryntum_schedulerpro_resources_data__c>();
Resources = [
SELECT Event_Color__c,
Name__c,
Role__c
FROM bryntum_schedulerpro_resources_data__c WITH SECURITY_ENFORCED
];
Map<String, Object> result = new Map<String, Object>{
'tasks' => Tasks, 'resources' => Resources };
return result;
}
}
The AuraEnabled
annotation enables our Lightning web component to use Apex methods and properties. We set cacheable=true
so that the results are cached to improve runtime performance.
In Salesforce Apex, collections are used to store data. There are three types: lists, sets, and maps. The getTasks
Apex method returns a Map
type, where the keys are strings and the values are objects. The getData
Apex method creates two lists. The type of elements that will be stored in the Tasks
list are records with the object type of the bryntum_schedulerpro_tasks_data__c
custom object. The type of elements stored in the Resources
list are records with the object type of the bryntum_schedulerpro_resources_data__c
custom object. The __c
indicates that it’s a custom object, this is a Salesforce naming convention.
Each list performs a SOQL read operation to get the tasks or resources data. The result of the query is assigned to the list to store the values. The getData
method returns a map of the tasks and resources data.
Add the Apex class to your Salesforce org
Add the Apex class to your Salesforce org by right-clicking the classes
folder in the VS Code Explorer pane and selecting SFDX: Deploy Source to Org
.
Load data from the custom objects into the Bryntum Scheduler Pro
To get the queried data into your Bryntum Scheduler Pro LWC, import the Apex method in the schedulerpro.js
file:
import getData from "@salesforce/apex/SchedulerproDataLoadController.getData";
This method is called in the loadAndPrepareData
method.
Before we use the data in the Scheduler Pro data stores, we need to change the property names from sentence case with words separated by an underscore to camel case and remove the __c
suffix. We use the mapData
function to do this. It’s also called in the loadAndPrepareData
method.
Define the mapData
function above the SchedulerPro
class definition as follows:
function mapData(fromSF) {
const tasks = fromSF.tasks.map((item) => ({
id: item.Id,
address: item.Address__c ? JSON.parse(item.Address__c) : null,
allDay: item.All_Day__c,
children: item.Children__c,
cls: item.Cls__c,
draggable: item.Draggable__c,
duration: item.Duration__c,
durationUnit: item.Duration_Unit__c,
color: item.Event_Color__c,
style: item.Event_Style__c,
exceptionDates: item.Exception_Dates__c
? JSON.parse(item.Exception_Dates__c)
: null,
cls: item.Icon_Cls__c,
name: item.Name__c,
postamble: item.Postamble__c,
preamble: item.Preamble__c,
percentDone: item.Percent_Done__c,
effort: item.Effort__c,
effortUnit: item.Effort_Unit__c,
readOnly: item.Read_Only__c,
recurrenceRule: item.Recurrence_Rule__c,
resizable: item.Resizable__c,
resourceId: item.Resource_Id__c,
startDate: item.Start_Date__c,
style: item.Style__c,
timeZone: item.Time_Zone__c,
}));
const resources = fromSF.resources.map((item) => ({
id: item.Id,
eventColor: item.Event_Color__c,
name: item.Name__c,
role: item.Role__c,
}));
return { tasks, resources };
}
The Address
field is an object, and the Exception Dates
field can be a string or an array. These fields are stored as strings in the Salesforce tasks object for simplicity, so they need to be parsed before they are added to the Bryntum Scheduler Pro event store. The data objects are stored in the schedulerproData
variable and added to the Scheduler Pro in the project
model configuration.
The Bryntum Scheduler will now have access to the initial tasks and resources data. Next, we’ll add our Bryntum Scheduler Pro LWC to our Salesforce scratch org.
Add the Bryntum Scheduler Pro component to a Salesforce Lightning App
In the VS Code explorer pane, right-click on the schedulerpro
LWC folder and select SFDX: Deploy Source to Org
. Open the VS Code command palette, then type and select SFDX: Open Default Org
. This opens your Salesforce scratch org in a browser window. In the “Quick Find” input at the top left of the page in the navigation column, search for “Lightning App Builder” and click on it. Follow these steps to create a Lightning Page with your Bryntum Scheduler Pro LWC:
- Click the “New” button to create a Lightning page.
- Click the “Next” button on the “App Page” tab.
- Give the page the label “Bryntum Scheduler Pro”.
- Select “One Region” for the layout type.
- Find the “Bryntum Scheduler Pro” custom component at the bottom of the components column on the left of the page, and drag it into the block with the label “Add Component(s) Here” in the center of the page.
- Click the “Save” button and then the “Activate” button.
- Click the “Save” button in the Activation dialog and then click “Finish”.
Now click the back button on the top left corner of the page. Open the App Launcher by clicking the nine-dot button at the top left of the page and search for “Bryntum Scheduler Pro”. You should see your Bryntum Scheduler Pro LWC in the Lightning Page:
Double-click on one of the events to open the task editor. To add an address, add at least four characters to the “ADDRESS” field input and press the Enter key.
Now let’s save changes made to the Bryntum Scheduler Pro LWC to our tasks and resources custom Salesforce objects.
Create or update tasks in the Scheduler Pro task editor
In the schedulerpro.js
file, import the createRecord
, updateRecord
, and deleteRecord
methods from the Salesforce lightning/uiRecordApi
module:
import {
createRecord,
deleteRecord,
updateRecord,
} from "lightning/uiRecordApi";
The Salesforce lightning/uiRecordApi
module allows us to make requests to Salesforce JavaScript APIs to create, delete, or update records in our custom objects.
Now add the following event listeners to the schedule
config:
listeners: {
beforeTaskEditShow() {
disableChange = true;
},
afterTaskSave({ taskRecord }) {
disableChange = false;
// create / update event
updateOrCreateTask(taskRecord);
},
},
Add the disableChange
variable at the top of the file:
let disableChange = false;
These event listeners are used to disable creating or updating the Salesforce custom tasks object while the task editor is open. This prevents unnecessary API calls.
Let’s define the updateOrCreateTask
function at the top of the createScheduler()
method:
updateOrCreateTask(taskRecord) {
// create
if (taskRecord.isPhantom) {
try {
const insertEvent = {
apiName: "bryntum_schedulerpro_tasks_data__c",
fields: {
Address__c: taskRecord.address.display_name
? JSON.stringify({
display_name: taskRecord.address.display_name,
lat: taskRecord.address.lat,
lon: taskRecord.address.lon,
})
: null,
All_Day__c: Number(taskRecord.allDay),
Children__c: taskRecord.children,
Cls__c: taskRecord.cls,
Draggable__c: Number(taskRecord.draggable),
Duration__c: taskRecord.duration,
Duration_Unit__c: taskRecord.durationUnit,
Event_Color__c: taskRecord.color,
Event_Style__c: taskRecord.style,
Exception_Dates__c: JSON.stringify(taskRecord.exceptionDates),
Icon_Cls__c: taskRecord.cls,
Name__c: taskRecord.name,
Postamble__c: taskRecord.postamble,
Preamble__c: taskRecord.preamble,
Percent_Done__c: taskRecord.percentDone,
Effort__c: taskRecord.effort,
Effort_Unit__c: taskRecord.effortUnit,
Read_Only__c: Number(taskRecord.readOnly),
Recurrence_Rule__c: taskRecord.recurrenceRule,
Resizable__c: `${taskRecord.resizable}`,
Resource_Id__c: taskRecord.resourceId,
Start_Date__c: formatDate(taskRecord.startDate),
Style__c: taskRecord.style,
Time_Zone__c: taskRecord.timeZone,
},
};
return createRecord(insertEvent).then((resEvent) => {
schedule.taskStore.applyChangeset({
updated: [
// This will set the proper id for the added task
{ $PhantomId: taskRecord.id, id: resEvent.id },
],
});
});
} catch (error) {
console.error(error);
}
} else {
// update
try {
const updateEvent = {
fields: {
Id: taskRecord.id,
Address__c: taskRecord.address
? JSON.stringify({
display_name: taskRecord.address.display_name,
lat: taskRecord.address.lat,
lon: taskRecord.address.lon,
})
: null,
All_Day__c: Number(taskRecord.allDay),
Children__c: taskRecord.children,
Cls__c: taskRecord.cls,
Draggable__c: Number(taskRecord.draggable),
Duration__c: taskRecord.duration,
Duration_Unit__c: taskRecord.durationUnit,
Event_Color__c: taskRecord.color,
Event_Style__c: taskRecord.style,
Exception_Dates__c: JSON.stringify(taskRecord.exceptionDates),
Icon_Cls__c: taskRecord.cls,
Name__c: taskRecord.name,
Postamble__c: taskRecord.postamble,
Preamble__c: taskRecord.preamble,
Percent_Done__c: taskRecord.percentDone,
Effort__c: taskRecord.effort,
Effort_Unit__c: taskRecord.effortUnit,
Read_Only__c: Number(taskRecord.readOnly),
Recurrence_Rule__c: taskRecord.recurrenceRule,
Resizable__c: `${taskRecord.resizable}`,
Resource_Id__c: taskRecord.resourceId,
Start_Date__c: formatDate(taskRecord.startDate),
Style__c: taskRecord.style,
Time_Zone__c: taskRecord.timeZone,
},
};
return updateRecord(updateEvent).then((resEvent) => {
console.log("resEvent for update: ", resEvent);
});
} catch (error) {
console.error(error);
}
}
}
This function creates a new task if the event has a phantom ID, which is an autogenerated unique value created by the Scheduler Pro on the client to identify a record. The “real” ID will be added by the Salesforce backend. The updateOrCreateTask
function updates a task if it has an ID assigned to it by Salesforce on the backend.
Add the following formatDate
helper function to the top of the file:
function formatDate(dateString) {
let date = new Date(dateString);
return date.toISOString();
}
We also need to listen for data changes that happen outside of the task editor to update the Bryntum Scheduler Pro data in Salesforce.
Listen for store data changes in the Bryntum Scheduler Pro LWC
In the ProjectModel
configuration, add the following listeners
property below the resourcesData
property:
listeners: {
change({ store, action, records }) {
const storeId = store.id;
if (!disableChange && storeId === "events") {
if (action === "add") {
console.log("add event");
}
if (action === "remove") {
console.log("remove event");
if (records[0].isPhantom) return;
records.forEach((record) => {
deleteRecord(record.id).then((res) =>
console.log("deleteRecord: ", { res })
);
});
}
if (action === "update") {
for (let i = 0; i < records.length; i++) {
const taskRecord = records[i];
if (taskRecord.isPhantom) continue;
try {
const updateEvent = {
fields: {
Id: taskRecord.id,
Address__c: taskRecord.address
? JSON.stringify({
display_name: taskRecord.address.display_name,
lat: taskRecord.address.lat,
lon: taskRecord.address.lon,
})
: null,
All_Day__c: Number(taskRecord.allDay),
Children__c: taskRecord.children,
Cls__c: taskRecord.cls,
Draggable__c: Number(taskRecord.draggable),
Duration__c: taskRecord.duration,
Duration_Unit__c: taskRecord.durationUnit,
Event_Color__c: taskRecord.color,
Event_Style__c: taskRecord.style,
Exception_Dates__c: JSON.stringify(
taskRecord.exceptionDates
),
Icon_Cls__c: taskRecord.cls,
Name__c: taskRecord.name,
Postamble__c: taskRecord.postamble,
Preamble__c: taskRecord.preamble,
Percent_Done__c: taskRecord.percentDone,
Effort__c: taskRecord.effort,
Effort_Unit__c: taskRecord.effortUnit,
Read_Only__c: Number(taskRecord.readOnly),
Recurrence_Rule__c: taskRecord.recurrenceRule,
Resizable__c: `${taskRecord.resizable}`,
Resource_Id__c: taskRecord.resourceId,
Start_Date__c: formatDate(taskRecord.startDate),
Style__c: taskRecord.style,
Time_Zone__c: taskRecord.timeZone,
},
};
return updateRecord(updateEvent).then((resEvent) => {
console.log("resEvent for update: ", resEvent);
});
} catch (error) {
console.error(error);
}
}
}
}
},
},
The change
method is called when data in a project store changes. We get information about the store change from the method parameters. The store
object allows us to determine which store changed, tasks
or resources
. The action
is the name of the action that triggered the change, such as “add”, “remove”, or “update”. There are other events, like “removeAll”, “updateMultiple”, “filter”, “dataset”, and “replace”. We have excluded them here for simplicity. The records
are an array of all of the changed records, tasks
or resources
. The change
method has nested if
statements that will allow us to perform the correct CRUD operation for a custom object store update using the Salesforce lightning/uiRecordApi
module methods.
You could further customize your Bryntum Scheduler Pro LWC using the Bryntum assignmentStore and dependencyStore, which we didn’t include in this tutorial.
Right-click on the schedulerpro
folder and select SFDX: Deploy Source to Org
. Create another Lightning Page with this updated Bryntum Scheduler Pro LWC. You now have a Bryntum Scheduler Pro LWC with CRUD functionality.