The Mind Reader

Years ago, when I taught Mathematics to both college and high school students, I used to show a fun puzzle that I found online called the Flash Mind Reader. It was a trick that had the students pick a number, then do a quick calculation. The Mind Reader would be able to guess the symbol next to their answer. Since then, Flash has fallen out of favor, so I thought I would recreate the trick in Salesforce.

Pick a 2 digit number, (example 23) then add the two digits together i.e. 2+3 = 5. Take your answer and subtract from the original number (23 – 5) and look up the symbol in the table. The Mind Reader will be able to tell you which symbol you picked when you click the Reveal button.

To try it out, go to this url: https://taylorkingsbury2-dev-ed.develop.my.site.com/

Or you can install it using this link in your dev org or scratch org, then navigate to the Mind Reader app in Salesforce:

https://MyDomainName.lightning.force.com/packagingSetupUI/ipLanding.app?apvId=04taj00000014UbAAI

Github: https://github.com/tjkingsbury/mindReader

Using the Lightning Message Service

When working with Lightning Aura Components, if we wanted to communicate between two unrelated components, we used Application Events. With Lightning Web Components, the equivalent to Application events was the Publish Subscribe model, or PubSub for short. This had some drawbacks, as it required developers to download the pubsub class and deploy it to their org in order to communicate between two lightning web components that didn’t have a parent – child relationship. If the pubsub class changed, we would have to download a newer version of it.

The Lightning Message Service deprecates the need to use pubsub. With the Lightning Message Service, a Lightning Web Component can publish or subscribe to a Message Channel that the developer defines.

I was trying to think of an example of how I could use this, and I thought about sending a search string from one component, publishing that string over a message channel, and having another component subscribe to that channel and use that string to populate a datatable.

From the publisher component, we type in a search parameter. The string is sent over a message channel, and handled by the subscriber component, where it is used to query the database and return a list of Account records whose name match the search string.

We have to create a message channel before we can publish to it. In VSCode, in the force-app/main/default folder, create a folder called messageChannels. Then create a file with the name SampleMessageChannel.messageChannel-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>SampleMessageChannel</masterLabel>
    <isExposed>true</isExposed>
    <description>This is a sample Lightning Message Channel.</description>
</LightningMessageChannel>

The publisher component allows the user to enter a search string. We can send this string to any other components that are subscribed to the message channel, Here we use a string to search by account name

<!-- publisherComponent.html -->
<template>
    <lightning-card title="myLwcPublisher" icon-name="custom:custom14">
        <div class="slds-m-around_medium">
            <p>MessageChannel: SampleMessageChannel</p>
            <br>
            <lightning-input value={inputMessage} onchange={handleInputChange}></lightning-input>
            <lightning-button label="Search" onclick={handleClick}></lightning-button>
        </div>
    </lightning-card>
</template>
// publisherComponent.js
import { LightningElement, wire } from 'lwc';
import { publish, MessageContext } from 'lightning/messageService';
import SAMPLEMC from '@salesforce/messageChannel/SampleMessageChannel__c';

export default class PublisherComponent extends LightningElement {
    @wire(MessageContext)
    messageContext;
    inputMessage;
          
    handleClick() {
        
        const message = { nameMessage : this.inputMessage};
        publish(this.messageContext, SAMPLEMC, message);
    }

    handleInputChange(event) {
        this.inputMessage= event.target.value;
    }
}

The subscriber component subscribes to the SampleMessageChannel, and from that message channel, takes takes the string that was sent and queries the Salesforce database for matching Accounts

<!-- subscribeComponent.html -->

<template>
    <lightning-card title="myLwcSubscriber" icon-name="custom:custom14">
        <div class="slds-m-around_medium">
            <p>MessageChannel: SampleMessageChannel</p>
        </div>

        <template if:true={accounts.data}>
            <div style="height: 300px;">
                <lightning-datatable
                        key-field="Id"
                        data={accounts.data}
                        columns={columns}
                        hide-checkbox-column>
                </lightning-datatable>
            </div>
        </template>
        

    </lightning-card>
</template>
// subscribeComponent.js
import { LightningElement, wire } from 'lwc';
import { subscribe, unsubscribe, APPLICATION_SCOPE, MessageContext } from 'lightning/messageService';

import SAMPLEMC from '@salesforce/messageChannel/SampleMessageChannel__c';
import searchAccounts from '@salesforce/apex/AccountSearch.searchAccounts';

const columns = [
    { label: 'Name', fieldName: 'Name' },
    { label: 'Type', fieldName: 'Type' }
];

export default class SubscribeComponent extends LightningElement {

    columns = columns;



    @wire(MessageContext)
    messageContext;

    subscription = null;
    receivedMessage;
    accountName;
    nameObject;


    connectedCallback(){
        this.subscribeMC();
    }

    subscribeMC() {
        if (this.subscription) {
            return;
        }
        this.subscription = subscribe(
            this.messageContext,
            SAMPLEMC, (message) => {
                this.handleMessage(message);
            },
            {scope: APPLICATION_SCOPE});
    }

    unsubscribeMC() {
        unsubscribe(this.subscription);
        this.subscription = null;
    }

