Обновление объекта Salesforce данными, поступающими из сторонней библиотеки Javascript

Я новичок в разработке Salesforce и начал работать над своей первой сертификацией разработчика платформы SF, но я не новичок в разработке программного обеспечения, это были отношения любви и ненависти, которые продолжались последние 20 лет.

В любом случае, перейдем к делу. Сказав, что я новичок в разработке Salesforce, многие молодые люди, которые сегодня приступили к разработке Salesforce без предварительного опыта работы с другими платформами и средами, не чувствуют себя комфортно с терминальной консолью (вставьте здесь усмешку, эти дети умрут, ха-ха! ). В настоящее время я являюсь веб-разработчиком Full Stack, это звание, которое они назвали тому, кто знает серверный Javascript, а также клиентский Javascript, со знанием jQuery, CSS, HTML5, отзывчивых веб-приложений, Angular, React, хотя Я не тратил так много времени на Angular, React, так как я смог создать свою собственную структуру внешнего интерфейса с EJS + jQuery, но это для другого обсуждения (извините за сегвей), а затем интеграции с сервером, будь то Heroku, Digital Ocean, Nodejitsu, простой AWS, Kubernetes или что-то еще, а затем дополнить его знаниями о разных источниках данных; SQL (Oracle, Postges, Sybase, MySQL и т. Д.), NoSQL (MongoDB и т. Д.) Плюс новейшие прогрессивные веб-приложения. Ясно, что разработка программного обеспечения - это игра, в которой нужно адаптироваться или умереть. Прямо сейчас Javascript управляет почти всем в веб-разработке, превосходя Ruby On Rails, Java, PHP и так далее. Следовательно, имеет смысл, когда ведущая компания CRM адаптируется в виде веб-компонентов Lightning. Итак, я просто кратко расскажу о веб-компонентах Lightning и о том, как мы будем передавать данные в объекты Salesforce, используя Apex в качестве класса контроллера и веб-компонент Lightning, где будет инициализироваться и использоваться сторонняя библиотека Javascript.

Класс Apex

Когда я впервые увидел код Apex, он напомнил мне Java (черт возьми! Почему мы вернулись сюда?), Да, он очень похож на Java от объявлений классов, коллекций до аннотаций. Присутствуют даже концепции объектно-реляционного сопоставления J2EE или запах Enterprise Java Beans (EJB), это очевидно в настраиваемых объектах с окончанием * __ c. Следующим шагом я должен был прочитать документацию Salesforce Developer Experience или SFDX и установить Salesforce CLI, и это уже предполагает, что опыт работы с консолью терминала неизбежен, потому что как веб-разработчик Full Stack я также освоил несколько Unix и серверы Linux, где мы использовали для развертывания наших приложений, созданных Maven (в те дни), в различных вариантах Unix и Linux, на сервере не было графических пользовательских интерфейсов. Итак, как вы собираетесь развертывать приложения Salesforce без браузера или пользовательского интерфейса? Простой, SFDX с интерфейсом командной строки Salesforce. Потому что, когда вы регистрируетесь в изолированной программной среде разработчика в Salesforce, это, по сути, «организация», которая должна быть авторизована для разработки, и чтобы сделать это быстро, нужно ввести в консоли терминала команду, которая выглядит следующим образом:

$ sfdx:force:org <options here>

Итак, давайте теперь предположим, что у нас все правильно настроено и готово к написанию нашего класса Apex, и это выглядит примерно так:

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__c
ORDER 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__c
WHERE 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;
    }
}

По сути, у нас есть класс контроллера Booking, который обновляет объект Booking__c. Функция updateBooking () будет вызываться из веб-компонента Lightning, который представляет собой код Javascript. Передача параметров проста, а привязка параметров проста, как вы можете видеть в: sfid, но, к сожалению, этот метод не упоминается ни в каких модулях trailhead (trailhead - это сайт обучения Salesforce) или каких-либо примерах документации.

Веб-компонент Lightning

Теперь, когда мы смогли создать наш класс Apex, пришло время создать наш веб-компонент Lightning. Веб-компонент Lightning на самом деле представляет собой ECMA-совместимый код Javascript, и если вы какое-то время занимались кодированием на Javascript, вам не составит труда изучить веб-компонент Lightning.

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 render
if (this.fullCalendarJsInitialised) {
  return;
}
this.fullCalendarJsInitialised = true;
// Executes all loadScript and loadStyle promises
// and only resolves them once all promises are done
let 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 configuration
console.log({message: ‘Values’, values});
this.initialiseFullCalendarJs(values[4]);
})
.catch(error => {
// eslint-disable-next-line no-console
console.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 today
navLinks: true, // can click day/week names to navigate views
editable: true,
eventLimit: true, // allow “more” link when too many events
events: 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 object
this.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;
}
}
});
}
}

Теперь у нас есть веб-компонент Lightning, который импортирует библиотеку FullCalendarJS как стороннюю библиотеку Javascript. Эта библиотека была инициализирована данными событий через объект JSON bookingData, который будет записан в массив событий и передан как свойство события во время инициализации календаря, который будет служить сторонними данными, которые будут переданы обратно в Salesforce при их обновлении с помощью определенное действие, в нашем примере это воображаемый пользователь, который перетаскивал событие в календаре с одной даты на другую и обновляет эту дату в объекте Booking__c в классе Apex, это действие произойдет после того, как событие 'eventDrop' будет вызывается из FullCalendarJS, и он вызывает проводной метод updateBooking (подробнее о концепциях подключения см. в документации по веб-компоненту Salesforce Lightning). Даты из FullCalendarJS передаются в виде строк, а не как объект Javascript Date в класс Apex BookingController, потому что объект Apex Date и объект Javascript Date напрямую НЕСОВМЕСТИМЫ! Следовательно, если вы вернетесь к нашему коду BookingController, значения даты должны быть десериализованы с помощью JSON, чтобы он мог успешно обновить объект Booking__c с новыми датами. Итак, теперь у меня появилась идея для библиотеки AppExchange, API Lightning Date Wrapper, которую, я думаю, я могу взимать 1 доллар за загрузку / использование.

Вот и все! С удовольствием отвечу на любые вопросы из комментариев.