5

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
        ];
    }

}

1 Answer 1

3

So I worked with support to understand the errors, and they determined that if I did this fix for the focus on combox box, by adding an onfocus handler, it should fix- and sure enough, it does now work. You can pass in the options directly, of via the fieldname option - both work in this example, but the fieldname option is commented out:

Extended 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']
        }
    };

}
<!-- picklistStatic -->
<template>
    <div>{typeAttributes.value}</div>
</template>


<!-- picklistColumn-->

<template>
        <!-- data-inputable="true"-->
        <c-data-table-picklist
                data-inputable="true"
                label={typeAttributes.label}
                value={editedValue}
                placeholder={typeAttributes.placeholder}
                options={typeAttributes.options}
                context={typeAttributes.context}>
        </c-data-table-picklist>

</template>

<c-data-table-picklist

<template>
        <lightning-combobox name="picklist"
                            label={label}
                            value={_value}
                            placeholder={placeholder}
                            options={_options}
                            onfocus={focus}
                            onchange={handleChange}>
        </lightning-combobox>
</template>

//Controller

import {LightningElement,api} from 'lwc';

export default class DataTablePicklist extends LightningElement {
    @api label;
    @api placeholder;
    @api context;
    _options;
    _value;

    @api
    get options() {
        return this._options
    }
    set options(value) {
        if (value) {
            this._options = Object.values(value);
        }
    }

    @api
    get value() {
        return this._value
    }
    set value(value) {
        if (value) {
            this._value = value;
        }
    }

    @api
    get validity() {
        return this.template.querySelector('lightning-combobox').validity;
    }
    @api
    showHelpMessageIfInvalid() {
        this.template.querySelector('lightning-combobox').showHelpMessageIfInvalid();
    }
    @api
    focus() {
        this.template.querySelector('lightning-combobox').focus();
    }

    handleChange(event) {
        try {
            this._value = event.detail.value;
        }
        catch (e) {
            console.log(e);
        }
    }
}

Actual Datatable

<template>
   <lightning-card title="Test Table">
    <template if:true={accountData}>
        <c-account-custom-table
                key-field="Id"
                data={accountData}
                columns={columns}
                draft-values={draftValues}
                oncellchange={handleCellChange}
                onsave={handleSave}
                oncancel={handleCancel}
                hide-checkbox-column="true"
        >
        </c-account-custom-table>
    </template>
   </lightning-card>
</template>

Controller

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;
accountDataOriginal;

//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: '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
            }
        }
    ];
}

queryAccountData() {
    getAccounts2()
        .then((result) => {
            this.accountData = [...result];
            this.accountDataOriginal = result.map(a => ({...a}));
               /* this.accountData.forEach( element => {
                        element.pickListOptions = this.pickListOptions
                })*/
        })
        .catch((error) => {
            this.error = error;
        });
    }

handleCellChange(event) {
    let draftValues = event.detail.draftValues;
    draftValues.forEach(ele=>{
        this.updateDraftValues(ele);
    })
}
//This can be cleaned up with ES6 functions but works for now
updateDraftValues(updateItem) {
    let draftValueChanged = false;
    let copyDraftValues = [...this.draftValues];
    copyDraftValues.forEach(item => {
        if (item.Id === updateItem.Id) {
                item.Type = updateItem.Type;
            draftValueChanged = true;
        }
    });

    if (draftValueChanged) {
        this.draftValues = [...copyDraftValues];
    } else {
        this.draftValues = [...copyDraftValues, updateItem];
    }

    this.draftValues.forEach(df => {
        this.accountData.forEach(item => {
        if (item.Id === df.Id) {
                item.Type = df.Type;
            }
        });
    });
 }

handleSave(event) {
    console.log('ready to save' + JSON.stringify(this.draftValues));
}

handleCancel(event) {
    this.draftValues = [];
    console.log(JSON.stringify(this.accountDataOriginal));
    this.accountData = [...this.accountDataOriginal];
}

}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .