afv-library/skills/generating-lwc-components/assets/datatable-component/datatableComponent.js
sandipkumar-yadav 37aa84df42
feat: @W-22444026@ Introducing Core Skills, Datacloud Skills, Industries and Utility Skills. (#268)
* Migrating Core Salesforce Skills

* Updating pr comments

* updat reference

* Updating a skill

* Migrating Datacloud skills

* Migrating Industries cloud skills

* Validating - skills fixing

---------

Co-authored-by: Sandip Kumar Yadav <sandipkumar.yadav+sfemu@salesforce.com>
2026-05-14 19:32:15 +05:30

368 lines
13 KiB
JavaScript

/**
* DATATABLE LWC COMPONENT TEMPLATE
*
* This template demonstrates lightning-datatable with:
* - Column configuration
* - Row selection (single/multi)
* - Inline editing
* - Sorting
* - Infinite scrolling
* - Row actions
*
* Replace: datatableComponent → yourComponentName
*/
import { LightningElement, api, wire, track } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { NavigationMixin } from 'lightning/navigation';
import getRecords from '@salesforce/apex/DatatableController.getRecords';
import updateRecords from '@salesforce/apex/DatatableController.updateRecords';
import deleteRecords from '@salesforce/apex/DatatableController.deleteRecords';
// Row actions
const actions = [
{ label: 'View', name: 'view' },
{ label: 'Edit', name: 'edit' },
{ label: 'Delete', name: 'delete' }
];
// Column definitions
const COLUMNS = [
{
label: 'Account Name',
fieldName: 'nameUrl',
type: 'url',
typeAttributes: {
label: { fieldName: 'Name' },
target: '_blank'
},
sortable: true
},
{
label: 'Industry',
fieldName: 'Industry',
type: 'text',
sortable: true,
editable: true
},
{
label: 'Annual Revenue',
fieldName: 'AnnualRevenue',
type: 'currency',
typeAttributes: {
currencyCode: 'USD',
minimumFractionDigits: 0
},
sortable: true,
editable: true,
cellAttributes: { alignment: 'right' }
},
{
label: 'Phone',
fieldName: 'Phone',
type: 'phone',
editable: true
},
{
label: 'Created Date',
fieldName: 'CreatedDate',
type: 'date',
typeAttributes: {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
},
sortable: true
},
{
label: 'Active',
fieldName: 'IsActive__c',
type: 'boolean',
editable: true
},
{
type: 'action',
typeAttributes: { rowActions: actions }
}
];
export default class DatatableComponent extends NavigationMixin(LightningElement) {
// ═══════════════════════════════════════════════════════════════════════
// PUBLIC API
// ═══════════════════════════════════════════════════════════════════════
@api recordId;
@api maxRows = 50;
@api enableInfiniteScroll = false;
@api showRowNumbers = false;
// ═══════════════════════════════════════════════════════════════════════
// PRIVATE PROPERTIES
// ═══════════════════════════════════════════════════════════════════════
columns = COLUMNS;
@track data = [];
error;
isLoading = true;
// Sorting
sortBy;
sortDirection = 'asc';
// Selection
selectedRows = [];
draftValues = [];
// Infinite scroll
offset = 0;
loadMoreStatus;
totalRecords = 0;
// Wire result for refresh
wiredRecordsResult;
// ═══════════════════════════════════════════════════════════════════════
// WIRE SERVICE
// ═══════════════════════════════════════════════════════════════════════
@wire(getRecords, {
parentId: '$recordId',
limitSize: '$maxRows',
offset: '$offset',
sortBy: '$sortBy',
sortDirection: '$sortDirection'
})
wiredRecords(result) {
this.wiredRecordsResult = result;
this.isLoading = false;
const { data, error } = result;
if (data) {
// Add URL field for navigation
this.data = data.records.map(record => ({
...record,
nameUrl: `/${record.Id}`
}));
this.totalRecords = data.totalCount;
this.error = undefined;
} else if (error) {
this.error = this.reduceErrors(error);
this.data = [];
}
}
// ═══════════════════════════════════════════════════════════════════════
// GETTERS
// ═══════════════════════════════════════════════════════════════════════
get hasData() {
return this.data && this.data.length > 0;
}
get hasSelection() {
return this.selectedRows && this.selectedRows.length > 0;
}
get selectedCount() {
return this.selectedRows.length;
}
get tableTitle() {
return `Accounts (${this.totalRecords})`;
}
// ═══════════════════════════════════════════════════════════════════════
// SORTING
// ═══════════════════════════════════════════════════════════════════════
handleSort(event) {
this.sortBy = event.detail.fieldName;
this.sortDirection = event.detail.sortDirection;
this.isLoading = true;
// Wire will automatically refetch with new sort params
}
// ═══════════════════════════════════════════════════════════════════════
// SELECTION
// ═══════════════════════════════════════════════════════════════════════
handleRowSelection(event) {
this.selectedRows = event.detail.selectedRows;
// Dispatch event for parent
this.dispatchEvent(new CustomEvent('selection', {
detail: {
selectedRows: this.selectedRows,
selectedIds: this.selectedRows.map(row => row.Id)
}
}));
}
// ═══════════════════════════════════════════════════════════════════════
// ROW ACTIONS
// ═══════════════════════════════════════════════════════════════════════
handleRowAction(event) {
const actionName = event.detail.action.name;
const row = event.detail.row;
switch (actionName) {
case 'view':
this.navigateToView(row.Id);
break;
case 'edit':
this.navigateToEdit(row.Id);
break;
case 'delete':
this.handleDeleteRow(row);
break;
default:
break;
}
}
navigateToView(recordId) {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: recordId,
objectApiName: 'Account',
actionName: 'view'
}
});
}
navigateToEdit(recordId) {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: recordId,
objectApiName: 'Account',
actionName: 'edit'
}
});
}
async handleDeleteRow(row) {
if (!confirm(`Delete ${row.Name}?`)) {
return;
}
this.isLoading = true;
try {
await deleteRecords({ recordIds: [row.Id] });
this.showToast('Success', 'Record deleted', 'success');
await this.refresh();
} catch (error) {
this.showToast('Error', this.reduceErrors(error), 'error');
} finally {
this.isLoading = false;
}
}
// ═══════════════════════════════════════════════════════════════════════
// INLINE EDITING
// ═══════════════════════════════════════════════════════════════════════
handleCellChange(event) {
this.draftValues = event.detail.draftValues;
}
async handleSave(event) {
const updatedFields = event.detail.draftValues;
this.isLoading = true;
try {
await updateRecords({ records: updatedFields });
this.showToast('Success', 'Records updated', 'success');
this.draftValues = [];
await this.refresh();
} catch (error) {
this.showToast('Error', this.reduceErrors(error), 'error');
} finally {
this.isLoading = false;
}
}
handleCancel() {
this.draftValues = [];
}
// ═══════════════════════════════════════════════════════════════════════
// INFINITE SCROLLING
// ═══════════════════════════════════════════════════════════════════════
loadMoreData(event) {
if (!this.enableInfiniteScroll) return;
const { target } = event;
target.isLoading = true;
if (this.data.length >= this.totalRecords) {
target.enableInfiniteLoading = false;
this.loadMoreStatus = 'All records loaded';
target.isLoading = false;
return;
}
this.offset = this.data.length;
// Wire will load more data
}
// ═══════════════════════════════════════════════════════════════════════
// BULK ACTIONS
// ═══════════════════════════════════════════════════════════════════════
async handleBulkDelete() {
if (!this.hasSelection) return;
const selectedIds = this.selectedRows.map(row => row.Id);
if (!confirm(`Delete ${selectedIds.length} records?`)) {
return;
}
this.isLoading = true;
try {
await deleteRecords({ recordIds: selectedIds });
this.showToast('Success', `${selectedIds.length} records deleted`, 'success');
this.selectedRows = [];
await this.refresh();
} catch (error) {
this.showToast('Error', this.reduceErrors(error), 'error');
} finally {
this.isLoading = false;
}
}
// ═══════════════════════════════════════════════════════════════════════
// UTILITIES
// ═══════════════════════════════════════════════════════════════════════
async refresh() {
this.isLoading = true;
await refreshApex(this.wiredRecordsResult);
this.isLoading = false;
}
handleRefresh() {
this.refresh();
}
showToast(title, message, variant) {
this.dispatchEvent(new ShowToastEvent({ title, message, variant }));
}
reduceErrors(errors) {
if (!Array.isArray(errors)) errors = [errors];
return errors
.filter(e => !!e)
.map(e => e.body?.message || e.message || JSON.stringify(e))
.join(', ');
}
}