Post 3 – Communicate Between Unrelated Salesforce Lightning Web Components

This is the latest series of posts I am doing on a new Pluralsight course I created titled, “Communicate Between Salesforce Lightning Web Components”. You can also check out a free 10 minute preview video that explains how I got to this point in the post series.

Welcome Back

In the last post, I showed you a solution to display products, but only focused on how parent to child communication worked in that example. In this post, I will point out the portions of the code that involve working with an unrelated component and show you the unrelated component that displayProducts will be working with.

Just to refresh your memory, the code for displayProducts is as follows:

<!-- displayProduct.html -->

<template>
    <div class="slds-card slds-var-p-around_x-small">
        <lightning-input
            label="Search Key"
            type="text"
            onchange={handleSearchKeyChange}
            class="search-bar"
        ></lightning-input>
        <template if:true={products.data}>
          <template if:true={products.data.length}>
             <div class="content">
                <template for:each={products.data}
                     for:item="product">
                     <!-- Product Tile Component here-->
                     <c-product-tile
                        key={product.Id}
                        product={product}
                        onselected={handleProductSelected}
                        class="slds-var-m-around_x-small">
                     </c-product-tile>
                 </template>
              </div>
            </template>
            <template if:false={products.data.length}>
                <p>No products matching your selection</p>
            </template>
        </template>
        <template if:true={products.error}>
            <p>Error when getting product data</p>
        </template>
    </div>
</template>


// displayProducts.js

import { LightningElement, wire } from 'lwc';

import { publish, MessageContext } from 'lightning/messageService';
import PRODUCT_SELECTED_MESSAGE from '@salesforce/messageChannel/ProductSelected__c';

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

export default class DisplayProducts extends LightningElement {

    searchKey = '';     
    
    // Load context for Lightning Messaging Service 
    @wire(MessageContext) messageContext;

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

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

    handleSearchKeyChange(event) {
        this.searchKey = event.target.value.toLowerCase();
    }

}

Notice in the code above that each productTile uses an onselected event to fire off the handleProductSelected function. Also notice that at the top of the JavaScript controller, publish and MessageContext functions are imported from lightning/messageService. These are required when working with Lightning Message Service (LMS).

Working with Lightning Message Service

Lightning Message Service (LMS) was introduced in Summer 20 and it offers a standard way to communicate across the DOM, or Document Object Model. LMS can be used with LWC’s, along with Aura components and even Visualforce pages – as long as they are contained within the same lightning page.

The Lightning App Page that is used to host the solution has two regions. displayProducts will be loaded into the left-hand region and on the right will be a new component named productCard.

When a user clicks a product tile in displayProducts, a message will be sent to the productCard component, allowing it to display additional detail information. Communicating with unrelated components allows a user to easily switch between the tiles and additional product information without having to navigate to an actual Salesforce detail page.

Steps to work with LMS:

  1. Create a message channel.
  2. Define scope of message channel.
  3. Publish to the message channel.
  4. Component that should receive data will subscribe to the message channel

Below is the code for the productCard component (which is the unrelated component in this example):

<!-- productCard.html -->

<template>
    <lightning-card icon-name="standard:apex_plugin">
        <template if:true={recordId}>
            <span slot="title">{productName}</span>
            <lightning-button-icon
                icon-name="utility:expand_alt"
                slot="actions"
                onclick={handleNavigateToRecord}
            ></lightning-button-icon>
            <div class="slds-var-m-horizontal_medium">
                <img
                    if:true={productPictureUrl}
                    src={productPictureUrl}
                    class="product"
                    alt="Product picture"
                />
                <lightning-record-view-form
                    record-id={recordId}
                    onload={handleRecordLoaded}
                    object-api-name="Product2"
                    density="compact"
                >
                    <lightning-output-field
                        field-name={familyField}
                    ></lightning-output-field>
                    <lightning-output-field
                        field-name={productCodeField}
                    ></lightning-output-field>
                    <lightning-output-field
                        field-name={msrpField}
                    ></lightning-output-field>
                    <lightning-output-field
                        field-name={descriptionField}
                    ></lightning-output-field>
                </lightning-record-view-form>
            </div>
        </template>
        <template if:false={recordId}>
            <div class="slds-var-p-around_large">
                <p class="placeholder">Select a product to see details</p>
            </div>
            
        </template>
    </lightning-card>
</template>

//  productCard.js

