W artykule "LWC podejście pierwsze" napisałem, że udało mi się zaimplementować obsługę FullCalendar w wersji 3.0. Napisałem też, że ciężko jest zsynchronizować ładowanie danych do kalendarza. Jak to czasami bywa, nie miałem do końca racji. Otóż da się to zrobić i to w dosyć prosty sposób. W momencie pisania artykułu nie do końca jeszcze rozumiałem i wiedziałem o innych sposobach pobierania danych z Apexa. Obecnie zamiast dekorować @wire zmienną odpowiedzialną za przechowywanie danych robię to nieco inaczej - imperatywnie. Oznacza to, że mam zdefiniowaną pustą tablicę dekoratorem @track. Metodę do pobrania danych wywołuję bezpośrednio po inicjalizacji kalendarza, co rozwiązuje problem jaki napotkałem poprzednio. Całość wygląda mniej więcej tak :
import getAllBillsApex from '@salesforce/apex/Bill_CalendarController.getAllBillsWrapped';
...
@track bills;
...
fillData() {
getAllBillsApex()
.then(result => {
this.bills = result;
this.error = undefined;
})
.then(() => {
this.calendar.fullCalendar('addEventSource', this.parseBackendData(this.bills, false));
this.dataLoaded = true;
})
.then(() => {
getAllHolidaysApex()
.then(result => {
this.holidays = result;
})
.then(() => {
this.calendar.fullCalendar('addEventSource', this.parseBackendData(this.holidays, true));
})
.then(() => {
this.calendar.fullCalendar('rerenderEvents');
})
.catch(error => {
this.error = error;
this.utils.showToast('Error!', reduceErrors(error).join(', '), 'error');
});
})
.catch(error => {
this.error = error;
this.bills = undefined;
this.holidays = undefined;
this.utils.showToast('Error!', reduceErrors(error).join(', '), 'error');
});
}
Pominę dodatkowe rzeczy, typu metody utilsowe, natomiast skupie się na pobieraniu danych. Pierwsza metoda getAllBillsApex() to metoda Apexowa, która zwraca listę obiektów z backendu, nie są to jednak zwykłe obiekty a opakowane we wrapper dane. Odpowiednia metoda następnie je parsuje i zapisuje do tablicy.
Następną rzeczą, którą się nauczyłem jest wydzielanie wspólnego kodu do klas pomocniczych. Na początku wszystko trzymałem w jednym komponencie, jednak szybko zauważyłem, że kod się powiela i powtarza. Starałem się każdą tego typu akcję wydzielać do osobnej metody. Tak właśnie powstały dwa twory : klasa Utils oraz komponent UiUtils. Oba mają inne zadania ale uzupełniają się. Klasa Utils zawiera jedynie metody, na tą chwilę są to metody odpowiedzialne np. za parsowanie proxy object na JSON (przydatne przy debugowaniu) lub wyszukiwaniu/usuwaniu wierszy w datatable. W komponencie UiUtils trzymam natomiast fragmenty kodu, które często wykorzystujemy do renderowania pewnych rzeczy w samym komponencie. Na tą chwilę umieściłem tutaj pokazywanie wiadomości Toast oraz nawigowanie do innych rekordów z wykorzystaniem interfejsu NavigationMixins.
Kolejnym ważnym krokiem jest dodanie możliwości konfigurowania komponentu z poziomu AppBuildera. Można to uzyskać dzięki zastosowaniu propertisów w pliku xml. Co istotne dane wpisane w AppBuilderze nadpiszą nasze dane zapisane w zmiennych. Dzięki temu, można w łatwy sposób testować zachowanie komponentu w momencie zmiany parametrów. Na tą chwilę zaimplementowałem jedynie opcje związane z kolorami komórek. Fragment z pliku xml :
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<property name="billBackgroundColor" label="Bill background color" type="String" default="#005fb2"/>
<property name="billBorderColor" label="Bill border color" type="String" default="#005fb2"/>
<property name="billTextColor" label="Bill text color" type="String" default="#ffffff"/>
<property name="holidayBackgroundColor" label="Holiday background color" type="String" default="#b55122"/>
<property name="holidayBorderColor" label="Holiday border color" type="String" default="#b55122"/>
<property name="holidayTextColor" label="Holiday text color" type="String" default="#ffffff"/>
<property name="showHolidayAsBackground" label="Show Holiday as background?" type="Boolean" default="false"/>
<objects>
<object>Bill__c</object>
</objects>
</targetConfig>
</targetConfigs>
oraz parametry w kodzie :
@api billBackgroundColor = '#005fb2';
@api billBorderColor = '#005fb2';
@api billTextColor = '#ffffff';
@api holidayBackgroundColor = '#b55122';
@api holidayBorderColor = '#b55122';
@api holidayTextColor = '#ffffff';
@api showHolidayAsBackground = false;
Dzięki takiej konstrukcji można to bardzo prosto wykorzystać, np jak poniżej :
parseBackendData(data, isHoliday) {
let arr = parseProxy(data);
let source = [];
arr.forEach(i => {
const calendarEvent = {
id: i.id,
title: i.title,
start: moment(i.startDate, DEFAULT_DATE_FORMAT),
end: moment(i.endDate, DEFAULT_DATE_FORMAT),
allDay: true,
color: isHoliday ? this.holidayBackgroundColor : this.billBackgroundColor,
borderColor: isHoliday ? this.holidayBorderColor : this.billBorderColor,
textColor: isHoliday ? this.holidayTextColor : this.billTextColor,
holiday: isHoliday,
rendering: isHoliday && this.showHolidayAsBackground ? 'background' : '',
editable: !isHoliday
};
source.push(calendarEvent);
});
return source;
}
Komponent powoli nabiera kształtu, dużo jeszcze przede mną aby było to w miarę generyczne, póki co działa w kontekście jednego custom obiektu. Kod jest nadal w przygotowaniu, jest też fragmentem większego projektu więc wydzielenie go do osobnego demo musi jeszcze chwilę poczekać.