Skip to content

Commit

Permalink
Ran prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
pozil committed Oct 21, 2019
1 parent d018116 commit f47f8ec
Show file tree
Hide file tree
Showing 14 changed files with 325 additions and 160 deletions.
58 changes: 33 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Salesforce Lookup Component (Lightning Web Component version)

Aura version is available [here](https://github.com/pozil/sfdc-ui-lookup).

<p align="center">
Expand All @@ -8,23 +9,26 @@ Aura version is available [here](https://github.com/pozil/sfdc-ui-lookup).
<img src="screenshots/dropdown-open.png" alt="Lookup with dropdown open" width="350" align="right"/>

## About

This is a generic &amp; customizable lookup component built using Salesforce [Lightning Web Components](https://developer.salesforce.com/docs/component-library/documentation/lwc) and [SLDS](https://www.lightningdesignsystem.com/) style.<br/>
It does not rely on third party libraries and you have full control over its datasource.

<b>Features</b>

The lookup component provides the following features:
- customizable data source that can return mixed sObject types
- single or multiple selection mode
- client-side caching & request throttling
- built-in server request rate limit mechanism
- project is unit tested

- customizable data source that can return mixed sObject types
- single or multiple selection mode
- client-side caching & request throttling
- built-in server request rate limit mechanism
- project is unit tested

<p align="center">
<img src="screenshots/selection-types.png" alt="Multiple or single entry lookup"/>
</p>

## Documentation

Follow these steps in order to use the lookup component:

### 1) Write the search endpoint
Expand All @@ -49,12 +53,14 @@ import apexSearch from '@salesforce/apex/SampleLookupController.search';

The lookup component exposes a `search` event that is fired when a search needs to be performed on the server-side.
The parent component that contains the lookup must handle the `search` event:

```xml
<c-lookup onsearch={handleSearch} label="Search" placeholder="Search Salesforce">
</c-lookup>
```

The `search` event handler calls the Apex `search` method and passes the results back to the lookup using the `setSearchResults` function:

```js
handleSearch(event) {
const target = event.target;
Expand All @@ -68,18 +74,19 @@ handleSearch(event) {
}
```


### 4) Optionally handle selection changes

The lookup component exposes a `selectionchange` event that is fired when the selection of the lookup changes.
The parent component that contains the lookup can handle the `selectionchange` event:

```xml
<c-lookup onsearch={handleSearch} onselectionchange={handleSelectionChange}
label="Search" placeholder="Search Salesforce">
</c-lookup>
```

The `selectionchange` event handler can then get the current selection by calling the `getSelection` function:

```js
handleSelectionChange(event) {
const selection = event.target.getSelection();
Expand All @@ -91,29 +98,30 @@ handleSelectionChange(event) {
That list contains a maximum of one elements if the lookup is a single entry lookup.

### Reference
| Attribute | Type | Description |
| --- | --- | --- |
| `label` | String | Lookup label |
| `selection` | Array | Lookup initial selection if any |
| `placeholder` | String | Lookup placeholder |
| `isMultiEntry` | Boolean | Whether the lookup is single (default) or multi entry. |
| `errors` | Array | List of errors that are displayed under the lookup. |
| `scrollAfterNItems` | Number | A null or integer value used to force overflow scroll on the result listbox after N number of items. Valid values are null, 5, 7, or 10. Use null to disable overflow scrolling. |
| `customKey` | String | Custom key that can be used to identify this lookup when placed in a collection of similar components. |

| Function | Description |
| --- | --- |
| `setSearchResults(results)` | Passes a search results array back to the lookup so that they are displayed in the dropdown. |
| `getSelection()` | Gets the current lookup selection. |
| `getkey()` | Retrieves the value of the `customKey` attribute. |

| Event | Description | Data |
| --- | --- | --- |
| `search` | Event fired when a search needs to be performed on the server-side. | `{ searchTerm: String, selectedIds: Array }` |
| `selectionchange` | Event fired when the selection of the lookup changes. This event holds no data, use the `getSelection` function to retrieve the current selection. | none |
| Attribute | Type | Description |
| ------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `label` | String | Lookup label |
| `selection` | Array | Lookup initial selection if any |
| `placeholder` | String | Lookup placeholder |
| `isMultiEntry` | Boolean | Whether the lookup is single (default) or multi entry. |
| `errors` | Array | List of errors that are displayed under the lookup. |
| `scrollAfterNItems` | Number | A null or integer value used to force overflow scroll on the result listbox after N number of items. Valid values are null, 5, 7, or 10. Use null to disable overflow scrolling. |
| `customKey` | String | Custom key that can be used to identify this lookup when placed in a collection of similar components. |

| Function | Description |
| --------------------------- | -------------------------------------------------------------------------------------------- |
| `setSearchResults(results)` | Passes a search results array back to the lookup so that they are displayed in the dropdown. |
| `getSelection()` | Gets the current lookup selection. |
| `getkey()` | Retrieves the value of the `customKey` attribute. |

| Event | Description | Data |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `search` | Event fired when a search needs to be performed on the server-side. | `{ searchTerm: String, selectedIds: Array }` |
| `selectionchange` | Event fired when the selection of the lookup changes. This event holds no data, use the `getSelection` function to retrieve the current selection. | none |

## Sample application

The default installation installs the lookup component and a sample application available under this URL (replace the domain):<br/>
https://<b>&lt;YOUR_DOMAIN&gt;</b>.lightning.force.com/c/SampleLookupApp.app

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<aura:component isTemplate="true" extends="aura:template">
<aura:set attribute="auraResetStyle" value=""/>
<aura:set attribute="auraResetStyle" value="" />
<aura:set attribute="auraPreInitBlock">
<auraStorage:init name="actions" persistent="false" secure="false" maxSize="1024" />
<auraStorage:init
name="actions"
persistent="false"
secure="false"
maxSize="1024"
/>
</aura:set>
</aura:component>
</aura:component>
42 changes: 32 additions & 10 deletions src-sample/main/default/classes/SampleLookupController.cls
Original file line number Diff line number Diff line change
@@ -1,33 +1,55 @@
public with sharing class SampleLookupController {

private final static Integer MAX_RESULTS = 5;

@AuraEnabled(Cacheable=true)
public static List<LookupSearchResult> search(String searchTerm, List<String> selectedIds) {
public static List<LookupSearchResult> search(
String searchTerm,
List<String> selectedIds
) {
// Prepare query paramters
searchTerm += '*';

// Execute search query
List<List<SObject>> searchResults = [FIND :searchTerm IN ALL FIELDS RETURNING
Account (Id, Name, BillingCity WHERE id NOT IN :selectedIds),
Opportunity (Id, Name, StageName WHERE id NOT IN :selectedIds)
LIMIT :MAX_RESULTS];
List<List<SObject>> searchResults = [
FIND :searchTerm
IN ALL FIELDS
RETURNING
Account(Id, Name, BillingCity WHERE id NOT IN :selectedIds),
Opportunity(Id, Name, StageName WHERE id NOT IN :selectedIds)
LIMIT :MAX_RESULTS
];

// Prepare results
List<LookupSearchResult> results = new List<LookupSearchResult>();

// Extract Accounts & convert them into LookupSearchResult
String accountIcon = 'standard:account';
Account [] accounts = ((List<Account>) searchResults[0]);
Account[] accounts = ((List<Account>) searchResults[0]);
for (Account account : accounts) {
results.add(new LookupSearchResult(account.Id, 'Account', accountIcon, account.Name, 'Account • '+ account.BillingCity));
results.add(
new LookupSearchResult(
account.Id,
'Account',
accountIcon,
account.Name,
'Account • ' + account.BillingCity
)
);
}

// Extract Opportunities & convert them into LookupSearchResult
String opptyIcon = 'standard:opportunity';
Opportunity [] opptys = ((List<Opportunity>) searchResults[1]);
Opportunity[] opptys = ((List<Opportunity>) searchResults[1]);
for (Opportunity oppty : opptys) {
results.add(new LookupSearchResult(oppty.Id, 'Opportunity', opptyIcon, oppty.Name, 'Opportunity • '+ oppty.StageName));
results.add(
new LookupSearchResult(
oppty.Id,
'Opportunity',
opptyIcon,
oppty.Name,
'Opportunity • ' + oppty.StageName
)
);
}

return results;
Expand Down
14 changes: 10 additions & 4 deletions src-sample/main/default/classes/SampleLookupControllerTest.cls
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
@isTest
public class SampleLookupControllerTest {
static testMethod void search_should_return_Account() {
Id [] fixedResults = new Id[1];
Id[] fixedResults = new List<Id>(1);
Account account = createTestAccount('Account');
fixedResults.add(account.Id);
Test.setFixedSearchResults(fixedResults);
List<String> selectedIds = new List<String>();

List<LookupSearchResult> results = SampleLookupController.search('Acc', selectedIds);
List<LookupSearchResult> results = SampleLookupController.search(
'Acc',
selectedIds
);

System.assertEquals(1, results.size());
System.assertEquals(account.Id, results.get(0).getId());
}

static testMethod void search_should_not_return_selected_item() {
Id [] fixedResults = new Id[1];
Id[] fixedResults = new List<Id>(1);
Account account1 = createTestAccount('Account1');
fixedResults.add(account1.Id);
Account account2 = createTestAccount('Account2');
Expand All @@ -23,7 +26,10 @@ public class SampleLookupControllerTest {
List<String> selectedIds = new List<String>();
selectedIds.add(account2.Id);

List<LookupSearchResult> results = SampleLookupController.search('Acc', selectedIds);
List<LookupSearchResult> results = SampleLookupController.search(
'Acc',
selectedIds
);

System.assertEquals(1, results.size());
System.assertEquals(account1.Id, results.get(0).getId());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
<template>
<lightning-card title="Sample Lookup (Lightning Web Component)">
<div class="slds-form slds-form_stacked slds-m-around_xx-large">
<lightning-input
type="checkbox"
label="Is mutli entry lookup"
checked={isMultiEntry}
onchange={handleLookupTypeChange}
></lightning-input>

<lightning-input type="checkbox" label="Is mutli entry lookup"
checked={isMultiEntry} onchange={handleLookupTypeChange}></lightning-input>

<c-lookup selection={initialSelection} errors={errors}
onsearch={handleSearch} onselectionchange={handleSelectionChange}
label="Search" placeholder="Search Salesforce" is-multi-entry={isMultiEntry}>
<c-lookup
selection={initialSelection}
errors={errors}
onsearch={handleSearch}
onselectionchange={handleSelectionChange}
label="Search"
placeholder="Search Salesforce"
is-multi-entry={isMultiEntry}
>
</c-lookup>

<lightning-button variant="brand" label="Submit" onclick={handleSubmit}></lightning-button>
<lightning-button
variant="brand"
label="Submit"
onclick={handleSubmit}
></lightning-button>
</div>
</lightning-card>
</template>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import apexSearch from '@salesforce/apex/SampleLookupController.search';

export default class SampleLookupContainer extends LightningElement {

// Use alerts instead of toast to notify user
@api notifyViaAlerts = false;

@track isMultiEntry = false;
@track initialSelection = [
{id: 'na', sObjectType: 'na', icon: 'standard:lightning_component', title: 'Inital selection', subtitle:'Not a valid record'}
{
id: 'na',
sObjectType: 'na',
icon: 'standard:lightning_component',
title: 'Inital selection',
subtitle: 'Not a valid record'
}
];
@track errors = [];

Expand All @@ -24,10 +29,16 @@ export default class SampleLookupContainer extends LightningElement {
handleSearch(event) {
apexSearch(event.detail)
.then(results => {
this.template.querySelector('c-lookup').setSearchResults(results);
this.template
.querySelector('c-lookup')
.setSearchResults(results);
})
.catch(error => {
this.notifyUser('Lookup Error', 'An error occured while searching with the lookup field.', 'error');
this.notifyUser(
'Lookup Error',
'An error occured while searching with the lookup field.',
'error'
);
// eslint-disable-next-line no-console
console.error('Lookup error', JSON.stringify(error));
this.errors = [error];
Expand All @@ -46,7 +57,9 @@ export default class SampleLookupContainer extends LightningElement {
}

checkForErrors() {
const selection = this.template.querySelector('c-lookup').getSelection();
const selection = this.template
.querySelector('c-lookup')
.getSelection();
if (selection.length === 0) {
this.errors = [
{ message: 'You must make a selection before submitting!' },
Expand All @@ -58,7 +71,7 @@ export default class SampleLookupContainer extends LightningElement {
}

notifyUser(title, message, variant) {
if (this.notifyViaAlerts){
if (this.notifyViaAlerts) {
// Notify via alert
// eslint-disable-next-line no-alert
alert(`${title}\n${message}`);
Expand All @@ -68,4 +81,4 @@ export default class SampleLookupContainer extends LightningElement {
this.dispatchEvent(toastEvent);
}
}
}
}
15 changes: 10 additions & 5 deletions src/main/default/classes/LookupSearchResult.cls
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
/**
* Class used to serialize a single Lookup search result item
* The Lookup controller returns a List<LookupSearchResult> when sending search result back to Lightning
*/
* Class used to serialize a single Lookup search result item
* The Lookup controller returns a List<LookupSearchResult> when sending search result back to Lightning
*/
public class LookupSearchResult {

private Id id;
private String sObjectType;
private String icon;
private String title;
private String subtitle;

public LookupSearchResult(Id id, String sObjectType, String icon, String title, String subtitle) {
public LookupSearchResult(
Id id,
String sObjectType,
String icon,
String title,
String subtitle
) {
this.id = id;
this.sObjectType = sObjectType;
this.icon = icon;
Expand Down
Loading

0 comments on commit f47f8ec

Please sign in to comment.