import { LightningElement, wire } from 'lwc';

// Lightning Message Service and a message channel
import { NavigationMixin } from 'lightning/navigation';
import { subscribe, MessageContext } from 'lightning/messageService';
import PRODUCT_SELECTED_MESSAGE from '@salesforce/messageChannel/ProductSelected__c';

// Utils to extract field values
import { getFieldValue } from 'lightning/uiRecordApi';

// Product__c Schema
import PRODUCT_OBJECT from '@salesforce/schema/Product2';
import NAME_FIELD from '@salesforce/schema/Product2.Name';
import PRODUCT_CODE_FIELD from '@salesforce/schema/Product2.ProductCode';
import FAMILY_FIELD from '@salesforce/schema/Product2.Family';
import MSRP_FIELD from '@salesforce/schema/Product2.MSRP__c';
import DESCRIPTION_FIELD from '@salesforce/schema/Product2.Description';
import PICTURE_URL_FIELD from '@salesforce/schema/Product2.Picture_URL__c';

/**
 * Component to display details of a Product__c.
 */
export default class ProductCard extends NavigationMixin(LightningElement) {
    // Exposing fields to make them available in template
    familyField = FAMILY_FIELD;
    msrpField = MSRP_FIELD;
    productCodeField = PRODUCT_CODE_FIELD;
    descriptionField = DESCRIPTION_FIELD;

    // Id of Product__c to display
    recordId;

    // Product fields displayed with specific format
    productName;
    productPictureUrl;

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

    /** Subscription for ProductSelected Lightning message */
    productSelectionSubscription;

    connectedCallback() {
        // Subscribe to ProductSelected message
        this.productSelectionSubscription = subscribe(
            this.messageContext,
            PRODUCT_SELECTED_MESSAGE,
            (message) => this.handleProductSelected(message.productId)
        );
    }

    handleRecordLoaded(event) {
        const { records } = event.detail;
        const recordData = records[this.recordId];
        this.productName = getFieldValue(recordData, NAME_FIELD);
        this.productPictureUrl = getFieldValue(recordData, PICTURE_URL_FIELD);
    }

    /**
     * Handler for when a product is selected. When `this.recordId` changes, the
     * lightning-record-view-form component will detect the change and provision new data.
     */
    handleProductSelected(productId) {
        this.recordId = productId;
    }

    handleNavigateToRecord() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: this.recordId,
                objectApiName: PRODUCT_OBJECT.objectApiName,
                actionName: 'view'
            }
        });
    }
}

Notice in the HTML code above (which is bolded), there is a lightning button icon component. If clicked the user will be directed to the products record page. This is just in case that is what the user would prefer to happen. Displaying the record detail information will be done using the lightning record view form base component, along with several output field base components.

Also notice in the Javascript code, import statements to access functions from the lightning message service, as well as the newly created message channel. The next import statement will include the uiRecordApi utility that allows me to extract field values for product fields, along with several import statements used to access schema metadata for specific Product2 fields.

Inside the class, I will begin by adding some local variables that will expose some of that schema metadata. Below that I need to define the scope of the message context, which in this case will just be the default of the active area. This is only available to me when using the @wire context.

Next is the handleRecordLoaded handler which is fired when the lightning record view form base component first loads. It will get the product record ID passed in the event message channel and use the uiRecordApi to extract the field values for the product name and picture URL.

The end result of this solution are two unrelated components that communicate seamlessly. The image below shows you what it looks like to have the components work together in a Lightning page for Experience Cloud.

Final rendered solution in Salesforce Experience Cloud
Final rendered solution in Experience Cloud

The code and explanations in this post is just a sample of all that is covered in the Pluralsight course, Communicate Between Salesforce Lightning Web Components”. Check that out if you are interested in learning more about how LWC’s communicate. You can also check out a free 10 minute preview video that explains how I got to this point in the post series.

Studying for the Salesforce Certified Sharing and Visibility Designer Exam?

The second course in a new Skill Path series on Pluralsight, designed to help prepare you for the Salesforce Certified Sharing and Visibility exam was just published. The new course is titled, “Share Salesforce Data Across Users, Groups, and Roles“.

Using a real-world scenario involving a global company, you’ll learn how to build a record sharing model for your Salesforce org. First, you’ll explore the Salesforce sharing architecture and the different layers of record sharing security Salesforce provides.

