Add the following import:
import { getRecord } from 'lightning/uiRecordApi';
Declare a constant named fields
as an array to define which fields to retrieve:
const fields = [
'Property__c.Name',
'Property__c.Price__c',
'Property__c.Status__c',
'Property__c.Beds__c',
'Property__c.Broker__c'
]
Add these tracked variables:
@track property;
@track price;
@track beds;
Add a new @wire
to retrieve the desired fields for the current record:
@wire(getRecord, {recordId: '$recordId', fields})
wiredProperty(value) {
if(value.data) {
this.property = value.data;
this.price = this.property.fields.Price__c.value;
this.beds = this.property.fields.Beds__c.value;
} else if (value.error) {
console.log("OOOPS: ", value.error)
}
}
price: '$price'
and beds: '$beds'
to the parameters being passed to the findProperties
Apex method.Edit the price of the property, and notice that the component performs a new search.
Even though the Apex method findProperties
only takes recordId and priceRange as parameters, the fact that $price
is reactive causes the method to be called when its value changes.
force-app/main/default
folder and choose New Folder.Properties.messageChannel-meta.xml
as the file name.Paste the following into the new file:
<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<masterLabel>Example Channel</masterLabel>
<isExposed>true</isExposed>
<description>This is an example Lightning Messages Channel.</description>
<lightningMessageFields>
<description>Where did this message originate</description>
<fieldName>source</fieldName>
</lightningMessageFields>
<lightningMessageFields>
<description>What is this message about</description>
<fieldName>title</fieldName>
</lightningMessageFields>
<lightningMessageFields>
<description>What is the message</description>
<fieldName>message</fieldName>
</lightningMessageFields>
</LightningMessageChannel>
Add the following to the imports for the component:
import { publish, createMessageContext, releaseMessageContext } from 'lightning/messageService';
import MESSAGE_CHANNEL from "@salesforce/messageChannel/Properties__c";
context = createMessageContext();
to the class on a new line after @track decorators.publish(this.context, MESSAGE_CHANNEL, this);
to the end of the handleSuccess method.Add the following import:
import { subscribe, createMessageContext, releaseMessageContext } from 'lightning/messageService';
import MESSAGE_CHANNEL from "@salesforce/messageChannel/Properties__c";
import { refreshApex } from '@salesforce/apex';
Add the following after the @track
declarations:
context = createMessageContext();
subscription = null;
Add the following methods to the end of the class:
connectedCallback() {
if (this.subscription) {
return;
}
this.subscription = subscribe(this.context, MESSAGE_CHANNEL, (message) => {
this.refreshSelection(message);
});
}
disconnectedCallback() {
releaseMessageContext(this.context);
}
refreshSelection() {
refreshApex(this.wiredRecords);
}
Add these new properties to the SimilarProperties class.
@api searchCriteria = 'Price';
@api priceRange = '100000';
@track cardTitle;
Add the searchCriteria
property to the parameters being sent to the Apex class and change the value of priceRange
to be dynamic like this:
priceRange: '$priceRange',
price: '$price',
beds: '$beds',
searchCriteria: '$searchCriteria'
title={cardTitle}
in similarProperties.html, then save the file.Add a renderedCallback to the SimilarProperties class to set the card title:
renderedCallback() {
this.cardTitle = 'Similar Properties by ' + this.searchCriteria;
}
Add the following configuration to the LightningComponentBundle in similarProperties.js-meta.xml:
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<property name="searchCriteria" datasource="Price,Bedrooms" label="Search Criteria" type="String" default="Price" />
<property name="priceRange" type="String" label="Price Range" default="100000" />
<objects>
<object>Property__c</object>
</objects>
</targetConfig>
</targetConfigs>
Replace the contents of the SimilarPropertyController class with:
@AuraEnabled(cacheable=true)
public static List<Property__c> getSimilarProperties (Id recordId, String searchCriteria, Decimal beds, Decimal price, Decimal priceRange ) {
if (searchCriteria == 'Bedrooms') {
return [
SELECT Id, Name, Beds__c, Baths__c, Price__c, Broker__c, Status__c, Thumbnail__c
FROM Property__c WHERE Id != :recordId AND Beds__c = :beds
];
} else {
Decimal range;
if (priceRange == null) {
range = 100000;
} else {
range = priceRange;
}
return [
SELECT Id, Name, Beds__c, Baths__c, Price__c, Broker__c, Status__c, Thumbnail__c
FROM Property__c WHERE Id != :recordId AND Price__c > :price - range AND Price__c < :price + range
];
}
}
In similarProperties.js, update the import for findProperties
to import getSimilarProperties
from SimilarPropertyController:
import findProperties from '@salesforce/apex/SimilarPropertyController.getSimilarProperties';
50000
, then hit Tab or Return. Notice the component updates with new results.<path>
tag from the SVG.<path>
tag with the one you just copied.<path>
you just pasted, add fill=”#fff” before the “d” attribute.(Informational Only) Change width="120px" height="120px" viewBox="0 0 120 120"
in the <svg>
tag to:
width="100px" height="100px" viewBox="0 0 100 100"
<path>
to #F26891
.Paste the following into similarProperties.svg:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M120,108 C120,114.6 114.6,120 108,120 L12,120 C5.4,120 0,114.6 0,108 L0,12 C0,5.4 5.4,0 12,0 L108,0 C114.6,0 120,5.4 120,12 L120,108 L120,108 Z" id="Shape" fill="#2A739E"/>
<path fill="#FFF" d="m78 24h-50v-2c0-1.1-0.9-2-2-2h-4c-1.1 0-2 0.9-2 2v56c0 1.1 0.9 2 2 2h4c1.1 0 2-0.9 2-2v-46h50c1.1 0 2-0.9 2-2v-4c0-1.1-0.9-2-2-2z m-4 14h-34c-3.3 0-6 2.7-6 6v22c0 3.3 2.7 6 6 6h34c3.3 0 6-2.7 6-6v-22c0-3.3-2.7-6-6-6z m-5.5 17h-2.5v10c0 0.6-0.4 1-1 1h-4c-0.6 0-1-0.4-1-1v-6c0-0.6-0.4-1-1-1h-4c-0.6 0-1 0.4-1 1v6c0 0.6-0.4 1-1 1h-4c-0.6 0-1-0.4-1-1v-10h-2.5c-0.5 0-0.7-0.6-0.3-0.9l11.2-10.9c0.4-0.3 0.9-0.3 1.3 0l11.2 10.9c0.3 0.3 0.1 0.9-0.4 0.9z"></path>
</g>
</svg>
Switch back to Lightning App Builder, and click the Refresh button at the top of the components’ list.
Add the following at the bottom of the markup before the closing </lightning-card>
:
<template if:false={props}>
<lightning-spinner variant="brand"
size="large"></lightning-spinner>
</template>
Save the file.
Add the following to the CSS file:
.spacer {
min-height: 10rem;
}
<div>
inside the <lightning-card>
.Wrap the <ul> ... </ul>
in a template if:
<template if:true={showRecords}>
<!-- the ul is here -->
</template>
Add the following on a new line after the opening <div>
tag:
<template if:false={showRecords}>
<h3 class="slds-text-heading_small slds-text-color_error">No similar properties found.</h3>
</template>
Switch to similarProperties.js and add the following after the renderedCallback
method:
get showRecords() {
if (this.props.data) {
if (this.props.data.length === 0) {
return false;
} else {
return true;
}
} else if (this.props.error) {
return 'Houston, we have a problem: ' + this.props.error;
}
}
Need a last second rescue? Check your code against the following:
<template>
<lightning-card title={cardTitle}
icon-name="custom:custom85">
<div class="slds-m-around_medium spacer">
<template if:false={showRecords}>
<h3 class="slds-text-heading_small slds-text-color_error">No similar properties found.</h3>
</template>
<template if:true={showRecords}>
<ul class="slds-list_vertical slds-has-dividers_top-space">
<template for:each={props.data}
for:item="item"
if:true={props.data}>
<li key={item.Id}
class="slds-list__item">
<c-similar-Property item={item}></c-similar-Property>
</li>
</template>
<template if:true={props.error}>
<li class="slds-list__item">
<h3 class="slds-text-heading_small slds-text-color_error">{props.error}</h3>
</li>
</template>
</ul>
</template>
</div>
<template if:false={props.data}>
<lightning-spinner variant="brand" size="large"></lightning-spinner>
</template>
</lightning-card>
</template>
import { LightningElement, api, wire, track } from 'lwc';
import findProperties from '@salesforce/apex/SimilarPropertyController.getSimilarProperties';
import { getRecord } from 'lightning/uiRecordApi';
import { subscribe, createMessageContext, releaseMessageContext } from 'lightning/messageService';
import MESSAGE_CHANNEL from "@salesforce/messageChannel/Properties__c";
import { refreshApex } from '@salesforce/apex';
const fields = [
'Property__c.Name',
'Property__c.Price__c',
'Property__c.Status__c',
'Property__c.Beds__c',
'Property__c.Broker__c'
]
export default class SimilarProperties extends LightningElement {
@api recordId;
@track props;
@track errorMsg;
@track property;
@track price;
@track beds;
@api searchCriteria = 'Price';
@api priceRange = '100000';
@track cardTitle;
context = createMessageContext();
subscription = null;
@wire(findProperties, {
recordId: '$recordId',
priceRange: '100000',
price: '$price',
beds: '$beds',
searchCriteria: '$searchCriteria'
})
props
@wire(getRecord, { recordId: '$recordId', fields })
wiredProperty(value) {
if (value.data) {
this.property = value.data;
this.price = this.property.fields.Price__c.value;
this.beds = this.property.fields.Beds__c.value;
} else if (value.error) {
console.log("OOOPS: ", value.error)
}
}
connectedCallback() {
if (this.subscription) {
return;
}
this.subscription = subscribe(this.context, MESSAGE_CHANNEL, (message) => {
this.refreshSelection(message);
});
}
disconnectedCallback() {
releaseMessageContext(this.context);
}
refreshSelection() {
refreshApex(this.props);
}
renderedCallback() {
this.cardTitle = 'Similar Properties by ' + this.searchCriteria;
}
get showRecords() {
if (this.props.data) {
if (this.props.data.length === 0) {
return false;
} else {
return true;
}
} else if (this.props.error) {
return 'Houston, we have a problem: ' + this.props.error;
}
}
}
.spacer {
min-height: 10rem;
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="similarProps">
<apiVersion>45.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Similar Properties</masterLabel>
<description>This component searches for similar properties.</description>
<targets>
<target>lightning__RecordPage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<property name="searchCriteria" datasource="Price,Bedrooms" label="Search Criteria" type="String" default="Price" />
<property name="priceRange" type="String" label="Price Range" default="50000" />
<objects>
<object>Property__c</object>
</objects>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
<template>
<div class="slds-media">
<div class="slds-media__figure">
<img src={item.Thumbnail__c} class="slds-avatar_large slds-avatar_circle" alt={item.Title_c} />
</div>
<div class="slds-media__body">
<div class="slds-grid slds-hint-parent">
<a onclick={navigateToRecord}>
<h3 class="slds-text-heading_small slds-m-bottom_xx-small">{item.Name}</h3>
</a>
<template if:false={editMode}>
<lightning-button-icon icon-name="utility:edit"
class="slds-col_bump-left"
icon-class="slds-button__icon_hint"
variant="bare"
alternative-text="Edit Record"
onclick={editRecord}></lightning-button-icon>
</template>
</div>
<template if:false={editMode}>
<lightning-record-view-form object-api-name="Property__c"
record-id={item.Id}>
<lightning-layout multiple-rows>
<lightning-layout-item size="6">
<lightning-output-field field-name="Price__c"></lightning-output-field>
</lightning-layout-item>
<lightning-layout-item size="6">
<lightning-output-field field-name="Beds__c"></lightning-output-field>
</lightning-layout-item>
<lightning-layout-item size="6">
<lightning-output-field field-name="Baths__c"></lightning-output-field>
</lightning-layout-item>
<lightning-layout-item size="6">
<lightning-output-field field-name="Status__c"></lightning-output-field>
</lightning-layout-item>
<lightning-layout-item size="12">
<lightning-output-field field-name="Broker__c"></lightning-output-field>
</lightning-layout-item>
</lightning-layout>
</lightning-record-view-form>
</template>
<template if:true={editMode}>
<lightning-record-edit-form object-api-name="Property__c"
record-id={item.Id}
layout-type="Full"
onsuccess={handleSuccess}
onerror={handleError}>
<lightning-layout multiple-rows>
<lightning-layout-item size="6">
<lightning-input-field field-name="Price__c"></lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="6">
<lightning-input-field field-name="Beds__c"></lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="6">
<lightning-input-field field-name="Baths__c"></lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="6">
<lightning-input-field field-name="Status__c"></lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="12">
<lightning-input-field field-name="Broker__c"></lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="12">
<div class="slds-m-top_large slds-grid slds-grid_align-center">
<lightning-button variant="neutral"
label="Cancel"
title="Cancel"
type="text"
onclick={handleCancel}
class="slds-m-right_small"></lightning-button>
<lightning-button variant="brand"
label="Submit"
title="Submit"
type="submit"></lightning-button>
</div>
</lightning-layout-item>
</lightning-layout>
</lightning-record-edit-form>
</template>
</div>
</div>
</template>
import { LightningElement, api, wire, track } from 'lwc';
import { NavigationMixin, CurrentPageReference } from 'lightning/navigation';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { publish, createMessageContext, releaseMessageContext } from 'lightning/messageService';
import MESSAGE_CHANNEL from "@salesforce/messageChannel/Properties__c";
export default class SimilarProperty extends NavigationMixin(LightningElement) {
@api item;
@track editMode = false;
context = createMessageContext();
// @wire(CurrentPageReference) pageRef;
navigateToRecord() {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.item.Id,
objectApiName: 'Property__c',
actionName: 'view',
},
});
}
editRecord() {
this.editMode = true;
}
handleSuccess() {
const evt = new ShowToastEvent({
title: "Success!",
message: "The record has been successfully saved.",
variant: "success",
});
this.dispatchEvent(evt);
this.editMode = false;
publish(this.context, MESSAGE_CHANNEL, this);
}
handleError() {
const evt = new ShowToastEvent({
title: "Error!",
message: "An error occurred while attempting to save the record.",
variant: "error",
});
this.dispatchEvent(evt);
this.editMode = false;
}
handleCancel(event) {
this.editMode = false;
event.preventDefault();
}
}
public with sharing class SimilarPropertyController {
@AuraEnabled(cacheable=true)
public static List<Property__c> getSimilarProperties (Id recordId, String searchCriteria, Decimal beds, Decimal price, Decimal priceRange ) {
if (searchCriteria == 'Bedrooms') {
return [
SELECT Id, Name, Beds__c, Baths__c, Price__c, Broker__c, Status__c, Thumbnail__c
FROM Property__c WHERE Id != :recordId AND Beds__c = :beds
];
} else {
Decimal range;
if (priceRange == null) {
range = 100000;
} else {
range = priceRange;
}
return [
SELECT Id, Name, Beds__c, Baths__c, Price__c, Broker__c, Status__c, Thumbnail__c
FROM Property__c WHERE Id != :recordId AND Price__c > :price - range AND Price__c < :price + range
];
}
}
}