New Pluralsight Course: Salesforce Lightning Web Components: The Big Picture

I am beyond happy to announce the release of my latest Pluralsight course, “Salesforce Lightning Web Components: The Big Picture“. This is a high-level overview of all the important things that make up this new modern and standards-based framework.

Viewers will first explore what makes up the entire Lightning Web Stack. This will include discovering the open-source Lightning Design System, which is key to the entire Lightning Experience.

Lightning Web Stack
Lightning Web Stack

Learners will also learn about the modern developer tools that Salesforce now offers. These tools offer developers a way to build robust and high-performing web apps. The tools should be instantly familiar to developers familiar with building modern web apps using frameworks like React or Vue.

Visual Studio Code and the Salesforce Extension Pack
Visual Studio Code and the Salesforce Extension Pack

When learners are finished, they should have the skills and knowledge of Lightning Web Components needed to build an adoption plan for their own Salesforce organizations.

And if you watch the course, please feel free to give me feedback. Good or bad. Thanks!

EDIT: Go here to access a promotional video I created for this course. It is a condensed 10 minute preview of the course that should give you a good idea of what it offers.

Salesforce Trailhead: Approach with Caution

Proceed to the Salesforce Trailhead with caution

As a former member of the Trailhead team, I am hesitant to write this, but I think it is an important thing for people to be aware of. If you have been using Trailhead for longer than a year, then you need to be aware of these pretty serious limitations.

While you can retake challenges for badges and projects, you cannot do so for Superbadges. More importantly, once you complete a badge, you have no way of knowing that the content for that badge may have been significantly updated. As in, all the content you understood when you completed the badge, is no longer relevant.

As I am sure you are aware, Salesforce does major releases three times a year. When they do, they prepare comprehensive release notes and the Trailhead authors are instructed to review these notes and update their content as necessary. That part is great. What is not great is that you as a consumer of Trailhead content have no way of knowing that the content was updated (without revisiting EVERY badge you have earned to examine the content).

The badge itself just indicates that it was completed on a certain date. As in the image below of a badge I completed in 2015. This badge has since been significantly updated and the content that was once relevant when I completed the badge is VERY different.

Trailhead badge completed in 2015 that now has VERY different content

How do you know that you are aware of the latest features and updates to the platform? Well, it is up to you to pick through the release notes and then cross reference any Trailhead content that you may have completed. Nice, right?

Just wanted to make sure everyone was aware of this (what I consider to be) serious limitation. I hope the team is actively addressing it. I would say I know, but they don’t talk to me since I left the team.

Top 5 Tips for Working with Salesforce Extensions for Visual Studio Code

After spending quite a lot of time working with the Salesforce Extensions for Visual Studio Code and discovering many hidden gems, here is a list of my top 5 tips. Hope they help you in your development journey.

#1 – Install the Expanded Extension Pack

This VERY handy version of the regular Salesforce Extension Pack, includes not only all the core SFDX plugin’s, but all the third-party ones you might need, like Prettier, ESLint, etc.

Salesforce Expanded Extension Pack
Salesforce Expanded Extension Pack

#2 – Run Update as Often as Possible

This is perhaps the MOST important tip I will offer. Unlike the rest of Salesforce software, the SS Salesforce Extensions are updated every week. That’s right. And they are constantly adding really cool features, so if you have not run…

sfdx update

in a while, you are probably wayyyyy behind and need to do it immediately!

#3 – Use the CLI Help Feature

For all of you that depend solely on the Command Palette, you really need to start using the built-in help that is offered. Come on, the Terminal is not that scary.

Not only will you get access to some really helpful info, but you will learn a heck of a lot about the CLI in general. Start by just typing sfdx help.

Go even further down the stack, but use –help past the first level. For example, to see the help for the project create command, so the following:

sfdx force:project:create --help
Use the built-in help feature
Use the built-in help feature

#4 – Use Code Completion and Snippets

If you do a lot of work with Apex, then you definitely want to check out using the Code Completion and Code Snippets features. This can be really helpful when creating Apex classes or triggers (especially if you are like me and do not do it that often).

#5 – See all SFDX Commands

When you are first learning how to work with the extensions, it can be helpful to see a list of all the available commands. This is accomplished by entering the following command: sfdx commands

See all the SFDX commands
See all the SFDX commands

Lightning Tip: Always Create Admin Friendly Lightning Components

In case you did not realize, Salesforce Lightning components can be one of two flavors:

  1. Aura components – Launched in 2015 and up until early 2019 were called Lightning components.
  2. Lightning web components (LWC’s) – Launched in 2019 and are considered the future of Salesforce development.

Design for Configurability

Whichever flavor you select, the tip in this post applies to both Aura components and the new Lightning web components.

So what does creating Admin friendly Lightning components mean?

In a nutshell, it means making your component as configureable as possible to anyone using it to assemble Lightning pages using Lightning App Builder. This is done by using a meta configuration file for LWC’s or a design resource file for Aura components.

Whichever flavor you chose, the tip in this post applies to both Aura components and the new Lightning web components.

To give you an example of how that is done for LWC’s, let’s look at the code for the eBikes sample app, which can be found here for the LWC version. This is what the HTML for that component looks like:

<template>
    <div class="slds-card slds-p-around_x-small">
        <template if:true={searchBarIsVisible}>
            <lightning-input
                label="Search Key"
                type="text"
                onchange={handleSearchKeyChange}
                class="search-bar"
            ></lightning-input>
        </template>
        <template if:true={products.data}>
            <template if:true={products.data.records.length}>
                <div class="content">
                    <template
                        for:each={products.data.records}
                        for:item="product"
                    >
                        <c-product-tile
                            key={product.Id}
                            product={product}
                            draggable={tilesAreDraggable}
                            onselected={handleProductSelected}
                            class="slds-m-around_x-small"
                        >
                        </c-product-tile>
                    </template>
                </div>
                <c-paginator
                    page-number={pageNumber}
                    page-size={products.data.pageSize}
                    total-item-count={products.data.totalItemCount}
                    onprevious={handlePreviousPage}
                    onnext={handleNextPage}
                ></c-paginator>
            </template>
            <template if:false={products.data.records.length}>
                <c-placeholder
                    message="There are no products matching your current selection"
                ></c-placeholder>
            </template>
        </template>
        <template if:true={products.error}>
            <c-error-panel errors={products.error}></c-error-panel>
        </template>
    </div>
</template>

And here is the JavaScript controller file:

import { LightningElement, api, wire } from 'lwc';

// Ligthning Message Service and message channels
import { publish, subscribe, MessageContext } from 'lightning/messageService';
import PRODUCTS_FILTERED_MESSAGE from '@salesforce/messageChannel/ProductsFiltered__c';
import PRODUCT_SELECTED_MESSAGE from '@salesforce/messageChannel/ProductSelected__c';

// getProducts() method in ProductController Apex class
import getProducts from '@salesforce/apex/ProductController.getProducts';

/**
 * Container component that loads and displays a list of Product__c records.
 */
export default class ProductTileList extends LightningElement {
    /**
     * Whether to display the search bar.
     * TODO - normalize value because it may come as a boolean, string or otherwise.
     */
    @api searchBarIsVisible = false;

    /**
     * Whether the product tiles are draggable.
     * TODO - normalize value because it may come as a boolean, string or otherwise.
     */
    @api tilesAreDraggable = false;

    /** Current page in the product list. */
    pageNumber = 1;

    /** The number of items on a page. */
    pageSize;

    /** The total number of items matching the selection. */
    totalItemCount = 0;

    /** JSON.stringified version of filters to pass to apex */
    filters = {};

    /** Load context for Ligthning Messaging Service */
    @wire(MessageContext) messageContext;

    /** Subscription for ProductsFiltered Ligthning message */
    productFilterSubscription;

    /**
     * Load the list of available products.
     */
    @wire(getProducts, { filters: '$filters', pageNumber: '$pageNumber' })
    products;

    connectedCallback() {
        // Subscribe to ProductsFiltered message
        this.productFilterSubscription = subscribe(
            this.messageContext,
            PRODUCTS_FILTERED_MESSAGE,
            (message) => this.handleFilterChange(message)
        );
    }

    handleProductSelected(event) {
        // Published ProductSelected message
        publish(this.messageContext, PRODUCT_SELECTED_MESSAGE, {
            productId: event.detail
        });
    }

    handleSearchKeyChange(event) {
        this.filters = {
            searchKey: event.target.value.toLowerCase()
        };
        this.pageNumber = 1;
    }

    handleFilterChange(message) {
        this.filters = { ...message.filters };
        this.pageNumber = 1;
    }

    handlePreviousPage() {
        this.pageNumber = this.pageNumber - 1;
    }

    handleNextPage() {
        this.pageNumber = this.pageNumber + 1;
    }
}

Notice that the searchBarIsVisible and titlesAreDraggable properties use the @api decorator and that the developer has kindly added a TODO comment here suggesting that the normalized value may come as a boolean, string or otherwise.

The reason the values may some across differently is because these two properties are configureable in the design file, but only for Record Pages and Community default pages. This means that anyone can use Lightning App Builder to change those values (well, at least for Record and Community Default pages).

To see how this is done, let’s take a look at the meta configuration file for this component:

<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>49.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Product Tile List</masterLabel>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <property
                name="searchBarIsVisible"
                type="Boolean"
                label="Search bar visible"
            />
            <property
                name="tilesAreDraggable"
                type="Boolean"
                label="Product tiles are draggable"
            />
            <objects>
                <object>Order__c</object>
            </objects>
        </targetConfig>
        <targetConfig targets="lightningCommunity__Default">
            <property
                name="searchBarIsVisible"
                type="Boolean"
                label="Search bar visible"
            />
            <property
                name="tilesAreDraggable"
                type="Boolean"
                label="Product tiles are draggable"
            />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Notice there are two targetConfig entries. One is for lightning__RecordPage and the other is for lightningCommunity__Default, but they both define the same properties. Even though these properties have default values of false, anyone assembling the pages for the two targeted page types can change these values in Lightning App Builder.

Consider this Change to the Configuration

Not to be too critical, but I can see room for improvement in the way the productTileList component was configured. After all, there is always room for improvement in anything we do, right?

The first change I would make is to add a description for all the targetConfig properties. The description is typically a short sentence, but it appears as a tooltip in Lightning App Builder. This sentence could be used to indicate to the app assembler that the property value should be either true or false and not any other value perhaps?

The end result would look something like the following (notice the tooltip on the right):

Lightning App Builder used to configure the productTileList component.
Lightning App Builder used to configure the productTileList component.

For more tips about what you can do to make your components configureable, check out this great developer doc page.

Post 5 – Customizing Salesforce with Lightning Aura Components Series

This will be the last of a series of posts I will be doing over the next few weeks. This particular post will cover working with the new Salesforce Mobile App. The series will all lead up to the introduction of my new course titled, “Customizing Salesforce with Lightning Aura Components” from Pluralsight. This course is a total re-write of my most popular course, “Customizing Salesforce with Lightning Components: Getting Started“, which was released back in early 2017. It will focus on using the new modern toolset that Salesforce offers as part of Salesforce DX.

Working with Salesforce Mobile

Salesforce introduced their mobile app called Salesforce1 in 2014. In November 2019, Salesforce introduced a new mobile app that closely mirrors the Lightning Experience on the Desktop. To understand how far the app has come, I think you need some images:

Image comparison of Salesforce1 (left) vs New Salesforce Mobile App (right)
Image comparison of Salesforce1 (left) vs New Salesforce Mobile App (right)

Right away you should have noticed that the details view includes more fields and this is primarily because of the denser view with labels to the left and less space between fields. This was a feature introduced for Lightning Experience to improve usability.

There are other big features like:

  1. Favoriting and Search icon links in the top menu bar
  2. There is also a really cool download icon that allows authorized users to send links to other authorized users.
  3. And the menu navigation has changed quite a bit and now mirrors what is found in Lightning Experience
Intuitive Navigation for Salesforce Mobile that mirrors Lightning Experience
Intuitive Navigation for Salesforce Mobile that mirrors Lightning Experience

There are also some Lightning App Builder improvements that let you preview pages better and use filters to set component visibility.

Set Component Visibility in Lightning App Builder
Set Component Visibility in Lightning App Builder

Using the Salesforce Mobile App QuickStart Page

There will be some steps Salesforce Administrators/Developers will need to take to support the transition to the new Salesforce Mobile App. To assist with that process, Salesforce has provided a new link in Setup called, New Salesforce Mobile App Quickstart.

The QuickStart includes a section called “Enable Your Customizations for Mobile” and in it is the following:

  • Run the Salesforce Optimizer for Mobile Lightning Pages Report (Beta) – Mostly includes recommendations for how you can fine-tune pages/apps for mobile. Once generated, a copy will be available in Files.
  • Update Your Lightning Apps for Mobile – Adds the phone form factor to apps. You can also do this manually through App Manager.
  • Update Your Pages for Mobile – Adds the phone form factor to desktop-assigned pages. You can also do this manually though Lightning App Builder.

Wrapping up the Course

The key points I hope you take away from the course are:

  • Don’t be afraid to embrace Aura components, even if you are a junior developer, a Salesforce Admin or a developer that has only worked with Visualforce. Simple Aura components are not difficult and can really improve the Lightning Experience for your customers.
  • Embrace the new modern tools that Salesforce offers and which this course goes over in depth. Even though you can create Aura components just using Developer Console, I strongly recommend that you embrace the modern toolset and Visual Studio Code. This will also help you to transition to creating Lightning Web Components, which can only be created with these tools.
  • When using Apex, always consider security since CRUD and FLS security is not automatically enforced.
  • However, if you can utilize the Base Lightning Components that do not use Apex, consider using them. It means you will not have to write Apex or Apex Unit tests, or worry about security.
  • Always consider the Usability of your components by using the Design Resources and making the components as flexible as possible.
  • Keep learning and pass what you learn onto others in the Salesforce community.

All of the code shown in the course, can be accessed through the following GitHub repo.

And if you are interested in going deeper with Aura components, check out my other intermediate Pluralsight course about Lightning Component Best Practices.

The course should be released soon. Tune in here for an announcement.

Thank you, Sara

Post 4 – Customizing Salesforce with Lightning Aura Components Series

This will be the fourth of a series of posts I will be doing over the next few weeks. This particular post will cover working with the Record Form Base Lightning Component. The series will all lead up to the introduction of my new course titled, “Customizing Salesforce with Lightning Aura Components” from Pluralsight. This course is a total re-write of my most popular course, “Customizing Salesforce with Lightning Components: Getting Started“, which was released back in early 2017. It will focus on using the new modern toolset that Salesforce offers as part of Salesforce DX.

Working with Record Forms

The recordEditForm and recordViewForm components have been around for a while, but the recordForm component was introduced in Summer 18 and was a big improvement. In fact, I was so impressed with this component, I wrote about it on this blog.

The recordForm component represents the best of two other components and essentially merges them into one. This means you will need to write less JavaScript code in the form of handlers, such as onload and onsubmit because the new component will handle these automatically.

Along with less markup, since you will not have to use lightning:input or lightning:output, along with cancel and submit buttons. They are provided for you automatically. And finally, the recordForm component is exposable to more platforms, which at this time includes Lightning Experience, Lightning Communities, Salesforce Mobile App, along with Lightning Out combined with Visualforce.

And the Drawbacks? Well, it is slightly less flexible and as of now, there is a known issue involving validation that I hope you consider voting for (since that is the only way Salesforce will fix it).

Creating a New Quick Case Component

In this section, I will show you how to build a component that let’s users quickly create a new case. The end result will be a component like this:

Quick Case Create Component
Quick Case Create Component

This component will use the recordForm base lightning component. Here is the code for the markup:

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" 
                            access="global"
>
    <aura:attribute name="fields" type="String[]" default="['Status','AccountId',
                                    'ContactId','Origin','Type','Reason','Subject']" />
    <aura:attribute name="reqFields" type="String[]" default="['Origin','Status']" />
    <aura:attribute name="recordId" type="String"/>
    <lightning:notificationsLibrary aura:id="notifLib"/>

    <lightning:card iconName="standard:case" title="Quick Case Create" >
        <p class="slds-p-horizontal_small">
            <lightning:recordForm objectApiName="Case" 
                          fields="{!v.fields}" 
                          onsuccess="{!c.handleSuccess}"  />
        </p>
    </lightning:card>	
</aura:component>	

And here is the code for the client-side controller. Note that it includes a commented method that would handle validation, since the component unfortunately does not do this (at this time – refer to issue above). I don’t really consider this a workaround, since the whole purpose of the component was to eliminate the need for the additional code. I personally think Salesforce needs to fix it, and hope you agree.

({
    handleSuccess : function(cmp, event, helper) {
           cmp.find('notifLib').showToast({
            "variant": "success",
            "title": "Case Created",
            "message": "Record ID: " + event.getParam("id")
        });
    }
    // },
    // handleValidation : function(cmp, event, helper) {
    //     // NOTE: This method will handle validation if you specify which field to check for specifically, which is 
    //     //  not a good workaround since the component should handle this for you without the need to write
    //     //  all this unecessary code (especially since the whole point of the component is to lessen the
    //     //  amount of code that needs to be written). That is why it was not mentioned in the actual course.
    //     var evtFields = event.getParam("fields");
    //     var reqField = 'Origin';
    //     if (evtFields.hasOwnProperty(reqField)) {
    //         event.preventDefault();  //Stops the record from being created
    //         cmp.find('notifLib').showToast({
    //             "variant": "error",
    //             "header": "Error was encountered",
    //             "message": "The following field is required: " + reqField
    //         });
    //     } 
        

})

The code includes the notifications base lightning component. This is used to alert the user with a toast message when the case was added successfully.

Creating Quick Actions

In addition to including this component on the Sales Home page, I exposed it as a quick action and then created a global action using the following steps in Setup.

  1. In Quick Find, type “global” and select Global Actions.
  2. Click New Action.
  3. Select “Lightning Component” as the Action Type.
  4. Select the quickCase component from the drop down list.
  5. Enter a label as “Quick Case Create” and tab to next line.
  6. Click Save to Finish.

After creating the global action, you still need to add the new action to the Publishers layout, which you do using the following steps:

  1. In Quick Find, type “Publisher” and select Publisher Layout.
  2. Click Edit to modify the default layout.
  3. Select “Mobile and Quick Actions”.
  4. Scroll down to the section for Salesforce Mobile and click the override the predefined actions link.
  5. Drag the Quick Case Create action onto the section.
  6. Click Save to finish.

In the next and final post, I will go over the newly redesigned Salesforce Mobile App.

Post 3 – Customizing Salesforce with Lightning Aura Components Series

This will be the third of a series of posts I will be doing over the next few weeks. This particular post will cover working with Salesforce Data. The series will all lead up to the introduction of my new course titled, “Customizing Salesforce with Lightning Aura Components” from Pluralsight. This course is a total re-write of my most popular course, “Customizing Salesforce with Lightning Components: Getting Started“, which was released back in 2017. It will focus on using the new modern toolset that Salesforce offers as part of Salesforce DX.

Working with Controllers

In the last post, I walked you through creating the updateCellNumber Aura component in Visual Studio Code. I also stepped you through how to add a design file so that the placeholder value was easily modifiable in Lightning App Builder.

So far this component will only show a windows alert when the submit button is pressed. I am going to show you how to change that and have it update the Salesforce database instead.

Lightning Components have two ways of dealing with Salesforce data:

  1. Using the Lightning Data Service
  2. Using Apex

In this post, I will only covering the second method of using Apex to write server-side code.

Working with Apex and DML

Apex is strongly-typed object-oriented proprietary language developed by Salesforce. Since Salesforce is a multi-tenanted environment, Apex is subject to limits.

DML or Data Manipulation Language is what Salesforce uses to insert, update or delete a Salesforce record. To change the updateCellNumber component so that it writes to the Salesforce database, I will need to add a Class that uses both Apex and DML. The class will be named updateCellNumberApex (so that it is not named the same as the client-side JavaScript controller). Any server-side methods used by Lightning components must have the @AuraEnabled annotation. They also must be public and static. The code for the class will be as follows:

public with sharing class updateCellNumberApex {
    @AuraEnabled()
    public static void CellNumberUpdate(String cellNumber) {
        List<User> users = [SELECT MobilePhone FROM User where 
                            Id =: UserInfo.getUserId()];
        
        if ( users.size() > 0 ) {
            users[0].MobilePhone = cellNumber;
            update users;
        }
    }
}

This code also uses a SOQL or Structured Object Query Language statement to select the MobilePhone number for the logged in user.

Once the class has been added, I will need to modify the component markup for updateCellNumber so that it is linked to this class. I do that by adding the following attribute to the aura:component tag, like this:

<aura:component controller="UpdateCellNumberApex"....

I will then need to switch to the client-side JavaScript controller and start by removing the code that produced a windows alert and replace it with the following:

var cellNumber = cmp.find("phone").get("v.value");
var action = cmp.get("c.CellNumberUpdate");
action.setParams({ "cellNumber" : cellNumber });
action.setCallback(this, function(response) {
    var state = response.getState();
    if (state === "SUCCESS") {
         alert(state);
     }
});
$A.enqueueAction(action);

Creating a New Open Cases Component

This component will display a list of open cases and will be named openCases. The final component will look like the following:

Open Cases Configureable Component
Open Cases Configureable Component

This Aura component will utilize the lightning:datatable base lightning component. Here is the code for the component markup:

<aura:component controller="getOpenCasesApex" implements="force:appHostable,flexipage:availableForAllPageTypes" access="global">
    <!-- attributes -->
    <aura:attribute name="caseList" type="Object"/>
    <aura:attribute name="columns" type="List"/>
    <aura:attribute name="numRecords" type="Integer" default="10" />

    <!-- handlers-->
    <aura:handler name="init" value="{! this }" action="{! c.init }"/>

    <lightning:card title="Open Cases">
        <lightning:datatable
                keyField="id"
                data="{! v.caseList }"
                columns="{! v.columns }"
                hideCheckboxColumn="true"/>
    </lightning:card>

</aura:component>	

And here is the JavaScript for the client-side controller:

({
    init : function(cmp, event, helper) {
        var numRecords = cmp.get("v.numRecords")

        cmp.set('v.columns', [
            {label: 'Subject', fieldName: 'linkName', type: 'url',
                typeAttributes: {label: {fieldName: 'Subject'}, target: '_self'}},
            {label: 'Type', fieldName: 'Type', type: 'text'},
            {label: 'Reason', fieldName: 'Reason', type: 'text'},
            {label: 'Status', fieldName: 'Status', type: 'text'},
        ]);

        var action = cmp.get("c.getOpenCases");
        action.setParams({"numRecords" : numRecords });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var records = response.getReturnValue();
                records.forEach(function(record){
                    record.linkName = '/' + record.Id;
                });
                cmp.set('v.caseList', records)
            }
        });
        $A.enqueueAction(action);
    }

})

The first part of this code is used to define the columns that will be used in the datatable. Notice that the first one is of the type url and that it has an additional definition for the typeAttributes. This is because this column will not only display the case subject, but will include a link to the case detail record.

The callback function will get whatever records are returned and then loop through those records and set the linkName so that it references the Cases record Id.

This component will utilize an Apex controller named getOpenCasesApex. It includes code needed to check for the appropriate FLS and CRUD level security. It also includes cacheable=true for the @AuraEnabled annotation in order to take advantage of caching for performance.

public with sharing class getOpenCasesApex {
    @AuraEnabled(cacheable=true)
    public static List<Case> getOpenCases(Integer numRecords) {
        // Code needed to check for proper access
        String [] caseFields = new String[] {'Id','Type','Reason','Subject','Status'};

        Map<String,Schema.SObjectField> caseMap = Schema.SObjectType.Case.fields.getMap();
        for (String fieldToCheck : caseFields) {
            if( !caseMap.get(fieldToCheck).getDescribe().isAccessible()) {
                throw new System.NoAccessException();
            }
        }

        List<Case> caseList = [SELECT Id,Type,Reason,Subject,Status FROM Case
        WHERE IsClosed = false
        LIMIT : numRecords];

        return caseList;
    }

}

And finally, it includes a numRecords input parameter, which is passed in form the value set when the component is configured in Lightning App Builder. The associated design file for this component is as follows:

<design:component label="Configurable Open Cases Component">
    <design:Attribute name="numRecords" label="Number of Records Returned" placeholder="10" />
</design:component>

In the next post, I will go over how to design components that do not require an Apex controller.

New Upcoming Pluralsight Course on Customizing Salesforce with Lightning Aura Components

I am VERY happy to announce that I am very close to releasing a new Pluralsight course called “Customizing Salesforce with Lightning Aura Components”. Well, kind of a new course.

Upcoming New Pluralsight Course

It is a total re-write of my most popular course (so far), “Customizing Salesforce with Lightning Components: Getting Started“, which was released back in 2017. That was right before Salesforce DX was released and all the other modern developer tools that Salesforce now promotes (for very good reasons).

In this course, all those great shiny new tools will be used to build very simple Aura Lightning Components, as they are now called. The original course used the online Developer Console, which is what Trailhead uses in all their content about Aura Components. On Trailhead, the new tools are only used for Lightning Web Components.

So, if you want to learn about building Aura Components, while also embracing the new modern toolset, this course is perfect for you. And if you want to learn about Lightning Web Components (LWC’s), then you can check out the “Building Your First Lightning Web Component (LWC) for Salesforce” course that I released earlier this year.

Both courses use the new tools, like:

Salesforce DX

Salesforce Command Line Interface

Salesforce Extensions for Visual Studio Code

Starting next week, I will be releasing a series of weekly blog posts that will feature what content will be covered in each Pluralsight module, followed by an announcement of the courses release.

So, keep an eye out for that.

Post 5 – Building Your First Lightning Web Component for Salesforce Series

This will be the fifth of a series of posts I will be doing over the next few weeks. They will all lead up to the introduction of my new course in January 2020 titled, “Building Your First Lightning Web Component for Salesforce” from Pluralsight. These posts will contain final code snippets of the code used in each course module, but will not include all the tips and best practices about using Visual Studio Code and SFDX, along with the way I personally approach teaching in my video courses.

Converting Aura Components to Lightning Web Components

In this module, I will be sharing things I learned based on mistakes I made when I first converted an Aura app to LWC’s. This was the result of that attempt.

For a while I intended to use what I did in that first converted app for this course, but luckily for you, I did not. Instead, I contacted my friend Kevin Hill (@KevinJHill) at Salesforce and he volunteered to review my app. He politely suggested that I make a few changes and thank goodness I did.

The biggest mistake I made was not fully accepting this inevitable truth at first:

“The LWC programming model is fundamentally different than the Aura model”

That message is so important to your success with LWC’s, so here it is again:

I did finally accept this and therefore re-designed the solution I ended up using for this course. The version I will show in the course may look similar to the original version, but underneath it is different and if you watch the course you will even get to see performance results that demonstrate that.

The talented developers at Salesforce (which included Kevin Hill and many others), did an incredible job creating Lightning Web Components. If you attempt to just blindly convert an Aura app to an LWC without accepting that the programming model is different, then you will not get the performance advantages that it provides. And imho, that is like spitting in the face of those GREAT developers at Salesforce.

So, please do not do that. And I hope you watch my course when it is released soon. It is almost finished.

Post 4 – Building Your First Lightning Web Component for Salesforce Series

This will be the fourth of a series of posts I will be doing over the next few weeks. They will all lead up to the introduction of my new course in January 2020 titled, “Building Your First Lightning Web Component for Salesforce” from Pluralsight. These posts will contain final code snippets of the code used in each course module, but will not include all the tips and best practices about using Visual Studio Code and SFDX, along with the way I personally approach teaching in my video courses.

Working With Salesforce Data

The most important thing you need to be aware of when working with Salesforce Data is the Lightning Data Service, or LDS. If you have worked with Aura components, then you have probably already heard about it since it was introduced back in late 2017. The LDS is built on top of the UI or User Interface API. This is the API that Salesforce developers use internally to build the Lightning Experience.

The reason why has to do with the following benefits:

  1. Caching, so loading data is as fast as possible
  2. Progressive loading of data, which also improves performance
  3. Optimization of server calls, which guess what, offers performance advantages.

Hopefully, you are seeing the key factor here.

Using the Lightning Data Service Wire Adapter

As I mentioned in the last post, I am building this solution in stages. In the last post, I created a very simple child component named leadListItem to display all the leads returned by a search term. This was ok, but the output was definitely not pretty. The easiest way to fix that is to take advantage of one of the many LWC base components, which as of Dreamforce 2019, are now mostly available as open source.

And one of the best and easiest to use base components, lightning-datatable offers incredible benefits such as formatting for appropriate data types, header and row level actions, resizing of columns sorting and text wrapping. And there are even more.

I will be replacing the template code in leadList.html that called the leadListItem component, with a call to the lightning-datatable. The HTML will now look like this:

<template>
    <lightning-card title="Lead Search" icon-name="standard:search" class="slds--around_medium">
        <div class="slds-box slds-theme_default">
            <lightning-input
                label="Search Term"
                variant="label-hidden"
                placeholder="Search by name, phone, website, or address"
                type="text"
                value={searchTerm}
                onchange={handleSearchTermChange}>
            </lightning-input>
         </div>
        <div class="slds-m-around_small">
            <lightning-datatable
                key-field="id"
                data={leads}
                columns={cols}
                onrowaction={handleRowAction}
                hide-checkbox-column = "true">
            </lightning-datatable>
            <template if:true={error}>
                // Hint. In the actual course, I will implement something better than this
                The following error was encountered: {error}
            </template>
        </div>
    </lightning-card>
</template>

I will need to remove the JSON data in the JavaScript and add a constant to support the column definitions, as well as a new tracked variable for the columns and another for the error that could be returned and only rendered when there is an actual error using the if:true directive in the HTML template.

To help minimize the number of Apex calls, I will use a common technique that was implemented in the lwc-recipes repo. It involves slightly delaying the dispatch of the newsearch Custom Event.

I will also need to add a new imports command that is used to call Apex Code that returns results from a search. And, I will need to add another imports for the wire directive to the top import command. The wire directive is used to read Salesforce data reactively and when the data is provisioned, it automatically re-renders the component. This will be implemented in loadLeads.

And finally, there will be a handler that uses the navigation service to navigate the user to the Lead record page if they decide to click the info icon displayed in the last datatable column. The code for the leadList.js will now look as follows:

import { LightningElement, track, wire } from 'lwc';
import searchLeads from '@salesforce/apex/LeadSearchController.searchLeads';
import { NavigationMixin } from 'lightning/navigation';

/** The delay used when debouncing event handlers before a method call. */
// This code was copied directly from the trailheadapps/lwc-recipes GitHub repo
const DELAY = 350;

// It is a general convention to capitalize constants
const COLS = [
    {
        label: 'Name',
        fieldName: 'Name',
        type: 'text'
    },
    {
        label: 'Title',
        fieldName: 'Title',
        type: 'text'
    },
    {
        label: 'Company',
        fieldName: 'Company',
        type: 'text'
    },
    {
        label: 'View',
        type: 'button-icon',
        initialWidth: 75,
        typeAttributes: {
            title: 'View Details',
            alternativeText: 'View Details',
            iconName: 'action:info'

        }
    }
];

export default class LeadList extends NavigationMixin(LightningElement) {
    @track leads = [];
    @track searchTerm;
    @track cols = COLS;
    @track error;
    
    handleSearchTermChange(event) {
        this.searchTerm = event.target.value;
        if (this.leads) {
            const selectedEvent = new CustomEvent('newsearch', {detail: this.searchTerm});
            window.clearTimeout(this.delayTimeout);
            // eslint-disable-next-line @lwc/lwc/no-async-operation
            this.delayTimeout = setTimeout(() => {
                this.dispatchEvent(selectedEvent);
            }, DELAY);
        }
    }

    @wire(searchLeads, {
        searchTerm: '$searchTerm'
    })
    loadLeads({ error, data }) {
        if (data) {
            this.leads = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.leads = undefined;
        }
    }

    handleRowAction(event) {
        const row = event.detail.row;
        this.record = row;
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: row.Id,
                actionName: 'view',
            },
        });
    }

}

The last thing to do is to create the Apex class that will do the actual search of the Salesforce data. It will use a SOSL query to perform a special kind of search across multiple fields in a single object. Notice that the searchLeads method includes an @AuraEnabled tag, as well as the cacheable = true. This ensures the data returned will be cached, which is perfect for mutable data such as the lead addresses. The code for that will look as follows:

public with sharing class LeadSearchController {
    
    @AuraEnabled(cacheable=true)
    public static List<Lead> searchLeads( String searchTerm ) {
        List<Lead> leads = new List<Lead>();
        if ( String.isNotBlank( searchTerm ) ) {
            List<List<SObject>> searchResults = [
                FIND :searchTerm
                RETURNING Lead(
                    Id, Name, Title, Company,
                    Street, City,
                    State, PostalCode
                    ORDER BY Name
                    LIMIT 10
                )
            ];
            leads = searchResults[0];
        }
        return leads;
    }
}

Using the CLI to Load Data

At this point, if you were to save and push all this code to your scratch org and then open the Lead Locator tab and do a search, what do you think would happen?

Well, you should get nothing returned because by default scratch orgs are not loaded with any Lead data. But, we can use some very handy CLI commands to export data from another developer org and then import it into our scratch org as JSON data.

I have already used the export command to get 10 lead records from a developer org. That command looked like the following:

sfdx force:data:tree:export --query \                                                                	"SELECT ID, FirstName, LastName, Phone, Company, Title, Street, City, \ 		State, PostalCode FROM Lead limit 10" \                                                	
--outputdir ./data --json

And to import them, I will only need to use the terminal in Visual Studio Code to execute another CLI command that looks like this:

sfdx force:data:tree:import -f data/Lead.json -u <your scratch org username here>

Adding the Lead Map Component

To finish off the solution, I will need to add the leadMap component. It will use another very useful Lightning base component called lightning-map. That base component can display multiple locations using geocoding and mapping imagery from Google Maps. The leadList.html for that component will look like this:

<template>
    <template if:true={leads}>
        <lightning-map
                zoom-level="11"
                map-markers={markers}
                markers-title="Leads">
        </lightning-map>
    </template>
    <template if:true={error}>
            // Hint. In the actual course, I will implement something better than this
            The following error was encountered: {error}
    </template>
</template>

The leadList.js file will import the same LeadSearchController code used in the leadList, but remember that this data is cacheable. And the @wire directive will do a search using a new variable called searchInput. This variable will be passed to the leadMap component as a public variable from the owner component using the @api directive. The final code for leadList.js will look like this:

import { LightningElement, track, wire, api } from 'lwc';
import searchLeads from '@salesforce/apex/LeadSearchController.searchLeads';

export default class leadMap extends LightningElement {
    @track markers = [];
    @track error;
    @track leads;

    // Private variable
    searchTerm;

    @api get searchInput() {
        return this.searchTerm;
    }

    set searchInput(value) {
        this.searchTerm = value;
    }

    @wire(searchLeads, {
       searchTerm: '$searchInput'
    })
    loadLeads({ error, data }) {
        if (data) {
            this.leads = data;
            this.markers = data.map(lead => {
                return {
                    location: {
                        Street: lead.Street,
                        State: lead.State,
                        City: lead.City,
                        PostalCode: lead.PostalCode
                    },
                    title: JSON.stringify(lead.Name),
                    icon: 'utility:pinned'
                }
            })
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.leads = undefined;
            this.markers = [];
        }
    }
}

The last thing to change is code in the Owner component. In the myFirstLWC.html, I will need to replace the placeholder text with the a call to the new leadList child component. But, to prevent the map from being displayed when the user first loads the page, I will need to use another CustomEvent called searchcomplete. If no search has been done, then the user will get a helpful info message displayed. The HTML code for the owner will now look like this:

<template>
    <div class="c-container">
            <lightning-layout multiple-rows="true">
                <lightning-layout-item padding="around-small" size="12">
                    <lightning-layout>
                        <lightning-layout-item padding="around-small" size="6">
                            <div class="slds-box slds-theme_default">
                                <c-lead-list onnewsearch={handleNewSearch}
                                onsearchcomplete={handleSearchComplete}></c-lead-list>
                            </div>
                        </lightning-layout-item>
                        <lightning-layout-item padding="around-small" size="6">
                            <div class="slds-box slds-theme_default">
                                    <lightning-card title="Lead Map" icon-name="action:map" class="slds--around_medium">
                                        <template if:true={searchComplete}>
                                            <c-lead-map search-input={searchInput}></c-lead-map>
                                        </template> 
                                        <template if:false={searchComplete}>
                                            <div class="slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_info" role="alert">
                                                <span class="slds-assistive-text">info</span>
                                                <h2>You will need to enter a search term before a map is displayed.</h2>
                                            </div>
                                        </template> 
                                    </lightning-card>  
                                </div>
                        </lightning-layout-item>    
                    </lightning-layout>
                </lightning-layout-item>
            </lightning-layout>
        </div>
</template>

Of course, this will mean I will also need to modify the myFirstLWC.js file to include a handler for that new CustomEvent. This code will now look as follows:

import { LightningElement, track } from 'lwc';

export default class MyFirstLWC extends LightningElement {
    @track searchTerm;
    @track searchInput;
    @track searchComplete = false;

    handleNewSearch(event) {
        this.searchTerm = event.target.value; 
    }

    handleSearchComplete(event) {
        this.searchInput = event.detail;
        this.searchComplete = true;
    }
}

And to explain all this in terms of the component communication, consider the following diagram:

But, the course is not done, and the best is yet to come in the next post in which I will go over converting Aura components using an example similar to this with a completely different approach that I think you will find very interesting.

And finally, if you are super excited about this, you might want to consider taking advantage of this limited time offer from Pluralsight for 40% off an annual subscription until December 6, 2019.

40% Off Pluralsight – Last Chance to Save