From there, you’ll discover how to set org-wide defaults and design a role hierarchy. Finally you will learn how to open up access when needed with sharing rules or manual sharing. When you’re finished, you will have the skills and knowledge to design a robust and secure sharing model for your Salesforce organization.

And look out for future posts on this blog about this very complex and critical piece of the Salesforce Security architecture.

Post 2 – Communicate Between Salesforce Lightning Web Components: Parent to Child

This is the latest series of posts I am doing on a new Pluralsight course I created titled, “Communicate Between Salesforce Lightning Web Components”.

Working with Public Properties

Public properties can be used to pass data from a parent component to one or more children components. For Lightning Web Components (LWC), data is passed from the parent to the child using the @api decorator.

Parent to Child Communication
Parent to Child Communication

Decorators are used in JavaScript to wrap one piece of code with another. That is why they are called decorators. The @ symbol is placed before the name of the decorator and for LWC the @api decorator is a special decorator used to expose public properties or methods.

When used with a property, these properties/fields are reactive. This means whenever the value of the property changes, the component is automatically rerendered. For this reason, each property can be associated with only one decorator. This also applies when a method is decorated.

Displaying Products

In this post, I will be working with nested components used to search for and display products. The parent or container component will be named displayProducts. The code for this component is seen below:

<!-- displayProduct.html -->

<template>
    <div class="slds-card slds-var-p-around_x-small">
        <lightning-input
            label="Search Key"
            type="text"
            onchange={handleSearchKeyChange}
            class="search-bar"
        ></lightning-input>
        <template if:true={products.data}>
          <template if:true={products.data.length}>
             <div class="content">
                <template for:each={products.data}
                     for:item="product">
                     <!-- Product Tile Component here-->
                     <c-product-tile
                        key={product.Id}
                        product={product}
                        onselected={handleProductSelected}
                        class="slds-var-m-around_x-small">
                     </c-product-tile>
                 </template>
              </div>
            </template>
            <template if:false={products.data.length}>
                <p>No products matching your selection</p>
            </template>
        </template>
        <template if:true={products.error}>
            <p>Error when getting product data</p>
        </template>
    </div>
</template>


// displayProducts.js

import { LightningElement, wire } from 'lwc';

import { publish, MessageContext } from 'lightning/messageService';
import PRODUCT_SELECTED_MESSAGE from '@salesforce/messageChannel/ProductSelected__c';

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

export default class DisplayProducts extends LightningElement {

    searchKey = '';     
    
    // Load context for Lightning Messaging Service 
    @wire(MessageContext) messageContext;

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

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

    handleSearchKeyChange(event) {
        this.searchKey = event.target.value.toLowerCase();
    }

}

Things to notice in the bolded portion of the displayProducts.html file above:

  1. Since a search may return multiple products, a child component named productTile will be used to display just one product as a tile.
  2. An iterator will be used to display as many instances of this child component as there are records returned.
  3. I am passing the product id through the key attribute and the actual product data through the product attribute.

Here is the HTML and JS for the productTile component.

<!-- producTile.html -->

<template>
  <a onclick={handleClick}>
    <div class="content">
      <img
         src={pictureUrl}
         class="product slds-align_absolute-center"
         alt="Product picture"
      />
      <div>
         <p class="title slds-align_absolute-center">{name}</p>
         <p class="slds-align_absolute-center">
            MSRP:&nbsp;
            <lightning-formatted-number
               format-style="currency"
               currency-code="USD"
               value={msrp}
               class="price"
               maximum-fraction-digits="0">
            </lightning-formatted-number>
         </p>
      </div>
    </div>
  </a>
</template>


// productTile.js

import { LightningElement, api } from 'lwc';

export default class ProductTile extends LightningElement {

    _product;
    pictureUrl;
    name;
    msrp;


    @api
    get product() {
        return this._product;
    }
    set product(value) {
        this._product = value;
        this.pictureUrl = value.Picture_URL__c;
        this.name = value.Name;
        this.msrp = value.MSRP__c;
    }

    handleClick() {
        const selectedEvent = new CustomEvent('selected', {
            detail: this.product.Id
        });
        this.dispatchEvent(selectedEvent);
    }


}

Things to notice in the bolded portion of the productTile.js file above:

  1. api was added to the list of modules to be imported.
  2. Below that will be private variables used for the product data passed from the parent component.
  3. Data will be passed to the child through getter and setter functions, which need to be annotated with the @api decorator, in order to make them public.

