Re-Usable Dynamic Custom Lookup LWC edition
Over at sfdcmonkey there's a nice AURA component that allows for dynamic lookup of a given object. Super nice and useful. I wondered what it would take to be rebuild in LWC
Same, Same but different
I want to achieve the same functionality, but would accept subtle differences. This is what I got:
- The original component shows the object icon on the left. My version shows the search symbol that comes out of the box with lightning-input
- I use 3 components instead of two. Input fields that trigger network calls are fairly common and it makes sense to debounce the input. So I created
c-ux-debounced-input
that signals entered data only after a period of 300ms - The component dispatches an event when a result has been selected or cleared, so it can be used inside other components
- For now: it can be directly put on a lightning page in page builder and configured there. Useful for demos and test
- When you clear the selected object, the result list opens up again, so no second network call is made until you change the input value
The code
The template is slightly lighter
<template>
<div class={resultClass} data-select="single">
<div class="slds-form-element__control">
<!-- sequence completed record selected -->
<div if:true={selectedRecord}>
<label>{label}</label>
<div class="slds-pill-container">
<lightning-pill
class="pillSize"
label={selectedRecord.Name}
onremove={handlePillRemove}>
<lightning-icon
icon-name={iconName}
variant="plain"
alternative-text={selectedRecord.Name}>
</lightning-icon>
</lightning-pill>
</div>
</div>
<!-- Input for search term -->
<c-ux-debounced-input
label={label}
onchange={handleSearchTerm}
if:false={selectedRecord}
value={lastSearchValue}>
</c-ux-debounced-input>
</div>
<ul
style="min-height:40px;margin-top:0px !important"
class="slds-listbox slds-listbox_vertical slds-dropdown slds-dropdown_fluid slds-lookup__menu slds"
role="listbox">
<lightning-spinner
variant="brand"
size="small"
if:true={showSpinner}
></lightning-spinner>
<center>{message}</center>
<template if:true={results} for:each={results} for:item="singleRec">
<c-ux-quick-lookup-result
icon-name={iconName}
record={singleRec}
onselection={handleRecordSelect}
key={singleRec.Id}
></c-ux-quick-lookup-result>
</template>
</ul>
</div>
</template>
The CSS is more or less the same
.pillSize {
width: 100%;
}
The JavaScript is proper ES6. It already contains some code for my "Next Stop"
import { LightningElement, api, track } from 'lwc';
import fetchLookUpValues from '@salesforce/apex/CustomLookUpController.fetchLookUpValues';
import fetchExtendedLookUpValues from '@salesforce/apex/CustomLookUpController.fetchExtendedLookUpValues';
export default class UxQuickLookup extends LightningElement {
@api objectApiName;
@api iconName = 'standard:account';
@api label = 'Lookup';
@api fields = null;
@track resultClass;
@track selectedRecord = null;
@track results = null;
@track message = null;
@track showSpinner = false;
@track lastSearchValue;
constructor() {
super();
this.switchResult(false);
}
handleSearchTerm(event) {
let searchValue = event.detail;
if (searchValue) {
this.switchResult(true);
this.message = 'searching...';
this.showSpinner = true;
let searchParams = {
searchKeyWord: searchValue,
objectName: this.objectApiName
};
if (this.fields) {
this.addFieldsToParam(searchParams);
fetchExtendedLookUpValues(searchParams)
.then(result => this.setResult(result))
.catch(error => this.handleError(error));
} else {
fetchLookUpValues(searchParams)
.then(result => this.setResult(result))
.catch(error => this.handleError(error));
}
} else {
this.switchResult(false);
this.message = null;
this.showSpinner = false;
this.results = null;
}
this.lastSearchValue = searchValue;
}
/* Ensure we always have Name and Id in the query */
addFieldsToParam(searchParam) {
let allFields = this.fields.split(',');
allFields.push('Id');
allFields.push('Name');
let cleanFields = this.dedupeArray(allFields).join(',');
searchParam.fieldsToQuery = cleanFields;
}
dedupeArray(incoming) {
var uniqEs6 = arrArg => {
return arrArg.filter((elem, pos, arr) => {
return arr.indexOf(elem) === pos;
});
};
return uniqEs6(incoming);
}
setResult(newValues) {
this.showSpinner = false;
if (newValues && newValues.length > 0) {
this.message = null;
this.results = newValues;
} else {
this.message = 'no results found';
}
}
/* Shows and hides the result area */
switchResult(on) {
this.resultClass = on
? 'slds-form-element slds-lookup slds-is-open'
: 'slds-form-element slds-lookup slds-is-close';
}
handlePillRemove() {
this.selectedRecord = null;
let payload = {
detail: {
canceled: true,
recordId: null
}
};
let selected = new CustomEvent('selection', payload);
this.dispatchEvent(selected);
// Restore last results
this.switchResult(this.lastSearchValue && this.results);
}
handleError(error) {
this.showSpinner = false;
this.message = "Sorry didn't work!";
let errorDispatch = new CustomEvent('failure', { detail: error });
this.dispatchEvent(errorDispatch);
}
handleRecordSelect(event) {
let record = event.detail;
this.selectedRecord = record;
let selectionDispatch = new CustomEvent('recordselected', {
detail: record
});
this.dispatchEvent(selectionDispatch);
this.switchResult(false);
}
}
What you don't see here are c-ux-quick-lookup-result
and c-ux-debounced-input
. To check those, head over to the repository, have a look, take a clone and deploy it with sfdx force:source:deploy -p experiments/experiment5
into a sandbox. Add the component to a lightning page and have a look.
Next Stop
When selecting object, the name might not be enough to pick the right value. So I'm looking into a way to optional display more field values. That's an interesting challenge for visualization.
As usual: YMMV
Posted by Stephan H Wissel on 27 March 2019 | Comments (2) | categories: Lightning Salesforce Software