Lookup Field in LWC Datatable Inline Edit

by Rijwan Mohmmed
30 comments
lookup-field-in-lwc-datatable-inline-edit

Hello friends, today we will discuss Lookup Field in LWC Datatable Inline Edit Salesforce. We all know the lookup field is not supported in LWC Datatable, but today I will create a custom type for the lookup field and will fit in our standard LWC Datatable.

Also, check this: Cookies in LWC Salesforce

lookup-field-in-lwc-datatable-inline-edit-output

Key Highlights :

  1. Create a custom type column which show lookup field.
  2. Datatable work very flexible with lookup.
  3. Can create multiple lookup fields.
  4. Lookup looks same like standard lookup.
  5. Can update the records.
  6. We can show/hide this lookup field by the pen icon.

Process & Code :

AccountDataController.cls :

public class AccountDataController {

    @AuraEnabled (cacheable=true)
    public static List<Contact> fetchContact(){
        return [SELECT Id, Name, Account.Name, AccountId, Email 
                FROM Contact LIMIT 10];       
    }
}

1. lookupColumn component :

lookupColumn.html :

<template>
<div class="lookupSection" id="lookup">
    <div if:true={showLookup} class="lookup-section">
        <div tabindex="0" class="container">
            <lightning-record-edit-form object-api-name={object}>
                <lightning-input-field 
                    class="slds-popover slds-popover_edit slds-popover__body"
                    field-name={fieldName} 
                    value={value} 
                    variant='label-hidden'
                    onchange={handleChange}
                    data-id="input"
                    >
                </lightning-input-field>
            </lightning-record-edit-form>
        </div>
    </div>

    <div if:false={showLookup} class="slds-table_edit_container slds-is-relative">
        <span class="slds-grid slds-grid_align-spread slds-cell-edit slds-align_absolute-center">
            <span class="slds-truncate" title={lookupName}>
                <lightning-formatted-url value={lookupValue} label={lookupName} target={target}>
                </lightning-formatted-url>
            </span>
            <button data-id={context} class="slds-button slds-button_icon slds-cell-edit__button slds-m-left_x-small" tabindex="-1"
                title="Edit" onclick={handleClick}>
                <svg class="slds-button__icon slds-button__icon_hint slds-button__icon_lock slds-button__icon_small slds-button__icon_edit slds-icon slds-icon-text-default slds-icon_xx-small"
                    aria-hidden="true">
                    <use xlink:href="/_slds/icons/utility-sprite/svg/symbols.svg?cache=9.37.1#edit"></use>
                </svg>
                <span class="slds-assistive-text">Edit</span>
            </button>
        </span>
    </div>
</div>
</template>

lookupColumn.Js: In this component, I used a static resource LWCDatatablePicklistUnzip and Upload this as a static resource and the name will be LWCDatatablePicklist.

import { LightningElement, api, track, wire } from 'lwc';
import { loadStyle } from 'lightning/platformResourceLoader';
import LWCDatatablePicklist from '@salesforce/resourceUrl/LWCDatatablePicklist';
import { getRecord } from "lightning/uiRecordApi";

export default class LookupColumn extends LightningElement {
    @api value;
    @api fieldName;
    @api object;
    @api context;
    @api name;
    @api fields;
    @api target;
    @track showLookup = false;

    //get the sobject record info with fields to show as url navigation text
    @wire(getRecord, { recordId: '$value', fields: '$fields' })
    record;

    getFieldName() {
        let fieldName = this.fields[0];
        fieldName = fieldName.substring(fieldName.lastIndexOf('.') + 1, fieldName.length);
        return fieldName;
    }

   //label of formatted url
    get lookupName() {
        console.log(this.record.data);
        return (this.value != undefined && this.value != '' && this.record.data != null) ?  this.record.data.fields[this.getFieldName()].value : '';
    }

    //value of formatted url
    get lookupValue() {
        return (this.value != undefined && this.value != '' && this.record.data != null && this.record.data.fields[this.getFieldName()].value) ? '/' + this.value : '';
    }

    renderedCallback() {
        Promise.all([
            loadStyle(this, LWCDatatablePicklist),
        ]).then(() => { });

        let container = this.template.querySelector('div.container');
        container?.focus();

        window.addEventListener('click', (evt) => {
           if(container == undefined){
               this.showLookup = false;
           }
        });
    }

    /*closeLookup(event) {
        if (!event.currentTarget.contains(event.relatedTarget)) {
            this.showLookup = false;
        }
    }*/