The end result of all of this is a component that looks like this:

Final rendered version of the displayProducts component with child productTile component
Final rendered version of the displayProducts component with child productTile component

The code and explanations in this post is just a sample of all that is covered in the Pluralsight course, Communicate Between Salesforce Lightning Web Components”. Check that out if you are interested in learning more about how LWC’s communicate.

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.

Why You Really Need to Checkout the Open Source Base Lightning Components Now

As you hopefully know, Lightning Web Components (LWC’s) were introduced in 2019. Right away, Don Robins met with Salesforce expert, Chuck Liddell and they produced a free Pluralsight course, titled, “Play by Play: Understanding Lightning Web Components for Salesforce Developers“.

In that course, Chuck walked everyone through the Open Source Recipes Repo that Salesforce provided when it first released LWC’s. I remember in one part of that excellent course, that Chuck mentioned that we could not see the source code for any of the base Lightning components. Specifically, he mentioned the lightning:card component. In the course he said,

“And if I were able to look at the source code for that component, I’m sure that I would see that their attribute title is a type Aura Component Array…”

Well, the GREAT news is that Salesforce has released all those Base Lightning Components as open source on GitHub. So now everyone can see that code and learn about the best practices that Salesforce has implemented in those components.

Anyone can clone or download the source code and then follow the directions in the README to spin up these components in a scratch org. Once you do, you can open that scratch org and using the App Launcher, browse to the Base Components app. From here, they can select one of the tabs, such as the one for the Layout Components, and see first-hand how that lightning Card component works, as well as click the link to view the source.

Is that not the coolest thing in the world? I sure think so.

I would be remiss if I did not also mention the Pluralsight course I just released called, “Building Your First Lightning Web Component for Salesforce“. It is not free, but come on, Pluralsight is soooooo worth the subscription cost.

Building Your First Lightning Web Component for Salesforce Pluralsight Course is Live!!!

I am so excited to finally announce that my new Pluralsight course, “Building Your First Lightning Web Component for Salesforce” course is Live!

New Pluralisght course "Building Your First Lightning Web Component for Salesforce"

The development of this course was a labor of love for me and I have to admit something I worked so hard on. I would not compromise the quality for anything (including the three deadlines I missed). Luckily, I was working with some of the best editors in the world (huge shout out to Bentley Lignell, Stacy Sohn and Austin Allen).

As for the course, it covers the following:

  • LWC Benefits and what are Web Components
  • Setting up you development environment with Salesforce, Visual Studio Code and Salesforce DX
  • Component Design using Custom DOM events
  • Working with Salesforce Data using the Wire Adapter and Debugging with Chrome Developer Tools
  • Converting Aura components and lessons I learned
  • Testing your JavaScript with Jest

In the course, we will be building an application that will look like this:

Lead Locator LWC app

If you are looking for the final code created in this course, you can find it here.

For anyone that might not be a Pluralsight subscriber yet, here is a link to a free 10-day trial.

And finally, please feel free to send me your feedback on the course. Good or bad. I appreciate it all because it helps me to develop better content that meets your needs.

Accessing the Final Code for Building Your First Lightning Web Component though GitHub.