    handleMessage(message) {
        this.receivedMessage = message ? JSON.stringify(message, null, '\t') : 'no message payload';
        this.accountName = message.nameMessage;
    }

    @wire(searchAccounts, {accountName : '$accountName'})
    accounts;
}

Apex Controller:

public with sharing class AccountSearch {
    

    @AuraEnabled(cacheable=true)
    public static List<Account> searchAccounts(String accountName){
        String accountNameWildcard = '%' + accountName + '%';
        List<Account> accountList = [SELECT Id, Name, Type FROM Account WHERE Name LIKE : accountNameWildcard];
        return accountList;
    }
}

GitHub: https://github.com/tjkingsbury/LightningMessagingService

Picklist Filters on a Datatable

With Lightning Web Components, we no longer have to click a button to perform filtering on tables. We can instead use a combination of event handlers and wire methods.

Here’s an example of a datatable that filters accounts according to the Industry picklist. Changing the value in the filter automatically refreshes the table to display the most relevant data.

HTML page:

<template>
    <lightning-card title="Account PicklistValues">
        <template if:true={IndustryPicklistValues.data}>
            <lightning-combobox name="progress"
                                label="Industry"
                                value={value}
                                placeholder="-Select-"
                                options={IndustryPicklistValues.data.values}
                                onchange={handleChange} >
            </lightning-combobox>
        </template>
        <div style="height: 300px;">
            <lightning-datatable
                key-field="Id"
                data={data}
                columns={columns}
                hide-checkbox-column>
            </lightning-datatable>
        </div>    
    </lightning-card>
</template>

Javascript Controller:

import { LightningElement, track, wire} from 'lwc';
import { getPicklistValues } from 'lightning/uiObjectInfoApi';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
import getAccountsByIndustry from '@salesforce/apex/PicklistFilterController.getAccountsByIndustry';

const columns = [
    { label: 'Name', fieldName: 'Name' },
    { label: 'Type', fieldName: 'Type' }
];

export default class PicklistFilter extends LightningElement {
    @track value = '';
    @track data = [];
    @track columns = columns;

    @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
    objectInfo;

    @wire(getPicklistValues, { recordTypeId: '$objectInfo.data.defaultRecordTypeId', fieldApiName: INDUSTRY_FIELD})
    IndustryPicklistValues;

    handleChange(event) {
        this.value = event.detail.value;
    }

    @wire(getAccountsByIndustry, { industry : '$value' })
    accountsByIndustryCallback({error,data}){
        this.data = data;
    }
}

Apex Controller:

public with sharing class PicklistFilterController {
    
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccountsByIndustry(String industry){
        return [SELECT Id, Name, Type FROM Account WHERE Industry=:industry];
    }
}

Calling a REST endpoint from a Lightning Web Component

Calling a REST endpoint in LWC is similar to how it is handled in Visualforce or in Lightning Aura Components. We can make the callout within our Apex controller, then parse the result in our client side controller, then display the results back to the user within the lightning component.

Everyone knows about the magic eight ball toy, where you shake an eight ball shaped container, ask a question, and receive an answer. I chose the Magic Eight Ball API because there’s no authentication needed, which makes it a rather simple case to work with.

In order to make callouts to external APIs you will need to add the endpoint to your remote site settings in Salesforce.

The apex controller makes a callout to the endpoint and returns the response:

 public with sharing class EightBallController {


    @AuraEnabled
    public static String askEightBall(String question){
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://8ball.delegator.com/magic/JSON/' + question);
        req.setMethod('GET');

        Http http = new Http();
        HttpResponse res = http.send(req);
        return res.getBody();
    }
} 

The Javascript controller handles the response from the rest endpoint, while the setBackgroundColor method sets the text color for the answer returned, based on whether the answer was a positive, neutral, or negative answer.

eightBallLwc.js

import { LightningElement, track, api } from 'lwc';
import askEightBall from '@salesforce/apex/EightBallController.askEightBall';

export default class EightBallLwc extends LightningElement {

    @track response;
    @track error;
    @track question;
    @api answer;
    @track jsonResponse;
    @api backgroundColor;

    handleChange(event){
        this.question = event.target.value;
    }

    askQuestion() {
        
        askEightBall({ question: this.question })
            .then(result => {
                this.response = result;
                this.jsonResponse = JSON.parse(this.response);
                this.answer = this.jsonResponse.magic.answer;
                this.type = this.jsonResponse.magic.type;
                this.setBackgroundColor();
                
                
                this.error = undefined;
            })
            .catch(error => {
                this.error = error;
                this.response = undefined;
            });
        
    }

    setBackgroundColor(){
        if(this.type === 'Affirmative'){
            this.backgroundColor = 'slds-text-color_success';
        }
        else if(this.type === 'Neutral'){
            this.backgroundColor = 'slds-text-color_weak';
        }
        else{
            this.backgroundColor = 'slds-text-color_error';
        }
    }
}

eightballLwc.html