    handleChange(event) {
        //show the selected value on UI
        this.value = event.detail.value[0];
        if(this.value == undefined){
            this.record.data = null;
        }
        //fire event to send context and selected value to the data table
        this.dispatchEvent(new CustomEvent('lookupchanged', {
            composed: true,
            bubbles: true,
            cancelable: true,
            detail: {
                data: { context: this.context, value: this.value }
            }
        }));
    }

    handleClick(event) {
        //wait to close all other lookup edit 
        setTimeout(() => {
            this.showLookup = true;
        }, 100);
    }
}

lookupColumn.css :

.lookup-section{
    margin-top: -0.6rem;
    margin-left: -0.5rem;
    position: absolute!important;
	z-index: 9999999999999999999999;
}

.lookup-section .slds-dropdown{
    position: fixed !important;
    max-height: 120px;
    max-width: fit-content;
    overflow: auto;
}

2. LWCLookupCustomDatatableType Component : We create a custom type Datatable here which extends standard LWC Datatable. Also, create an extra HTML file lookupColumn.html, so we can put the lookup component here. Below image of the structure.

lookup-field-in-lwc-datatable-inline-edit-stracture-custom-datatable
lookup-fields-in-lwc-datatable-inline-edit-stracture-custom-datatable

lWCLookupCustomDatatableType.HTML :

<template></template>

lookupColumn.Html :

<template>
	<c-lookup-column value={typeAttributes.value} field-name={typeAttributes.fieldName}
		object={typeAttributes.object} context={typeAttributes.context} name={typeAttributes.name}
		fields={typeAttributes.fields} target={typeAttributes.target}>
	</c-lookup-column>
</template>

lWCLookupCustomDatatableType.JS :

import LightningDatatable from 'lightning/datatable';
import lookupColumn from './lookupColumn.html';

export default class LWCLookupCustomDatatableType extends LightningDatatable {
    static customTypes = {
        lookupColumn: {
            template: lookupColumn,
            standardCellLayout: true,
            typeAttributes: ['value', 'fieldName', 'object', 'context', 'name', 'fields', 'target']
        }
    };
}

3. LWCDatatableWithLookup Component: Now we will create the last final LWC component

LWCDatatableWithLookup.Html :

<template>
    <!-- header -->
    <div class="slds-tabs_card">
		<div class="slds-page-header">
			<div class="slds-page-header__row">
				<div class="slds-page-header__col-title">
					<div class="slds-media">
						<div class="slds-media__figure">
							<span class="slds-icon_container slds-icon-standard-opportunity">
								 <lightning-icon icon-name="standard:recipe" alternative-text="recipe" title="recipe"></lightning-icon>
                            </span>
						</div>
						<div class="slds-media__body">
							<div class="slds-page-header__name">
								<div class="slds-page-header__name-title">
									<h1>
										<span>Lookup Field In LWC Inline Datatable Edit</span>
										<span class="slds-page-header__title slds-truncate" title="Recently Viewed">TechDicer</span>
									</h1>
								</div>
							</div>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div> <br/>
    <!-- /header -->

    <!-- create folder card -->
    <lightning-card  variant="Narrow"  title="Lookup Field In LWC Inline Datatable Edit" icon-name="standard:folder" class="cardSpinner">
        <!-- loader -->
        <div if:true={showSpinner}>
            <lightning-spinner
                alternative-text="Loading..." variant="brand">
            </lightning-spinner>
        </div>
        <!-----/loader-------->
        <div class="slds-var-p-around_small">
            <template if:true={data}>
                <c-l-w-c-lookup-custom-datatable-type
                    key-field="Id" 
                    data={data} 
                    columns={columns} 
                    onlookupchanged={lookupChanged} 
                    onvalueselect={handleSelection}
                    draft-values={draftValues} 
                    oncellchange={handleCellChange}
                    onsave={handleSave}
                    oncancel={handleCancel}
                    hide-checkbox-column>
                </c-l-w-c-lookup-custom-datatable-type>
            </template>
        </div>
    </lightning-card>
</template>

LWCDatatableWithLookup.JS :