My Pluralsight course, “Building Your First Lightning Web Component for Salesforce” was just released. But, I have created a GitHub repo, where you can go ahead and download the final code solution (which includes a Jest test used to test the LeadList component.

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

This will be the sixth 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.

Handling Testing and Wrapping Up

If you are already a Salesforce developer then you know that server-side Apex Unit tests are required, but client-side JavaScript tests are not and if you are like most developers, you probably think that creating tests should be optional, kind of like documenting, right?

You might not believe this, but writing tests (even for non-required JavaScript) can actually save you a lot of time, such as:

  • Time from chasing down bugs
  • Fixing those bugs
  • Releasing your fix using whatever release process your company uses

Introducing Jest

Jest is an easy to use tool that was originally created by the engineers at Facebook. It is now supported as open-source by the community. Salesforce DX provides a utility that lets you setup and configure Jest for your LWC project.

There are a few steps involved in getting this setup, which include:

  • Install prerequisites (which includes the latest versions of node.js and npm). Even if you already have node installed, it is recommended that you install the Long Term Support Version of Node.js, which will also include npm. This will guarantee that critical bugs will be fixed for a total of 30 months. You can install the LTS version from here.
  • Setup LWC VS Code project with npm – Go to the Terminal window in your VS Code project and from the root folder, run the following command:

npm init

This will create a package.json file. Just hit enter through all the prompts

  • Install sfdx-lwc-jest and dependencies – Also from Terminal window:

npm install

This will create a package-lock.json file

npm install @salesforce/sfdx-lwc-jest --save-dev

  • Identify test script – Open the package.json file and add this to the scripts section:
,
    "test:unit": "sfdx-lwc-jest --coverage",
    "test:unit:watch": "sfdx-lwc-jest --watch",
    "test:unit:debug": "sfdx-lwc-jest --debug"

Writing Jest Tests

Unlike Apex unit tests, Jest tests are run locally and are completely independent of Salesforce. As a best practice, you should create all your tests in a special folder at the root of your project that is named __tests__. If you look at the .forceignore file in your project, you will see that an entry for this folder already exists, thus excluding any tests from being pushed to your scratch org.

In the course, I walk you through creating a test of the leadList component. That test is just a JavaScript file that is created in the __tests__folder and is named: leadList.test.js (which is also a recommended best practice). In that folder, there is also a data subfolder that contains two JSON files named: getLeads.json and getNoLeads.json. Below is the code for the test:

import { createElement } from 'lwc';
import leadList from 'c/leadList';
import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
import searchLeads from '@salesforce/apex/LeadSearchController.searchLeads';

// Represent a list of valid records
const mockGetLeads = require('./data/getLeads.json');
// Represent a list of no records
const mockGetNoLeads = require('./data/getNoLeads.json');
// Register the Apex wire adapter
const searchLeadsAdapter = registerApexTestWireAdapter(searchLeads);

describe('c-lead-list', () => {
    beforeAll(() => {
        // Use fake timers as setTimeout
        jest.useFakeTimers();
    });

    // Best practice to cleanup after each test
    afterEach(() => {
        // The jsdom instance is shared across test cases in a single file so reset the DOM
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
    });

    describe('searchLeads @wire returns records with search parameter', () => {
        it('called with data from input', () => {
            const USER_INPUT = 'Bertha';
            const SEARCH_TERM = { searchTerm: USER_INPUT };

            // Create the leadList element
            const element = createElement('c-lead-list', {
                is: leadList
            });
            document.body.appendChild(element);

            // Simulate user input
            const inputValue = element.shadowRoot.querySelector('lightning-input');
            inputValue.value = USER_INPUT;
            inputValue.dispatchEvent(new CustomEvent('change'));

            // Run the fake timers
            jest.runAllTimers();

            // Return a promise to wait for asynchronous results
            // and fail if promise is rejected
            return Promise.resolve().then(() => {
                expect(searchLeadsAdapter.getLastConfig()).toEqual(
                    SEARCH_TERM
                );
            });
        });

        it('renders data of one record', () => {
            const USER_INPUT = 'Bertha Boxer';

            // Create the leadList element
            const element = createElement('c-lead-list', {
                is: leadList
            });
            document.body.appendChild(element);
            
            // Simulate user input
            const inputValue = element.shadowRoot.querySelector('lightning-input');
            inputValue.value = USER_INPUT;
            inputValue.dispatchEvent(new CustomEvent('change'));

            // Run the fake timers
            jest.runAllTimers();

            // Use the searchLeadsAdapter to emit data
            searchLeadsAdapter.emit(mockGetLeads);

            // Return a promise to wait for asynchronous results
            // and fail if promise is rejected
            return Promise.resolve().then(() => {
                // select some elements that would be rendered if it succeeded
                const detailElement = element.shadowRoot.querySelector('lightning-datatable');
                const rows = detailElement.data;
                expect(detailElement.length).toBe(searchLeadsAdapter.length);
                expect(rows[0].Name).toBe(mockGetLeads[0].Name);
            });

        });

        it('renders data when no record is available', () => {
            const USER_INPUT = '';

            // Create the leadList element
            const element = createElement('c-lead-list', {
                is: leadList
            });
            document.body.appendChild(element);

            // Simulate user input
            const inputValue = element.shadowRoot.querySelector('lightning-input');
            inputValue.value = USER_INPUT;
            inputValue.dispatchEvent(new CustomEvent('change'));

            // Run the fake timers
            jest.runAllTimers();

            // Use the searchLeadsAdapter to emit data
            searchLeadsAdapter.emit(mockGetNoLeads);

            // Return a promise to wait for asynchronous results
            // and fail if promise is rejected
            return Promise.resolve().then(() => {
                // select some elements that would be rendered if it succeeded
                const detailElement = element.shadowRoot.querySelector('lightning-datatable');
                expect(detailElement.length).toBe(searchLeadsAdapter.length);
            });
        });

    });

    describe('searchLeads @wire returns error', () => {
        it('shows error panel', () => {
            // Create the leadList element
            const element = createElement('c-lead-list', {
                is: leadList
            });
            document.body.appendChild(element);

            // Emit error from @wire
            searchLeadsAdapter.error();

            // Return a promise to wait for any asynchronous DOM updates. Jest
            // will automatically wait for the Promise chain to complete before
            // ending the test and fail the test if the promise rejects.
            return Promise.resolve().then(() => {
                const errorPanelEl = element.shadowRoot.querySelector(
                    'c-error-panel'
                );
                expect(errorPanelEl).not.toBeNull();
            });
        });
    });

})

And here are the two data files:

getLeads.json:

[
    {
        "Name": "Bertha Boxer",
        "Phone": "(850) 644-4200",
        "Company": "Farmers Coop. of Florida",
        "Title": "Director of Vendor Relations",
        "Street": "321 Westcott Building",
        "City": "Tallahassee",
        "State": "FL",
        "PostalCode": "32306"
    }
]

and getNoLeads.json:

[]

Running Jest Tests

To run the test seen above, you will need to return to the Terminal window and run this command:

npm run test:unit

Wrapping Up

So, this is the last post in the series and I should be announcing the release of this course soon. Sorry it has taken so long, but I hope you go away with these thoughts:

  •  Less frameworks; more web standards  
  • Embrace VS Code and Salesforce DX  
  • Use CustomEvents when possible  
  • LWC programming model is very different  
  • Never be embarrassed about mistakes  
  • Keep learning and share it

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 3 – Building Your First Lightning Web Component for Salesforce Series

This will be the third 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 titled, “Building Your First Lightning Web Component for Salesforce” from Pluralsight. These posts will contain code snippets of the code used in the course, but will not include all the tips and best practices about using SFDX, along with the way I personally approach teaching in my video courses.

You also have to realize that the solution is built in stages and so the code you see here is not exactly what you will see by the end of the next module. For all that, you will just have to wait and watch the course, which I hope you do.

And Finally, I know there has been a big delay between the first two posts and these remaining posts, but I promise you that the delay will have been worth the wait. I am VERY proud of how this course is turning out and feel like it will be my best yet. 

Component Design

LWC’s are similar to Aura components in the fact that you should consider each component as an application building block. And just like with Aura components, most LWC’s will contain many smaller, nested components. The biggest difference is that the communication patterns between Aura components and LWC’s is VERY different.

LWC Nested Composition

NestedComposition

In this course, we will actually be creating multiple LWC components that demonstrate a nested pattern. At the outermost layer we will have a Lightning App Page and inside of that will be an owner or container component. The owner can contain other components, which also contain other components. What is happening here is that we have a parent-child relationship going on and that is very important is determining how data is passed between LWC components.

Creating the Owner Component

At first this component (which we will name myFirstLWC) will just contain placeholder text for where the two components it will contain will ultimately reside. Here is the HTML we will use for now:

<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">
                                <h2>TO DO: Add List LWC Component here</h2>
                            </div>
                        </lightning-layout-item>
                        <lightning-layout-item padding="around-small" size="6">
                            <div class="page-section page-main">
                                <h2>TO DO: Add Map LWC Component here</h2>
                            </div>
                        </lightning-layout-item>
                    </lightning-layout>
                </lightning-layout-item>
            </lightning-layout>
        </div>
</template>

You will also need to modify the metadata file so it exposes this component (but ONLY this component since it is the owner) to the Lightning App Builder.

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="MyFirstLWC">
    <apiVersion>47.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>     
      <target>lightning__RecordPage</target>
      <target>lightning__AppPage</target>
      <target>lightning__HomePage</target>
  </targets>
</LightningComponentBundle>

  1. Type “Lightning App” in Quick Find and select Lightning App Builder.
  2. Click New to start the Wizard.
  3. Leave the default on App Page and click Next.
  4. Enter the label “Lead Locator” and click Next.
  5. Select One Region and click Finish.
  6. In the Builder, scroll down to to the Custom List and drag the myFirstLWC onto the design surface.
  7. Click Save.
  8. Click Activate when prompted.
  9. From the Activation Wizard, accept the defaults, and select the Lightning Experience tab.
  10. Select Sales and click Add Page to app.
  11. Click Save.
  12. Click Back to return to Setup.
  13. Click the App Launcher and select the Sales app.You should see the Lead Locator app and you should select it to see your very basic app so far.

Creating the LeadList Component

The Here is the HTML for our leadList component. Notice that it contains two other nested components. One is a Lightning base component called lightning-input and the other is a custom one yet to be created called lead-list-item.

<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>
            <h3>Leads for {searchTerm}</h3>
        </div>
        <div class="slds-m-around_small">
            <template if:true={leads}>
                <template for:each={leads} for:item="lead">
                    <c-lead-list-item key={lead.Id} lead={lead} ></c-lead-list-item> 
                </template>
            </template>
        </div>
    </lightning-card>
</template>

Since this module is only about Composition and not about data, the JavaScript for this component will just contain some static JSON code to represent the lead data. It will also use the Track decorators for the leads array and the searchTerm, which allow you to keep track of a properties value when it is re-rendered.

Most importantly, it will follow best practices and use the simplest way to communicate with events by using a standard CustomEvent called newsearch.

import { LightningElement, track} from 'lwc';

export default class LeadList extends (LightningElement) {
    @track leads =[];
    @track searchTerm;

    handleSearchTermChange(event) {
        this.searchTerm = event.target.value;
        const selectedEvent = new CustomEvent('newsearch', {detail: this.searchTerm});
        this.dispatchEvent(selectedEvent);
    }

    leads = [
        {
            "Id": "LeadRef1",
            "Name": "Bertha Boxer",
            "Title": "Director of Vendor Relations",
            "Company": "Farmers Coop. of Florida",
            "Street": "321 Westcott Building",
            "City": "Tallahassee",
            "State": "FL",
            "PostalCode": "32306"
        },
        {
            "Id": "LeadRef2",
            "Name": "Phyllis Cotton",
            "Title": "CFO",
            "Company": "Chamber of Commerce",
            "Street": "300 E Park Ave",
            "City": "Tallahassee",
            "State": "FL",
            "PostalCode": "32301"
        },
        {
            "Id": "LeadRef3",
            "Name": "Jeff Glimpse",
            "Title": "SVP, Procurement",
            "Company": "Tallahassee Taxes",
            "Street": "1327 Colorado St",
            "City": "Tallahassee",
            "State": "FL",
            "PostalCode": "32304"
        }
    ];

}

 Modifying the Owner Component

Since we added a CustomEvent to the leadList component, we will need to add some JavaScript to the owner component, myFirstLWC that will handle this event.

import { LightningElement, track } from 'lwc';

export default class MyFirstLWC extends LightningElement {
    @track searchTerm;

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

We will also need to modify the HTML so that we remove the placeholder text for the leadList component. Notice though how it is named. All LWC’s will be rendered with the c namespace, followed by a hyphen and then the component name, which is also separated by a hyphen. LWC knows that the leadList component is two words because we used kebab or camel case when naming it. This is a commonly used HTML standard and it means that the component must start with a lowercase letter and the other letter must be capitalized with no spaces in between, which is how we named the leadList component.

<c-lead-list onnewsearch={handleNewSearch}></c-lead-list>

Creating the leadListItem Component

This is just a very basic component that will display the results of the search. Not much to it for now.

<template>
    <lightning-layout vertical-align="center">
        <lightning-layout-item padding="around-small">
            <p>{lead.Name}</p>
        </lightning-layout-item>
        <lightning-layout-item padding="around-small">
            <p>{lead.Title}</p>
        </lightning-layout-item>
        <lightning-layout-item padding="around-small">
            <p>{lead.Company}</p>
        </lightning-layout-item>
    </lightning-layout>
</template>

And here is the JavaScript for that component. It will use the api decorator since the lead that is passed to it is a public property.

import { LightningElement, api } from 'lwc';

export default class LeadListItem extends LightningElement {
    @api lead;
}

So that is all we have for now. If you push all the code we have here and go to do a search, you should see the term you type displayed right below it, but not much else is happening yet. But stay tuned for the next post, because that is where all the magic will happen and the solution will come together.