Попадалась на одном легаси проекте задача: сделать на вижуалфорс странице табличку, показывающую список дочерних записей и позволяющую их редактировать без перехода на саму запись. Соответственно, страничка на лаяуте родителя. Особых проблем это не вызвало, довольно рутинная задача, но в какой-то момент стало интересно реализовать то же самое, но используя LWC.
В силу своей лени я решил не создавать для этого новые объекты на своей дэв организации (в оригинале было 2 кастомных), а просто вывести список контактов на аккаунте.
Вот что в итоге получилось.
Для того, чтобы вытащить нужные контакты, создал простенький класс, который только это и умеет:
public with sharing class CuteContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> getContactsByAccount(String accountId) {
return [SELECT Id, FirstName, Lastname, Title, Phone, Email from Contact where AccountId = :accountId];
}
}
Потом создал сам компонент. Традиционно, он состоит из 3х файлов: .html, .js и .js-meta.xml.
С последним всё понятно, версия АПИ - текущая, isExposed = true и в targets указываем lightning__RecordPage. Текст файла ниже:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>47.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>
В файле с html разметкой на помощь приходит компонент lightning-datatable:
<template>
<lightning-card title="Related Contacts" icon-name="custom:custom69">// nice
<div class="slds-m-around_medium">
<template if:true={contacts.data}> // valid contact data has been returned from an apex method
<lightning-datatable
key-field="Id" // maps each row to a contact record
data={contacts.data}
columns={columns} // constant collection we'll set in js constructor
onsave={handleSave} // js method to handle event
draft-values={draftValues} // the place to store user input
hide-checkbox-column=true>
</lightning-datatable>
</template>
<template if:true={contacts.error}> // we're just playing so let's hope everything's gonna be ok:)
<!-- if some error is appeared do something -->
</template>
</div>
</lightning-card>
</template>
И, собственно, javascript контроллер:
import { LightningElement, wire, track, api } from 'lwc';
import getContactsByAccount from '@salesforce/apex/CuteContactController.getContactsByAccount';
import { updateRecord } from 'lightning/uiRecordApi';
import { refreshApex } from '@salesforce/apex';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
const COLUMNS = [
{ label: 'First Name', fieldName: 'FirstName', editable: true },
{ label: 'Last Name', fieldName: 'LastName', editable: true },
{ label: 'Title', fieldName: 'Title', editable: true },
{ label: 'Phone', fieldName: 'Phone', type: 'phone', editable: true },
{ label: 'Email', fieldName: 'Email', type: 'email', editable: true }
];
export default class RelatedContactsByForAccount extends LightningElement {
@track columns = COLUMNS;
@track draftValues = [];
@api recordId;
@wire(getContactsByAccount, {accountId:'$recordId'})
contacts;
handleSave(event) {
const recordInputs = event.detail.draftValues.slice().map(draft => {
const fields = Object.assign({}, draft);
return { fields };
});
const promises = recordInputs.map(recordInput => updateRecord(recordInput));
Promise.all(promises).then(records => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Contact updated',
variant: 'success'
})
);
this.draftValues = [];
return refreshApex(this.contacts);
}).catch(error => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error updating record',
message: error.body.message,
variant: 'error'
})
);
});
}
}
COLUMNS описывает значения и поведение колонок нашей таблицы. В данном случае я хотел, чтобы все значения были редактируемыми, поэтому editable параметр выставлен в true для всех колонок. Само собой, это опционально.
Коллекция draftValues изначально пустая. В ней будут храниться изменения, внесённые пользователем.
recordId - айдишник текущего аккаунта.
Для вызова апекс метода, который мы заимпортили в начале, используем @wire, передаём ему recordId и помещаем результат в переменную contacts. Теперь она хранит коллекцию контактов, которую вернул апекс метод.
При изменении каких-либо значений в таблице, и нажатии кнопки Save (содержится в lightning-datatable компоненте, поэтому явно на странице не указана), ивент обрабатывается методом handleSave.
Тут стоит заметить, что метод updateRecord принимает только одну строку за раз. И если бы мы обрабатывали изменения только первой строки таблицы (странный пример, но для наглядности), то выглядело бы это как-то так:
const fields = {};
fields[ID_FIELD.fieldApiName] = event.detail.draftValues[0].Id;
fields[FIRSTNAME_FIELD.fieldApiName] = event.detail.draftValues[0].FirstName;
fields[LASTNAME_FIELD.fieldApiName] = event.detail.draftValues[0].LastName;
//and all used fields
const recordInput = {fields};
updateRecord(recordInput)
// and so on
Когда все записи успешно обновлены, на странице появляется сообщение об этом:
new ShowToastEvent({
title: 'Success',
message: 'Contact updated',
variant: 'success'
})
Коллекция с изменениями очищается и футер lightning-datatable компонента с кнопками Cancel и Save пропадает:
this.draftValues = [];
И, что самое приятное, таблица обновляется. Апекс метод у нас cacheable, но в данном случае мы собственноручно обновили записи в базе данных, значит данные, сохранённые в кеше, уже не актуальны. Вызвать метод заново и получить свежую информацию можно с помощью метода refreshApex(wiredProperty), где wiredProperty - та самая переменная, над которой мы указывали аннотацию @wire. Апекс метод будет вызван заново и наша wired переменная обновится, а с ней и данные в нашей таблице.