import { LightningElement, track, wire } from 'lwc';
import fetchContact from '@salesforce/apex/AccountDataController.fetchContact';
import { updateRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { refreshApex } from '@salesforce/apex';

const columns = [
    { label: 'Name', fieldName: 'Name', editable: true },
    { label: 'Email', fieldName: 'Email', type: 'email', editable: true },
    {
        label: 'Account Name',
        fieldName: 'AccountId', //lookup API field name 
        type: 'lookupColumn',
        typeAttributes: {
            object: 'Contact', //object name which have lookup field
            fieldName: 'AccountId',  //lookup API field name 
            value: { fieldName: 'AccountId' },  //lookup API field name 
            context: { fieldName: 'Id' }, 
            name: 'Account',  //lookup object API Name 
            fields: ['Account.Name'], //lookup objectAPIName.Name
            target: '_self'
        },
        editable: false,
    },
    /*{
        label: 'User',
        fieldName: 'User__c',
        type: 'lookupColumn',
        typeAttributes: {
          object: 'Contact',
          fieldName: 'User__c',
          value: { fieldName: 'User__c' },
          context: { fieldName: 'Id' },
          name: 'User',
          fields: ['User.Name'],
          target: '_self'
        },
        editable: false,
      }*/ //Note : If you have custom field then you can use this
]

export default class LWCDatatableWithLookup extends LightningElement {
    columns = columns;
    showSpinner = false;
    @track data = [];
    @track contactData;
    @track draftValues = [];
    lastSavedData = [];

    //here I pass picklist option so that this wire method call after above method
    @wire(fetchContact, {})
    wireData(result) {
        this.contactData = result;
        if (result.data) {
            this.data = JSON.parse(JSON.stringify(result.data));
            console.log(this.data);
            this.data.forEach(ele => {
                ele.accountLink = ele.AccountId != undefined ? '/' + ele.AccountId : '';
                ele.accountName = ele.AccountId != undefined ? ele.Account.Name : '';
            })

            this.lastSavedData = JSON.parse(JSON.stringify(this.data));

        } else if (result.error) {
            console.log(result.error);
            this.data = undefined;
        }
    };

    updateDataValues(updateItem) {
        let copyData = JSON.parse(JSON.stringify(this.data));

        copyData.forEach(item => {
            if (item.Id === updateItem.Id) {
                for (let field in updateItem) {
                    item[field] = updateItem[field];
                }
            }
        });

        //write changes back to original data
        this.data = [...copyData];
    }

    updateDraftValues(updateItem) {
        let draftValueChanged = false;
        let copyDraftValues = [...this.draftValues];
        //store changed value to do operations
        //on save. This will enable inline editing &
        //show standard cancel & save button
        copyDraftValues.forEach(item => {
            if (item.Id === updateItem.Id) {
                for (let field in updateItem) {
                    item[field] = updateItem[field];
                }
                draftValueChanged = true;
            }
        });

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

    //listener handler to get the context and data
    //updates datatable, here I used AccountId you can use your look field API name
    lookupChanged(event) {
        console.log(event.detail.data);
        event.stopPropagation();
        let dataRecieved = event.detail.data;
        let accountIdVal = dataRecieved.value != undefined ? dataRecieved.value : null;
        let updatedItem = { Id: dataRecieved.context, AccountId: accountIdVal  };
        console.log(updatedItem);
        this.updateDraftValues(updatedItem);
        this.updateDataValues(updatedItem);
    }

    //handler to handle cell changes & update values in draft values
    handleCellChange(event) {
        this.updateDraftValues(event.detail.draftValues[0]);
    }

    handleSave(event) {
        this.showSpinner = true;
        this.saveDraftValues = this.draftValues;

        const recordInputs = this.saveDraftValues.slice().map(draft => {
            const fields = Object.assign({}, draft);
            return { fields };
        });

        // Updateing the records using the UiRecordAPi
        const promises = recordInputs.map(recordInput => updateRecord(recordInput));
        Promise.all(promises).then(res => {
            this.showToast('Success', 'Records Updated Successfully!', 'success', 'dismissable');
            this.draftValues = [];
            return this.refresh();
        }).catch(error => {
            console.log(error);
            this.showToast('Error', 'An Error Occured!!', 'error', 'dismissable');
        }).finally(() => {
            this.draftValues = [];
            this.showSpinner = false;
        });
    }

    handleCancel(event) {
        //remove draftValues & revert data changes
        this.data = JSON.parse(JSON.stringify(this.lastSavedData));
        this.draftValues = [];
    }

    showToast(title, message, variant, mode) {
        const evt = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: mode
        });
        this.dispatchEvent(evt);
    }

    // This function is used to refresh the table once data updated
    async refresh() {
        await refreshApex(this.accountData);
    }
}

LWCDatatableWithLookup.CSS :

.cardSpinner{
    position: relative;
}

lWCDatatableWithLookup.js-meta.xml :

<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
	<apiVersion>54.0</apiVersion>
	<isExposed>true</isExposed>
	<targets>
		<target>lightning__HomePage</target>
	</targets>
