Updating Salesforce Object with data coming from a third-party Javascript library

Jared Odulio
6 min readMar 23, 2019

--

I’m new to Salesforce development and started working on my first SF platform developer certification but I’m not new to software development, it has been a love-hate relationship going on in the past 20 years.

Anyway, let’s get down to the business. Having said that I’m new Salesforce development, a lot of young folks today who went straight down to Salesforce development without prior experience from other platforms and environments aren’t comfortable with the terminal console (insert grin here, these kids gonna die haha!). Currently, I’m a Full Stack Web Developer, that’s the title they put on someone who knows server-side Javascript as well as client-side Javascript, with knowledge on jQuery, CSS, HTML5, Responsive Web Apps, Angular, React , though I didn’t spend so much time on Angular, React since I was able to build my own frontend framework with EJS + jQuery but that’s for another discussion (sorry for the segway) and then backend integration whether it’s Heroku, Digital Ocean, Nodejitsu, plain AWS, Kubernetes or whatever and then top it off with knowledge on different data sources; SQL (Oracle, Postges, Sybase, MySQL etc.), NoSQL (MongoDB etc.) plus the latest called Progressive Web Apps. Clearly, software development is an adapt-or-die game. Right now, Javascript rules almost everything in web development beating Ruby On Rails, Java, PHP and so forth. Therefore, it makes a lot of sense when a top CRM company will adapt in the form of Lightning Web Components. So I’ll just go through briefly on Lightning Web Components on how we will pass data to Salesforce Objects using Apex as a controller class and Lightning Web Component where the third-party Javascript library will be initialised and used.

The Apex Class

When I first saw an Apex code, it reminded me of Java(oh shit! Why are we back here?), yes, it resembles a lot like Java from class declarations, collections to annotations. Even J2EE’s object-relational mapping concepts or the stench of Enterprise Java Beans (EJBs) are there , it’s evident in the custom objects with *__c endings. Next step was I had to read through the Salesforce Developer Experience or SFDX documentation and installed the Salesforce CLI and that already suggests that an experience with the terminal console is imminent , because as a Full Stack Web Developer, I also got my hand in several Unix and Linux servers where we used to deploy our Maven-built (back in those days) apps in different flavors of Unix and Linux,there were no graphical user interfaces on the server. So how are you going to deploy Salesforce apps without a browser or UI? Simple, SFDX with Salesforce CLI. Because when you sign up for a developer’s sandbox in Salesforce it is basically an “org” that needs to be authorized for development and to be able to do that quickly is by typing a command that looks like this in the in the terminal console:

$ sfdx:force:org <options here>

So let us now assume we have everything properly set up and ready to write our Apex class and it goes a little something like this:

public with sharing class BookingController {@AuraEnabled(Cacheable=true)public static Booking__c[] getAllBookings () { return [SELECT Name, Account__c, Start_Date__c, End_Date__c FROM  Booking__cORDER BY Name LIMIT 10];}@AuraEnabled
public static void updateBooking(String name, String sfid, String startDate, String endDate){
try {Booking__c booking = [SELECT Id, Name, Account__c, Start_Date__c, End_Date__c FROM Booking__cWHERE Id = :sfid LIMIT 1 FOR UPDATE];Datetime sDatestr = (Datetime)JSON.deserialize(‘“‘ + startDate + ‘“‘, Datetime.class);Datetime eDatestr = (Datetime)JSON.deserialize(‘“‘ + endDate + ‘“‘, Datetime.class);Datetime sysDate = System.now();booking.Start_Date__c = sDatestr;booking.End_Date__c = eDatestr;update booking;} catch (Exception e) {System.debug(‘An error has occurred: ‘ + e.getMessage());} finally { } return; }}

Basically, we have a Booking controller class that updates the Booking__c object. The updateBooking() function will be called be from the Lightning Web Component which is a Javascript code. Passing the parameters is straightforward and binding the parameters is simple as well as you can see in the :sfid but unfortunately, this technique is not mentioned in any trailhead modules (trailhead is a Salesforce training site) or any example documentations.

The Lightning Web Component

Now that we were able to build our Apex class, it’s time to create our Lightning Web Component. Lightning Web Component is actually an ECMA-compatible Javascript code and if you have been coding in Javascript for a while it shouldn’t be hard to learn Lightning Web Component

import { LightningElement, track, wire, api } from ‘lwc’;
import { loadScript, loadStyle } from ‘lightning/platformResourceLoader’;
import FullCalendarJS from ‘@salesforce/resourceUrl/FullCalendarJS’;
import getAllBookings from ‘@salesforce/apex/BookingController.getAllBookings’;
import updateBooking from ‘@salesforce/apex/BookingController.updateBooking’;
import { refreshApex } from ‘@salesforce/apex’;
const FIELDS = [SERVICE_NAME, BOOKING_ID];/*** FullCalendarJs* @description Full Calendar JS — Lightning Web Components*/export default class FullCalendarJs extends LightningElement {
@api recordId;
@track error;
@track sfid;
@track startDate;
@track endDate;
@track name;
@wire (getAllBookings) bookings;
@wire (updateBooking, {name: ‘$name’, sfid:’$sfid’, startDate: ‘$startDate’, endDate: ‘$endDate’}) updateBooking;
//@track bookings;fullCalendarJsInitialised = false;/*** @description Standard lifecyle method ‘renderedCallback’* Ensures that the page loads and renders the* container before doing anything else*/renderedCallback() {// Performs this operation only on first renderif (this.fullCalendarJsInitialised) { return;}this.fullCalendarJsInitialised = true;// Executes all loadScript and loadStyle promises// and only resolves them once all promises are donelet bookingPromise = Promise.resolve(this.bookings.data);Promise.all([bookingPromise,loadScript(this, FullCalendarJS + ‘/jquery.min.js’),
loadScript(this, FullCalendarJS + ‘/moment.min.js’),
loadScript(this, FullCalendarJS + ‘/fullcalendar.min.js’),
loadStyle(this, FullCalendarJS + ‘/fullcalendar.min.css’),
]).then((values) => {// Initialise the calendar configurationconsole.log({message: ‘Values’, values});this.initialiseFullCalendarJs(values[4]);}).catch(error => {// eslint-disable-next-line no-consoleconsole.error({message: ‘Error occured on FullCalendarJS’,error});})}/*** @description Initialise the calendar configuration* This is where we configure the available options for the calendar.* This is also where we load the Events data.*/initialiseFullCalendarJs(bookings) {const ele = this.template.querySelector(‘div.fullcalendarjs’);/* $(‘.cal-draggable’).draggable({revert: true,revertDuration: 0}) */const bookingData = this.bookings.data;let dataObj = {};let events = [];bookingData.forEach(element => {dataObj.title = element.Name;dataObj.sfid = element.Id;dataObj.start = element.Start_Date__c;dataObj.end = element.End_Date__c;dataObj.url = ‘/’ + element.Id;dataObj.account__c = element.Account__c;events.push(dataObj);dataObj = {};});// eslint-disable-next-line no-undef$(ele).fullCalendar({header: {left: ‘prev,next today’,center: ‘title’,right: ‘month,basicWeek,basicDay’},//defaultDate: ‘2019–01–12’,droppable: true,defaultDate: new Date(), // default day is todaynavLinks: true, // can click day/week names to navigate viewseditable: true,eventLimit: true, // allow “more” link when too many eventsevents: events,eventDrop: (event, delta, revertFunc)=>{alert(event.title + “ — “ + event.sfid + “ was dropped on “ + event.start);if (!confirm(“Are you sure about this change?”)) {revertFunc();} else {//update the booking objectthis.sfid = event.sfid;this.startDate = event.start.format();this.endDate = event.end.format();this.name = event.account__c;console.log(‘Passing: ‘, this.name + ‘; ‘ + this.sfid + ‘; ‘ + this.startDate + ‘; ‘ + this.endDate);updateBooking({name: this.name, sfid: this.sfid, startDate: this.startDate, endDate: this.endDate}).then((data)=>{console.log (‘Result affected: ‘, data);return refreshApex(this.bookings)}).catch((error)=>{console.log(‘Error received: ‘, error.errorCode + ‘ ‘ + error.body.message);});//let result = this.updateBooking.data;}}});}}

Now, we have a Lightning Web Component which imports a FullCalendarJS library as the 3rd party Javascript library. This library was initialised with an events data through the bookingData JSON object which will be written to the events array and passed as an event property during the calendar initialisation that will serve as a the 3rd party data to be passed back to Salesforce when it’s updated by a certain action, in our example, it’s an imaginary user who dragged and dropped an event in the calendar from one date to another and updates that date to the Booking__c object in the Apex class, this action will happen once the ‘eventDrop’ event is called from the FullCalendarJS and it will call the wired method ‘updateBooking’ (more on wiring concepts in the Salesforce Lightning Web Component documentation). The dates from the FullCalendarJS are passed as strings and not as Javascript Date object to the Apex BookingController class because the Apex Date object and the Javascript Date Object are directly INCOMPATIBLE! Hence, if you go back to our BookingController code the date values has to be deserialized with JSON so that it can update successfully the Booking__c object with new dates. So now I got an idea for an AppExchange library, Lightning Date Wrapper API which I think I can charge $1 per download/use.

That’s it! Happy to answer any questions from the comments.

--

--

Jared Odulio

Developer of really cool apps in Vue and Bulma, Sketcher wannabe, Mercedes-Benz fanatic, SWAG Equities Trader, Certified Securities Representative