I have been trying for much too long to get a create a custom data type of picklist for an LWC table. I have read the standard docs about creating an editable custom type, and reviewed the many examples like this and this.
Right now, the code below is throwing an unhandled LWC error, but there also seem to be some subtle changes in Spring 24 to how you can do this e.g.
You need to add data-inputable="true"
to the editTemplate reference, but not sure if I am doing that right since the docs don't cover that scenario.
And when I pass the picklist values to the picklist component, they have been converted to be Objects with keys and values, so unless you handle that in the setter, they don't work (which took way too long to discover)
So I am trying to work out if I am doing this wrong, if Spring 24 has broken it somehow, and ultimately to come up with a working solution I can point folks to, since many of the examples seem out of date?
So below is a minimum POC that does display a table with a picklist, but whenever I select a value in the picklist, it throws the error:
<!-- Data Table Picklist -->
<template>
<lightning-combobox name="picklist"
label={label}
value={value}
placeholder={placeholder}
options={_options}
onchange={handleChange}>
</lightning-combobox>
</template>
//Controller
import {LightningElement,api} from 'lwc';
export default class DataTablePicklist extends LightningElement {
@api label;
@api value;
@api placeholder;
@api context;
_options;
@api
get options() {
return this._options
}
set options(value) {
console.log('options in pv' + JSON.stringify(value));
if (value) {
this._options = Object.values(value);
}
}
handleChange(event) {
//show the selected value on UI
try {
let newvalue = event.detail.value;
//fire event to send context and selected value to the data table
this.dispatchEvent(new CustomEvent('picklistchanged', {
composed: true,
bubbles: true,
cancelable: true,
detail: {
data: {context: this.context, value: newvalue}
}
}));
}
catch (e) {
console.log(e);
}
}
}
//Extend Datatable
import LightningDatatable from 'lightning/datatable';
import picklistColumn from './picklistColumn.html';
import picklistStatic from './picklistStatic.html'
export default class AccountCustomTable extends LightningDatatable {
static customTypes = {
picklistColumn: {
template: picklistStatic,
editTemplate: picklistColumn,
standardCellLayout: true,
typeAttributes: ['label', 'placeholder', 'options', 'value', 'context', 'variant','name']
}
};
}
<!-- In same directory-->
<!-- picklistStatic.html -->
<template>
<div>{typeAttributes.value}</div>
</template>
<!-- picklistColumn.html -->
<template>
<c-data-table-picklist
data-inputable="true"
label={typeAttributes.label}
value={typeAttributes.value}
placeholder={typeAttributes.placeholder}
options={typeAttributes.options}
context={typeAttributes.context}>
</c-data-table-picklist>
</template>
//Implement custom table:
<!-- Account Table Poc -->
<template>
<lightning-card title="Test Table">
<template if:true={showTable}>
<c-account-custom-table
key-field="Id"
data={accountData}
columns={columns}
onpicklistchanged={handlePicklistChanged}
onvalueselect={handleSelection}
draft-values={draftValues}
oncellchange={handleCellChange}
onsave={handleSave}
oncancel={handleCancel}
hide-checkbox-column>
</c-account-custom-table>
</template>
</lightning-card>
</template>
import { LightningElement, track, wire } from 'lwc';
import getAccounts2 from '@salesforce/apex/LWCMethods.getAccounts2';
import ACCOUNT_OBJEECT from '@salesforce/schema/Account';
import ACCOUNT_TYPE_FIELD from '@salesforce/schema/Account.Type';
import { getPicklistValues, getObjectInfo } from 'lightning/uiObjectInfoApi';
export default class AccountTablePoc extends LightningElement {
@wire(getObjectInfo, { objectApiName: ACCOUNT_OBJEECT })
objectInfo;
@track pickListOptions = [];
draftValues = [];
accountData;
//fetch picklist options
@wire(getPicklistValues, {
recordTypeId: "$objectInfo.data.defaultRecordTypeId",
fieldApiName: ACCOUNT_TYPE_FIELD
})
wirePickList({ error, data }) {
if (data) {
this.pickListOptions = data.values;
this.queryAccountData();
} else if (error) {
console.log(error);
}
}
get columns() {
return [
{label: 'Name', fieldName: 'Name', editable: true},
{label: 'Phone', fieldName: 'Phone', type: 'phone', editable: true},
/* { label: 'Type', fieldName: 'Type', type: 'text', editable: true },*/
{
label: 'Type', fieldName: 'Type', type: 'picklistColumn', editable: true,
typeAttributes: {
placeholder: 'Choose Type',
options: this.pickListOptions,
value: {fieldName: 'Type'}, // default value for picklist,
context: {fieldName: 'Id'} // binding account Id with context variable to be returned back
}
}
];
}
get showTable() {
if (this.accountData) {
if (this.pickListOptions) {
return true;
}
}
return false;
}
queryAccountData() {
getAccounts2()
.then((result) => {
this.accountData = [...result];
})
.catch((error) => {
this.error = error;
});
}
handleCellChange(event) {
console.log('cell change');
this.loading = true;
let draftValue = event.detail.draftValues[0];
console.log(draftValue);
let tempValues = JSON.parse(JSON.stringify(this.draftValues));
if (tempValues.length > 0) {
for (let i = 0; i < tempValues.length; i++) {
if (tempValues[i].Id == draftValue.Id) {
tempValues[i] = Object.assign(tempValues[i], draftValue);
}
}
}
else {
tempValues.push(draftValue);
}
this.draftValues = tempValues;
this.loading = false;
}
handleSelection(event) {
console.log(event.detail.value);
}
handleSave(event) {
}
handleCancel(event) {
}
handlePicklistChanged(event) {
event.stopPropagation();
let dataReceieved = event.detail.data;
console.log('dataReceieved' + JSON.stringify(dataReceieved));
}
}
public with sharing class LWCMethods {
@AuraEnabled(cacheable=false)
public static List<Account> getAccounts2() {
return [
SELECT Id,
Name,
Phone,
Type
FROM Account
WHERE Type != ''
LIMIT 20
];
}
}