You need to sign in to do that
Don't have an account?
Lwc Seekar
LWC Form Custom Save (Need some fix) simple one
I would like to save the data filled in the form as shown in below. How ever i am not understanding how to do.
I have a quick action on Account object , on click of quick action button customer opens the form as shown below .
Here account name gets autopopulated and additional note needs to be filled by user and on hit on save button this needs to get saved in "Customer object"
1.Account name : its a custom dynamic lookup
where we can search the record .
code :
<template>
<lightning-card>
<header class="slds-modal__header" data-aura-rendered-by="142:2755;a">
<h2 class="slds-modal__title" data-aura-rendered-by="143:2755;a"><b>Customer</b></h2>
</header>
<div class="slds-m-around--xx-large ">
<div class="form-group">
<c-custom-Lookup-Lwc icon-name="standard:account"
s-object-api-name="Account"
label="Account Name "
onlookupupdate={lookupRecord}
placeholder="Account here..."></c-custom-Lookup-Lwc>
</div>
<div class="form-group">
<lightning-textarea
max-length="100"
message-when-value-missing="Please enter the description"
label="Additional Note"
required
value={description}
onchange={handleChange}
class="textAreaCSS"></lightning-textarea>
</div>
<footer class="slds-modal__footer" data-aura-rendered-by="145:2889;a">
<div>
<lightning-button variant="neutral" label="Cancel" onclick={closeAction} ></lightning-button>
<lightning-button variant="brand" label="Save" onclick={saveAction} ></lightning-button>
</div>
</footer>
</div>
</lightning-card>
</template>
=========================================
import { LightningElement,wire,api,track} from 'lwc';
import { CloseActionScreenEvent } from 'lightning/actions';
export default class CustomerQA extends LightningElement {
@api records;
@api error;
lookupRecord(event){
alert('Selected Record Value on Parent Component is ' + JSON.stringify(event.detail.selectedRecord));
}
closeAction(){
this.dispatchEvent(new CloseActionScreenEvent());
}
saveAction(event){
}
}
=======================================================
customlookuplwc.html
<template>
<div class="slds-form-element" onmouseleave={toggleResult} data-source="lookupContainer">
<div class="slds-combobox_container slds-has-selection">
<label class="slds-form-element__label" for="combobox-id-1">{label}</label>
<div class="lookupInputContainer slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click" aria-expanded="false" aria-haspopup="listbox" role="combobox">
<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_left-right" role="none">
<div class="searchBoxWrapper slds-show">
<!--Lookup Input Field-->
<lightning-input
type="search"
data-source="searchInputField"
onclick={toggleResult}
onchange={handleKeyChange}
is-loading={isSearchLoading}
value={searchKey}
variant="label-hidden"
placeholder={placeholder}
></lightning-input>
</div>
<!--Lookup Selected record pill container start-->
<div class="pillDiv slds-hide">
<span class="slds-icon_container slds-combobox__input-entity-icon">
<lightning-icon icon-name={iconName} size="x-small" alternative-text="icon"></lightning-icon>
</span>
<input type="text"
id="combobox-id-1"
value={selectedRecord.Name}
class="slds-input slds-combobox__input slds-combobox__input-value"
readonly
/>
<button class="slds-button slds-button_icon slds-input__icon slds-input__icon_right" title="Remove selected option">
<lightning-icon icon-name="utility:close" size="x-small" alternative-text="close icon" onclick={handleRemove}></lightning-icon>
</button>
</div>
</div>
<!-- lookup search result part start-->
<div style="margin-top:0px" id="listbox-id-5" class="slds-dropdown slds-dropdown_length-with-icon-7 slds-dropdown_fluid" role="listbox">
<ul class="slds-listbox slds-listbox_vertical" role="presentation">
<template for:each={lstResult} for:item="obj">
<li key={obj.Id} role="presentation" class="slds-listbox__item">
<div data-recid={obj.Id} onclick={handelSelectedRecord} class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta" role="option">
<span style="pointer-events: none;" class="slds-media__figure slds-listbox__option-icon" >
<span class="slds-icon_container" >
<lightning-icon icon-name={iconName} size="small" alternative-text="icon" ></lightning-icon>
</span>
</span>
<span style="pointer-events: none;" class="slds-media__body" >
<span class="slds-listbox__option-text slds-listbox__option-text_entity">{obj.Name}</span>
</span>
</div>
</li>
</template>
<!--ERROR msg, if there is no records..-->
<template if:false={hasRecords}>
<li class="slds-listbox__item" style="text-align: center; font-weight: bold;">No Records Found....</li>
</template>
</ul>
</div>
</div>
</div>
</div>
</template>
=============================================
customlookuplwc.js:
import { LightningElement, api, wire } from 'lwc';
// import apex method from salesforce module
import fetchLookupData from '@salesforce/apex/CustomLookupLwcController.fetchLookupData';
import fetchDefaultRecord from '@salesforce/apex/CustomLookupLwcController.fetchDefaultRecord';
import { CurrentPageReference } from 'lightning/navigation';
const DELAY = 300; // dealy apex callout timing in miliseconds
// Start
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';
// end
export default class CustomLookupLwc extends LightningElement {
// public properties with initial default values
@api label = 'custom lookup label';
@api placeholder = 'search...Acc...';
@api iconName = 'standard:account';
@api sObjectApiName = 'Account';
@api defaultRecordId = '';
@api recordId;
// private properties
lstResult = []; // to store list of returned records
hasRecords = true;
searchKey = ''; // to store input field value
isSearchLoading = false; // to control loading spinner
delayTimeout;
selectedRecord = {}; // to store selected lookup record in object formate
//getting record id from below function
@wire(CurrentPageReference)
getStateParameters(currentPageReference) {
if (currentPageReference) {
this.recordId = currentPageReference.state.recordId;
console.log('this.recordId==35===' + this.recordId);
}
}
// initial function to populate default selected lookup record if defaultRecordId provided
connectedCallback() {
try{
if (this.recordId != '') {
fetchDefaultRecord({ recordId: this.recordId, 'sObjectApiName': this.sObjectApiName })
.then((result) => {
console.log('result===== ',result);
if (result != null) {
this.searchKey = result.Name;
console.log('this.searchKey===== ',this.searchKey);
this.selectedRecord = result;
this.handelSelectRecordHelper(); // helper function to show/hide lookup result container on UI
//creating a custom event to pass the Account name to parent
const answerEvent = new CustomEvent("accname", { detail: this.searchKey });
this.dispatchEvent(answerEvent);
}
})
.catch((error) => {
this.error = error;
this.selectedRecord = {};
console.error('OUTPUT: ', error);
});
}
} catch (error) {
console.error('OUTPUT: ', error);
}
}
// wire function property to fetch search record based on user input
@wire(fetchLookupData, { searchKey: '$searchKey', sObjectApiName: '$sObjectApiName' })
searchResult(value) {
const { data, error } = value; // destructure the provisioned value
this.isSearchLoading = false;
if (data) {
this.hasRecords = data.length == 0 ? false : true;
this.lstResult = JSON.parse(JSON.stringify(data));
}
else if (error) {
console.log('(error---> ' + JSON.stringify(error));
}
};
// update searchKey property on input field change
handleKeyChange(event) {
// Debouncing this method: Do not update the reactive property as long as this function is
// being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
this.isSearchLoading = true;
window.clearTimeout(this.delayTimeout);
const searchKey = event.target.value;
this.delayTimeout = setTimeout(() => {
this.searchKey = searchKey;
}, DELAY);
}
// method to toggle lookup result section on UI
toggleResult(event) {
const lookupInputContainer = this.template.querySelector('.lookupInputContainer');
const clsList = lookupInputContainer.classList;
const whichEvent = event.target.getAttribute('data-source');
switch (whichEvent) {
case 'searchInputField':
clsList.add('slds-is-open');
break;
case 'lookupContainer':
clsList.remove('slds-is-open');
break;
}
}
// method to clear selected lookup record
handleRemove() {
console.log('handleRemove');
this.searchKey = '';
this.selectedRecord = {};
this.lookupUpdatehandler(undefined); // update value on parent component as well from helper function
// remove selected pill and display input field again
const searchBoxWrapper = this.template.querySelector('.searchBoxWrapper');
searchBoxWrapper.classList.remove('slds-hide');
searchBoxWrapper.classList.add('slds-show');
const pillDiv = this.template.querySelector('.pillDiv');
pillDiv.classList.remove('slds-show');
pillDiv.classList.add('slds-hide');
}
// method to update selected record from search result
handelSelectedRecord(event) {
var objId = event.target.getAttribute('data-recid'); // get selected record Id
this.selectedRecord = this.lstResult.find(data => data.Id === objId); // find selected record from list
this.lookupUpdatehandler(this.selectedRecord); // update value on parent component as well from helper function
this.handelSelectRecordHelper(); // helper function to show/hide lookup result container on UI
}
/*COMMON HELPER METHOD STARTED*/
handelSelectRecordHelper() {
this.template.querySelector('.lookupInputContainer').classList.remove('slds-is-open');
const searchBoxWrapper = this.template.querySelector('.searchBoxWrapper');
searchBoxWrapper.classList.remove('slds-show');
searchBoxWrapper.classList.add('slds-hide');
const pillDiv = this.template.querySelector('.pillDiv');
pillDiv.classList.remove('slds-hide');
pillDiv.classList.add('slds-show');
}
// send selected lookup record to parent component using custom event
lookupUpdatehandler(value) {
const oEvent = new CustomEvent('lookupupdate',
{
'detail': { selectedRecord: value }
}
);
this.dispatchEvent(oEvent);
}
}
============
public class CustomLookupLwcController {
// Method to fetch lookup search result
@AuraEnabled(cacheable=true)
public static list<sObject> fetchLookupData(string searchKey , string sObjectApiName) {
List < sObject > returnList = new List < sObject > ();
string sWildCardText = '%' + searchKey + '%';
string sQuery = 'Select Id,Name From ' + sObjectApiName + ' Where Name Like : sWildCardText order by createdDate DESC LIMIT 5';
for (sObject obj: database.query(sQuery)) {
returnList.add(obj);
}
return returnList;
}
// Method to fetch lookup default value
@AuraEnabled
public static sObject fetchDefaultRecord(string recordId , string sObjectApiName) {
string sRecId = recordId;
string sQuery = 'Select Id,Name From ' + sObjectApiName +' Where Id =\''+ sRecId+'\' LIMIT 1' ;
for (sObject obj: database.query(sQuery)) {
return obj;
}
return null;
}
}
I have a quick action on Account object , on click of quick action button customer opens the form as shown below .
Here account name gets autopopulated and additional note needs to be filled by user and on hit on save button this needs to get saved in "Customer object"
1.Account name : its a custom dynamic lookup
where we can search the record .
code :
<template>
<lightning-card>
<header class="slds-modal__header" data-aura-rendered-by="142:2755;a">
<h2 class="slds-modal__title" data-aura-rendered-by="143:2755;a"><b>Customer</b></h2>
</header>
<div class="slds-m-around--xx-large ">
<div class="form-group">
<c-custom-Lookup-Lwc icon-name="standard:account"
s-object-api-name="Account"
label="Account Name "
onlookupupdate={lookupRecord}
placeholder="Account here..."></c-custom-Lookup-Lwc>
</div>
<div class="form-group">
<lightning-textarea
max-length="100"
message-when-value-missing="Please enter the description"
label="Additional Note"
required
value={description}
onchange={handleChange}
class="textAreaCSS"></lightning-textarea>
</div>
<footer class="slds-modal__footer" data-aura-rendered-by="145:2889;a">
<div>
<lightning-button variant="neutral" label="Cancel" onclick={closeAction} ></lightning-button>
<lightning-button variant="brand" label="Save" onclick={saveAction} ></lightning-button>
</div>
</footer>
</div>
</lightning-card>
</template>
=========================================
import { LightningElement,wire,api,track} from 'lwc';
import { CloseActionScreenEvent } from 'lightning/actions';
export default class CustomerQA extends LightningElement {
@api records;
@api error;
lookupRecord(event){
alert('Selected Record Value on Parent Component is ' + JSON.stringify(event.detail.selectedRecord));
}
closeAction(){
this.dispatchEvent(new CloseActionScreenEvent());
}
saveAction(event){
}
}
=======================================================
customlookuplwc.html
<template>
<div class="slds-form-element" onmouseleave={toggleResult} data-source="lookupContainer">
<div class="slds-combobox_container slds-has-selection">
<label class="slds-form-element__label" for="combobox-id-1">{label}</label>
<div class="lookupInputContainer slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click" aria-expanded="false" aria-haspopup="listbox" role="combobox">
<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_left-right" role="none">
<div class="searchBoxWrapper slds-show">
<!--Lookup Input Field-->
<lightning-input
type="search"
data-source="searchInputField"
onclick={toggleResult}
onchange={handleKeyChange}
is-loading={isSearchLoading}
value={searchKey}
variant="label-hidden"
placeholder={placeholder}
></lightning-input>
</div>
<!--Lookup Selected record pill container start-->
<div class="pillDiv slds-hide">
<span class="slds-icon_container slds-combobox__input-entity-icon">
<lightning-icon icon-name={iconName} size="x-small" alternative-text="icon"></lightning-icon>
</span>
<input type="text"
id="combobox-id-1"
value={selectedRecord.Name}
class="slds-input slds-combobox__input slds-combobox__input-value"
readonly
/>
<button class="slds-button slds-button_icon slds-input__icon slds-input__icon_right" title="Remove selected option">
<lightning-icon icon-name="utility:close" size="x-small" alternative-text="close icon" onclick={handleRemove}></lightning-icon>
</button>
</div>
</div>
<!-- lookup search result part start-->
<div style="margin-top:0px" id="listbox-id-5" class="slds-dropdown slds-dropdown_length-with-icon-7 slds-dropdown_fluid" role="listbox">
<ul class="slds-listbox slds-listbox_vertical" role="presentation">
<template for:each={lstResult} for:item="obj">
<li key={obj.Id} role="presentation" class="slds-listbox__item">
<div data-recid={obj.Id} onclick={handelSelectedRecord} class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta" role="option">
<span style="pointer-events: none;" class="slds-media__figure slds-listbox__option-icon" >
<span class="slds-icon_container" >
<lightning-icon icon-name={iconName} size="small" alternative-text="icon" ></lightning-icon>
</span>
</span>
<span style="pointer-events: none;" class="slds-media__body" >
<span class="slds-listbox__option-text slds-listbox__option-text_entity">{obj.Name}</span>
</span>
</div>
</li>
</template>
<!--ERROR msg, if there is no records..-->
<template if:false={hasRecords}>
<li class="slds-listbox__item" style="text-align: center; font-weight: bold;">No Records Found....</li>
</template>
</ul>
</div>
</div>
</div>
</div>
</template>
=============================================
customlookuplwc.js:
import { LightningElement, api, wire } from 'lwc';
// import apex method from salesforce module
import fetchLookupData from '@salesforce/apex/CustomLookupLwcController.fetchLookupData';
import fetchDefaultRecord from '@salesforce/apex/CustomLookupLwcController.fetchDefaultRecord';
import { CurrentPageReference } from 'lightning/navigation';
const DELAY = 300; // dealy apex callout timing in miliseconds
// Start
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';
// end
export default class CustomLookupLwc extends LightningElement {
// public properties with initial default values
@api label = 'custom lookup label';
@api placeholder = 'search...Acc...';
@api iconName = 'standard:account';
@api sObjectApiName = 'Account';
@api defaultRecordId = '';
@api recordId;
// private properties
lstResult = []; // to store list of returned records
hasRecords = true;
searchKey = ''; // to store input field value
isSearchLoading = false; // to control loading spinner
delayTimeout;
selectedRecord = {}; // to store selected lookup record in object formate
//getting record id from below function
@wire(CurrentPageReference)
getStateParameters(currentPageReference) {
if (currentPageReference) {
this.recordId = currentPageReference.state.recordId;
console.log('this.recordId==35===' + this.recordId);
}
}
// initial function to populate default selected lookup record if defaultRecordId provided
connectedCallback() {
try{
if (this.recordId != '') {
fetchDefaultRecord({ recordId: this.recordId, 'sObjectApiName': this.sObjectApiName })
.then((result) => {
console.log('result===== ',result);
if (result != null) {
this.searchKey = result.Name;
console.log('this.searchKey===== ',this.searchKey);
this.selectedRecord = result;
this.handelSelectRecordHelper(); // helper function to show/hide lookup result container on UI
//creating a custom event to pass the Account name to parent
const answerEvent = new CustomEvent("accname", { detail: this.searchKey });
this.dispatchEvent(answerEvent);
}
})
.catch((error) => {
this.error = error;
this.selectedRecord = {};
console.error('OUTPUT: ', error);
});
}
} catch (error) {
console.error('OUTPUT: ', error);
}
}
// wire function property to fetch search record based on user input
@wire(fetchLookupData, { searchKey: '$searchKey', sObjectApiName: '$sObjectApiName' })
searchResult(value) {
const { data, error } = value; // destructure the provisioned value
this.isSearchLoading = false;
if (data) {
this.hasRecords = data.length == 0 ? false : true;
this.lstResult = JSON.parse(JSON.stringify(data));
}
else if (error) {
console.log('(error---> ' + JSON.stringify(error));
}
};
// update searchKey property on input field change
handleKeyChange(event) {
// Debouncing this method: Do not update the reactive property as long as this function is
// being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
this.isSearchLoading = true;
window.clearTimeout(this.delayTimeout);
const searchKey = event.target.value;
this.delayTimeout = setTimeout(() => {
this.searchKey = searchKey;
}, DELAY);
}
// method to toggle lookup result section on UI
toggleResult(event) {
const lookupInputContainer = this.template.querySelector('.lookupInputContainer');
const clsList = lookupInputContainer.classList;
const whichEvent = event.target.getAttribute('data-source');
switch (whichEvent) {
case 'searchInputField':
clsList.add('slds-is-open');
break;
case 'lookupContainer':
clsList.remove('slds-is-open');
break;
}
}
// method to clear selected lookup record
handleRemove() {
console.log('handleRemove');
this.searchKey = '';
this.selectedRecord = {};
this.lookupUpdatehandler(undefined); // update value on parent component as well from helper function
// remove selected pill and display input field again
const searchBoxWrapper = this.template.querySelector('.searchBoxWrapper');
searchBoxWrapper.classList.remove('slds-hide');
searchBoxWrapper.classList.add('slds-show');
const pillDiv = this.template.querySelector('.pillDiv');
pillDiv.classList.remove('slds-show');
pillDiv.classList.add('slds-hide');
}
// method to update selected record from search result
handelSelectedRecord(event) {
var objId = event.target.getAttribute('data-recid'); // get selected record Id
this.selectedRecord = this.lstResult.find(data => data.Id === objId); // find selected record from list
this.lookupUpdatehandler(this.selectedRecord); // update value on parent component as well from helper function
this.handelSelectRecordHelper(); // helper function to show/hide lookup result container on UI
}
/*COMMON HELPER METHOD STARTED*/
handelSelectRecordHelper() {
this.template.querySelector('.lookupInputContainer').classList.remove('slds-is-open');
const searchBoxWrapper = this.template.querySelector('.searchBoxWrapper');
searchBoxWrapper.classList.remove('slds-show');
searchBoxWrapper.classList.add('slds-hide');
const pillDiv = this.template.querySelector('.pillDiv');
pillDiv.classList.remove('slds-hide');
pillDiv.classList.add('slds-show');
}
// send selected lookup record to parent component using custom event
lookupUpdatehandler(value) {
const oEvent = new CustomEvent('lookupupdate',
{
'detail': { selectedRecord: value }
}
);
this.dispatchEvent(oEvent);
}
}
============
public class CustomLookupLwcController {
// Method to fetch lookup search result
@AuraEnabled(cacheable=true)
public static list<sObject> fetchLookupData(string searchKey , string sObjectApiName) {
List < sObject > returnList = new List < sObject > ();
string sWildCardText = '%' + searchKey + '%';
string sQuery = 'Select Id,Name From ' + sObjectApiName + ' Where Name Like : sWildCardText order by createdDate DESC LIMIT 5';
for (sObject obj: database.query(sQuery)) {
returnList.add(obj);
}
return returnList;
}
// Method to fetch lookup default value
@AuraEnabled
public static sObject fetchDefaultRecord(string recordId , string sObjectApiName) {
string sRecId = recordId;
string sQuery = 'Select Id,Name From ' + sObjectApiName +' Where Id =\''+ sRecId+'\' LIMIT 1' ;
for (sObject obj: database.query(sQuery)) {
return obj;
}
return null;
}
}
In orderTo save the data filled in the form to a "Customer" object, you can implement the saveAction method in your LWC. You can refer below high-level steps:
- You can handle the success and errors in the then and catch blocks of the Promise returned by the Apex method call.
Related: https://www.salesforcelwc.in/2019/04/how-to-create-record-in-lightning-web.htmlhttps://www.forcetalks.com/blog/create-and-insert-record-in-lwc-without-using-record-edit-form-salesforce-developer-guide/
Thanks.