<template>
    <lightning-card title="Magic Eight Ball" icon-name="custom:custom63">
        <div class="slds-m-around_medium">
            <p class="slds-m-bottom_small">
                <lightning-input label="Question" value={question} onchange={handleChange}></lightning-input>
                <lightning-button label="Ask" onclick={askQuestion}></lightning-button>
            </p>
            <p>
                <lightning-formatted-text class={backgroundColor} value={answer}></lightning-formatted-text>
            </p>
        </div>
    </lightning-card>
</template>

Github link: https://github.com/tjkingsbury/EightBall

Open a modal from a datatable to edit records

If I’m on an account and I want to quickly edit some of the information on my child records, I can create a button next to each record that will open a modal, which will allow me to edit the record and then return to my parent account record.

Apex Controller:

public with sharing class ContactsController {
    @AuraEnabled(cacheable = true)
    public static List<Contact> getContacts(id accountId){
        return [SELECT Id, FirstName, LastName, Phone, Email FROM Contact WHERE accountId =:accountId];
    }
}

Javascript controller (contactDataTable.js):

import { LightningElement, wire, track, api } from 'lwc';
import getContacts from '@salesforce/apex/ContactsController.getContacts';
import {ShowToastEvent} from 'lightning/platformShowToastEvent';

import {refreshApex} from '@salesforce/apex';

const columns = [
    { label: 'FirstName', fieldName: 'FirstName'},
    { label: 'LastName', fieldName: 'LastName'},
    { label: 'Phone', fieldName : 'Phone', type: 'phone'},
    { label: 'Email', fieldName: 'Email', type: 'email'},
    { label: 'Edit', type: 'button-icon', initialWidth: 75,
        typeAttributes: {
            iconName: 'action:edit',
            title: 'Edit',
            variant: 'border-filled',
            alternativeText: 'Edit'
        }
    }
];

export default class ContactDataTable extends LightningElement {
    //reactive
    @api recordId;
    @track columns = columns;
    @track bShowModal = false;
    @track currentRecordId;
    @track contacts;
    @track error;


    wiredContactsResult;

    @wire(getContacts,{ accountId: '$recordId'})
    wiredContacts(result){
        this.wiredContactsResult = result;
        if(result.data){
            this.contacts = result.data;
            this.error = undefined;
        } else if(result.error){
            this.error = result.error;
            this.contacts = undefined;
        }
    }


    handleRowAction(event){
        const row = event.detail.row;
        this.record = row;
        this.bShowModal = true;
        this.editCurrentRecord(row);
    }

    // closing modal box
    closeModal() {
        this.bShowModal = false;
    }

    handleSubmit(event){
        event.preventDefault();
        this.template.querySelector('lightning-record-edit-form').submit(event.detail.fields);
        this.bShowModal = false;
        this.dispatchEvent(new ShowToastEvent({
            title: 'Success!!',
            message: event.detail.fields.FirstName +' Contact updated Successfully!!',
            variant: 'success'
        }),);
    }

    handleSuccess() {
        return refreshApex(this.wiredContactsResult);
    }

    editCurrentRecord(currentRow){
        this.currentRecordId = currentRow.Id;
    }
    
}

Html (contactDataTable.html):

<template>
    <lightning-card title="Contacts" icon-name="standard:contact">
        <lightning-datatable data={contacts}
            columns={columns}
            key-field="id"
            hide-checkbox-column="true"
            onrowaction={handleRowAction}>
        </lightning-datatable>

        <template if:true={bShowModal}>
                <section role="dialog" tabindex="-1"
                         aria-labelledby="modal-heading-01"
                         aria-modal="true"
                         aria-describedby="modal-content-id-1"
                        class="slds-modal slds-fade-in-open">
                   <div class="slds-modal__container">
                      <!-- modal header start -->
                      <header class="slds-modal__header">
                         <button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse" title="Close" onclick={closeModal}>
                            <lightning-icon icon-name="utility:close" alternative-text="close" variant="inverse" size="small" ></lightning-icon>
                         </button>
                         <h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">Record Detail</h2>
                      </header>
                      <!-- modal body start -->
                      <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
                        <dl class="slds-list_horizontal slds-wrap">
                            <lightning-record-edit-form layout-type="Full" record-id={currentRecordId} object-api-name="Contact" onsubmit={handleSubmit} onsuccess={handleSuccess}>
                                <lightning-messages></lightning-messages>
                                <lightning-input-field field-name="FirstName"></lightning-input-field>                                
                                <div style="text-align:center;">
                                    <lightning-button class="slds-m-top_small"
                                                      variant="brand"
                                                      type="submit"
                                                      name="update"
                                                      label="Update Record"></lightning-button>
                                </div>
                            </lightning-record-edit-form><br/>
                            
                        </dl>
                      </div>
                      <!-- modal footer start-->
                      <footer class="slds-modal__footer">
                           <lightning-button variant="brand"
                           label="Close"
                           title="Close"
                           onclick={closeModal}
                           ></lightning-button>
                      </footer>
                   </div>
                </section>
                <div class="slds-backdrop slds-backdrop_open"></div>
             </template>
    </lightning-card>
</template>

See the code on github : https://github.com/tjkingsbury/ContactModalUpdate