</LightningComponentBundle>

Output :

lookup-field-in-lwc-datatable-inline-edit
lookup-fields-in-lwc-datatable-inline-edit

Reference :

  1. LWC Datatable
  2. Inline Editing in lightning-datatable in LWC Salesforce

You may also like

30 comments

GK October 20, 2022 - 8:26 am

Can you tell me how you entered data for columns?

Reply
Preethi October 20, 2022 - 2:24 pm

The code is not working for custom field. what needs to be changed

Reply
Rijwan Mohmmed October 23, 2022 - 5:47 am

Can you please share me LWCDatatableWithLookup.JS file columns code. I have update the code for custom field you can check this.

Reply
SP October 20, 2022 - 2:25 pm

The code is not working for custom field. what needs to be changed

Reply
Salman October 31, 2022 - 12:32 pm

I am unable to pre-populate the data on custom lookup field in the component

Reply
Rijwan Mohmmed November 1, 2022 - 3:26 am

check your JSON columns, or send me I will check this.

Reply
Salman October 31, 2022 - 12:34 pm

This is how I am doing right now
{
label: ‘Suggested Allocation’,
fieldName: ‘submissionExecutiveId’,
type: ‘lookupColumn’,
typeAttributes: {
object: ‘Case’,
fieldName: ‘Submissions_Executive__c’,
value: { fieldName: ‘Submissions_Executive__c’ },
context: { fieldName: ‘Id’ },
name: ‘User’,
fields: [‘User.Name’],
target: ‘_self’
},
editable: false,
}

rec.submissionExecutiveId = ‘/’ + item.Id;
rec.submissionExecutiveName = item.Name;
recordsWithSubmissionExecutive.push(rec);

Reply
Rijwan Mohmmed November 1, 2022 - 4:00 am

check line 17 console in lookupColumn.JS. Also check error in console.

Reply
Salman November 1, 2022 - 7:33 am

It is coming as a blank I have tried many ways

rec.submissionExecutiveId = ‘/’ + item.Id;
rec.submissionExecutiveName = item.Name;
rec.submissionExecutiveLink = ‘/’ + item.Id;
rec.submissionExecutiveName = item.Name;
rec.userLink = ‘/’ + item.Id;
rec.userName = item.Name;
rec.Submissions_Executive__cLink = ‘/’ + item.Id;
rec.Submissions_Executive__cName = item.Name
recordsWithSubmissionExecutive.push(rec);

{
label: ‘Suggested Allocation’,
fieldName: ‘userLink’,
type: ‘lookupColumn’,
typeAttributes: {
object: ‘Case’,
fieldName: ‘Submissions_Executive__c’,
value: { fieldName: ‘Submissions_Executive__c’ },
context: { fieldName: ‘Id’ },
name: ‘User’,
fields: [‘User.Name’],
target: ‘_self’,
label: {fieldName: ‘userName’}
},
editable: false,
}

But none of them are working and I tried to change the field with the standard field it worked well maybe there is some issue related to mapping just like you are doing for the account i.e accountLink

Reply
Rijwan Mohmmed November 1, 2022 - 9:16 am

connect me on linkedin

Reply
Salman November 1, 2022 - 10:11 am

Thank you so much Rijwan for solving my problem. It is much appreciated.

Sameer November 1, 2022 - 7:15 am

Data is only pre-populating for standard field do you know why it not working for custom field?

Reply
Rijwan Mohmmed November 1, 2022 - 9:10 am

Check your lookup JSON column. send me your JS typeAttributes
{
label: ‘User’,
fieldName: ‘User__c’,
type: ‘lookupColumn’,
typeAttributes: {
object: ‘Contact’,
fieldName: ‘User__c’,
value: { fieldName: ‘User__c’ },
context: { fieldName: ‘Id’ },
name: ‘User’,
fields: [‘User.Name’],
target: ‘_self’
},
editable: false,
}

Reply
Saravjeet singh November 10, 2022 - 2:49 pm

I am also facing the same issue, its not working for custom lookup field.

Here is the code.

{
label: ‘Office’,
fieldName: ‘Office__c’,
type: ‘lookupColumn’,
typeAttributes: {
object: ‘Team__c’,
fieldName: ‘Office__c’,
value: { fieldName: ‘Office__c’ },
context: { fieldName: ‘Id’ },
name: ‘Office’,
fields: [‘Office.Name’],
target: ‘_self’
},
editable: false,
}

Reply
Rijwan Mohmmed November 11, 2022 - 10:23 am

Use Below one

{
label: ‘Office’,
fieldName: ‘Office__c’,
type: ‘lookupColumn’,
typeAttributes: {
object: ‘Team__c’,
fieldName: ‘Office__c’,
value: { fieldName: ‘Office__c’ },
context: { fieldName: ‘Id’ },
name: ‘Office__c’,
fields: [‘Office__c.Name’],
target: ‘_self’
},
editable: false,
}

Reply
Saravjeet Singh November 11, 2022 - 11:55 am

already tried that…it did not work.

And even tried this… below also with fields: [‘Office__r.Name’], this also did not work

{
label: ‘Office’,
fieldName: ‘Office__c’,
type: ‘lookupColumn’,
typeAttributes: {
object: ‘Team__c’,
fieldName: ‘Office__c’,
value: { fieldName: ‘Office__c’ },
context: { fieldName: ‘Id’ },
name: ‘Office__c’,
fields: [‘Office__r.Name’],
target: ‘_self’
},
editable: false,
}

Reply
Rijwan Mohmmed November 12, 2022 - 2:45 am

Connect me on LinkedIn

Reply
Saravjeet Singh November 14, 2022 - 12:10 pm

I have sent request. please accept.

Sean November 17, 2022 - 6:23 pm

Can this be modified to work with Accounts and assigning Account owner as lookup?

Reply
Rijwan Mohmmed November 18, 2022 - 3:22 pm

Hi Sean,
Yes you can.

Reply
Sean November 22, 2022 - 2:50 pm

Thanks Rijwan,

I have been spending some time trying to modify this unassisted and am getting tripped up a little on the lookup. I was successful in updating the class to pull the proper Account data and display the Account Owner, however, when I try to edit the Account owner my lookup displays a red cancel image when hovering the lookup.

{
label: ‘Account Owner’,
fieldName: ‘OwnerId’,
type: ‘lookupColumn’,
typeAttributes: {
object: ‘Account’,
fieldName: ‘OwnerId’,
value: { fieldName: ‘OwnerId’ },
context: { fieldName: ‘Id’ },
name: ‘User’,
fields: [‘Owner.Name’],
target: ‘_self’
},
editable: false,
}

Above is my modified data type for displaying the Account Owner. Any ideas what may be happening? Is it trying to modify an object that can not be accessed?

Reply
Rijwan Mohmmed November 22, 2022 - 4:43 pm

Hi Sean,
fields: [‘User.Name’]
******************************
{
label: ‘Account Owner’,
fieldName: ‘OwnerId’,
type: ‘lookupColumn’,
typeAttributes: {
object: ‘Account’,
fieldName: ‘OwnerId’,
value: { fieldName: ‘OwnerId’ },
context: { fieldName: ‘Id’ },
name: ‘User’,
fields: [‘User.Name’],
target: ‘_self’
},
editable: false,
}

Reply
Rijwan Mohmmed November 22, 2022 - 4:45 pm

I used this fields for retrieve the object record name, so the object name was not corrected in your case that’s why you are getting an issue

Reply
Sean November 22, 2022 - 4:57 pm

Thank you, I was able to debug and saw the response error right before your reply. This does display the account owner names correctly, but the lookup displays my user name as the default and does not let me select a new name.

Reply
Sean November 22, 2022 - 5:42 pm

To clarify, my lightning input for the lookup has the disabled property attached to it when it becomes active from clicking the pen icon.

Sean November 22, 2022 - 4:58 pm

Thank you, I was able to debug and saw the response error right before your reply. This does display the account owner names correctly, but the lookup displays my user name as the default and does not let me select a new name.

Reply
B Rod December 1, 2022 - 5:23 am

Hi Rijwan Mohmmed, thank you very much for it. I just have an issue with the cancel button when I enter a new value in the lookup field which was blank and click on the cancel button, it doesn’t clear my lookup field so it still with the old value. I tried debugging to resolve it but I couldn’t find where the error is. Can you help me?

Reply
Rijwan Mohmmed December 1, 2022 - 4:22 pm

Hi B Rod, can you please me screen shot

Reply
Rijwan Mohmmed December 1, 2022 - 4:50 pm

Hi B Rod,
I updated the code and also your issue is fixed. I have updated in lookupColumn.Js file.
Thank you for posting a bug.

Reply
Sudheer December 7, 2022 - 6:15 am

Hi Rizwan, I am trying to use this for searching in a list of values. My requirement is to add a column in lightning data table which shows suggestions to select based on the characters entered in the input.

Reply

Leave a Comment