New Course on Managing Account Access with Salesforce Territory Management

This course is one of several, designed to prepare you on your journey towards completing the Salesforce Certified Sharing and Visibility Designer Exam. In this course, you will learn how to successfully setup a Territory model using Salesforce’s Enterprise Territory Management.

By the end of the course, you will know how to do the following:

  1. Define Salesforce Enterprise Territory Management
  2. Understand the benefits of using Enterprise Territory Management
  3. Understand how to setup Territory Management in Salesforce
  4. Explain the capabilities of Enterprise Territory Management and its impact on data visibility
  5. Explain how Enterprise Territory Management impacts forecasting in Salesforce
Globomantics is a robotics company that needs to configure Salesforce Enterprise Territory Management
Globomantics is a robotics company that needs to configure Enterprise Territory Management

The fictional company at the center of the story line for this course and the whole series will be Globomantics. The main character of the story will be a Salesforce Architect that has been asked to design the security model for this Salesforce Enterprise customer. The story for this course will be that the Architect is continuing her analysis of their org and is now assisting on building territories to help with account management.

If you are able to check out the course, I would love to hear your feedback. Especially, if you are using this course to help prepare you for the certification exam.

Why Lightning Web Runtime Is a Game Changer for All Web Developers

Lightning Web Runtime (LWR)

Salesforce’s annual Dreamforce 2021 was different in many ways. A very select number of people were invited to attend (thanks COVID). Most of Dreamforce was done virtually for the widest audience possible. For this reason, the content delivered came off more as an informercial and did not have the meat that Dreamforce attendees are used to. I kept waiting for a 1-800 number to appear on the screen. It had to be said.

What was the same was that there are always lots of big announcements. But due to the infomercial style, most Salesforce developers probably missed the biggest announcement Lightning Web Runtime is available as an NPM package.

So what does this really mean? In my opinion, it is a game changer for Salesforce developers, but possibly all web developers.

For starters, the Lightning Web Runtime (LWR) means that Lightning Web Components (LWC’s) can be used to assemble web applications that render a full page in less than one second. Yes, that is what I said. BOOM!

This means that Lightning Apps can actually render as fast as we have all been hoping for since the word “Lightning” was introduced. Will the real modern high performance web architecture please stand up?

Thanks to page generation at build time, not runtime, our bar is set at subsecond full-page loads.

LWR Docs – https://developer.salesforce.com/docs/platform/lwr/guide/lwr-intro.html

But the best part is that because of the release of the NPM package, serious web developers can build high performing web apps that may or may not connect to Salesforce. I’m serious. This is the flexibility that Salesforce developers have been wanting forever.

What is coming? Open Source, but no time frames on when that will be. I also imagine there will be more features and hopefully lots of demo apps.

My advice to you is to “Run, don’t walk” to the LWR guide as soon as possible. I know I am. And stay tuned here, because there is so much more to come.

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 Course on Salesforce Security: Getting Started

Salesforce Security: Getting Started is the first in a series that is dedicated to helping you prepare for the Salesforce Certified Sharing and Visibility Designer exam. This course is designed to take you beyond just learning the necessary facts and give you real-world scenarios that should help prepare you on your journey towards certification.

The company at the center of this course is Globomantics, a fictional global robotics provider. They are also a new Salesforce Enterprise customer with over 20,000 employees located across the globe.

Globomantics is using Field Service Lightning to help their employees provide world-class customer service. They have hired a Solution architect to help them design a secure and scalable sharing architecture on Salesforce.

In this course, you will learn how to evaluate and configure object, field-level, role and security settings that provide access to the platform. When you’re finished, you will have the skills and knowledge to declaratively provide high-level data access to the relevant people in your Salesforce organization.

Record sharing, which is a very complex area will not be covered until the next course in this series. But that should be coming out in the next month. Stay tuned for that announcement.

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

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

Communicating With Events

In this post, I will be covering the different ways nested lightning web components can communicate from the child to the parent using custom events.

Child to Parent Component Relationship

Custom events are used to communicate up the component hierarchy. They allow a child to communicate with it’s parent component.

Even though they are based on a web standard, Lightning Web Components offer a CustomEvent interface to create and dispatch these events.

For simple events, in which no data needs to be passed to the parent component, the custom event can be created and dispatched with a single line of code, such as what you see below:

handleClick() {
	 this.dispatchEvent(new CustomEvent('clicked'));
}

The event type, which in this example is “clicked” is required. It should also follow the DOM event naming standard. This means there should be no uppercase letters or spaces. If two words are used, they should be separated with an underscore.

Child to Parent Example

I am going to show you a nested component scenario and point out the important things to consider. In this scenario the children will not be able to communicate with each other. They will only communicate with their parent component. I will also use Lightning App Builder to create a one-region app page to host this parent component. The end result will look like the following:

Simple Child to Parent Communication
Simple Child to Parent Communication

Parent Component Code

In this example, there will be a parent component that will act as a container component and nested inside of it will be two children components named child and child2.

<!-- parent.html -->

<template>
    <lightning-card 
        title="Child to Parent Communication" 
        icon-name="utility:people">
        <lightning-layout vertical-align="start">
            <lightning-layout-item padding="around-small" size="6">
                <div class="slds-box_small">
                    <b>Event Name:</b>
                    <lightning-formatted-text 
                        class="slds-m-left_small" 
                        value={eventName}>
                    </lightning-formatted-text>
                    <div class="slds-box slds-m-around_small">
                        <c-child onclicked={handleButtonClicked}></c-child>
                     </div>
                </div>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small" size="6">
                <div class="slds-box_small">
                    <b>Event Name:</b>
                    <lightning-formatted-text 
                        class="slds-m-left_small" 
                        value={eventName2}>
                    </lightning-formatted-text>
                    <div class="slds-box slds-m-around_small">
                        <c-child2 onclicked2={handleButtonClicked2}></c-child2>
                    </div>
                </div>
            </lightning-layout-item>
      </lightning-layout>
    </lightning-card>
</template>
//parent.js

import { LightningElement } from 'lwc';

export default class Parent extends LightningElement {
    eventName;
    eventName2;

    handleButtonClicked2(event) {
       //this.eventName2 = 'Child2 Button Clicked: ' + event.detail;
       this.eventName2 = 'Child2 Button Clicked: ' + 
            event.detail.ename + event.detail.num;
    }

    handleButtonClicked(event) {
        this.eventName = 'Child Button Clicked';
    }
    
}
<!-- parent.js.meta.xml-->

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>50.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>LWC Events</masterLabel>
    <description>This is a component demonstrating how child to parent event relationship works</description>
    <targets>
        <target>lightning__AppPage</target>
    </targets>
</LightningComponentBundle>

Simple Child Component

This child component will use an inline event handler to simply pass a notification to the parent that a button has been clicked inside the child. There is not much to say about this method since it is very straight forward.

<!-- child.html -->

<template>
    <lightning-card title="Simple Child Component">
        <lightning-layout vertical-align="start">
            <lightning-layout-item padding="around-small" size="12">
                <lightning-button 
                    variant="brand" 
                    label="Fire Event from Simple Child" 
                    onclick={handleClick} 
                    class="slds-m-around_small">
                </lightning-button> 
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
</template>
// child.js

import { LightningElement } from 'lwc';

export default class Child extends LightningElement {
    
    handleClick() {
        this.dispatchEvent(new CustomEvent('clicked'));
    }

}

Child Component Passing Data

The child2 component will be more complex since it will pass multiple data elements from the child to the parent component. The first input data field can be used to enter a text-based field and the second one allows the user to enter a number. The values for both data fields will be concatenated together and displayed as an event name in the parent component.

<!-- child2.html -->

<template>
    <lightning-card title="Complex Child Component">
        <lightning-layout vertical-align="start">
            <lightning-layout-item padding="around-small" size="12">
                <lightning-input 
                    type="text" 
                    name="inName" 
                    label="Enter a name:" 
                    class="slds-m-around_small"
                    onchange={handleNameChange}>
                </lightning-input>
                <lightning-input 
                    type="number" 
                    name="inNumber" 
                    label="Enter a number:" 
                    class="slds-m-around_small"
                    onchange={handleNumberChange}
                    placeholder="2">
                </lightning-input>
                <lightning-button 
                    variant="brand" 
                    label="Fire Event from Complex Child" 
                    onclick={handleClick} 
                    class="slds-m-around_small">
                </lightning-button> 
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
</template>
// child2.js

import { LightningElement } from 'lwc';

export default class Child2 extends LightningElement {
    eventNumber = 0;
    copiedObject;
    eventObject = {
        num : 0,
        ename: ''
    };

    handleNumberChange(event) {
        this.eventObject.num = event.detail.value;
    } 

    handleNameChange(event) {
        this.eventObject.ename = event.detail.value;
    } 

    handleClick() {
        // Make a shallow copy into new object
        this.copiedObject = Object.assign({}, this.eventObject);
        this.dispatchEvent(
            new CustomEvent('clicked2', { detail: this.copiedObject } 
        ));
        

    }
}

The really important thing to observe in the Javascript controller for child2 is the code in the handleClick function. I am using an object to pass both the number and name value in the CustomEvent.

On the surface this seems simple, but the official Salesforce documentation includes a warning that custom events should only pass primitive data, such as a string or a number and not an object. This is because any listener (which could be a malicious one) can mutate the passed object and change the values.

To avoid this issue, I am making a shallow copy of the eventObject into a new variable named copiedObject. Only the value of the copied object is passed in the detail of the CustomEvent.

If you found this post helpful, you might want to check out my Pluralsight course, “Communicate Between Salesforce Lightning Web Components”.

Preview Video for Communicating Between Lightning Web Components Course

Below you will find a preview video for my latest Pluralsight course, “Communicate Between Salesforce Lightning Web Components”. This is not a course for beginners new to LWC’s or Salesforce. But, if you plan on building an app that involves multiple LWC’s and you already know the basics, then this course is for you and hopefully this preview video will convince you of that.

Communicate Between Salesforce Lightning Web Components

In this course you will learn how to effectively communicate between LWC’s. First you will discover the three methods used to communicate between components. Then you will learn details about each method by seeing how they work in a real-world app. By the end of the course, you will know how to build your own high-performing Lightning-based app that is assembled with Lightning Web Components. This course is focused solely on LWC communication.

Building a Complex LWC app? You may need to learn more about how they communicate

Are you a Salesforce developer that has been tasked with building a complex app involving multiple Lightning Web Components (LWC’s)?

The most important thing to understand is all the ways these components can communicate. Without a solid understanding of this concept, you may struggle and inadvertently create a poor performing or error prone app.

That is why I am happy to announce that my latest Pluralsight course, “Communicate Between Salesforce Lightning Web Components” has just been released. This is not a course for beginners new to LWC’s or Salesforce. But, if you plan on building an app that involves multiple LWC’s and you already know the basics, then this course is for you.

Communicate Between Salesforce Lightning web Components

In this course you will learn how to effectively communicate between LWC’s. First you will discover the three methods used to communicate between components. Then you will learn details about each method by seeing how they work in a real-world app. By the end of the course, you will know how to build your own high-performing Lightning-based app that is assembled with Lightning Web Components. This course is focused solely on LWC communication.

I hope you find the course useful. If you do check it out and have feedback, I would love to hear it – good or bad. You can give feedback through Pluralsight or here on my blog. Either way, it would be appreciated. Thank you.

Post 2 – Building Lightning-based Sites with Experience Builder

This will be the second of a series of posts I am doing on a new Pluralsight course titled, “Developing and Extending a Salesforce Community Experience with Code”.

Community Cloud (now known as Experience Cloud) allows you to easily create Lightning-based sites. Even though Salesforce offers many out-of-the-box components for these sites, many customers still want to customize sites with their own custom lightning components. This post will walk you through what might happen when a customer wants to customize their partner community.

Adding Components to Lightning Community

There are a few questions you need to ask yourself before creating a lightning component for your lightning-based Community.

  1. Is there not already a standard component available or a free one on the App Exchange? If so, then it is best to use one of those before creating your own.
  2. If you answered no to that first question, then the next thing to ask is, “Will this be an Aura or LWC component?” This is important to ask because each is developed not only with different tooling, but they involve very different developer skill sets.
  3. But you may not be able to fully answer that second question until you have asked yourself the last question, which is, “What type of component do you need to build?” For now (at least), Aura-based components must be used to build any layout or theme components.

Building and Configuring Lightning Web Components

Lightning Web Components were introduced in 2019 and they represent the future of Salesforce development. They were specifically developed to address limitations with the original Lightning components, which are now known as Aura components.

Below you will find the code for a multi-component solution which allows users to search for and displays products as picture tiles. The container or parent component is called displayProducts.

Rendered version of the Display Products Component
Rendered version of the Display Products Component – clicking one of the tiles directs the user to the product detail page

Below is the HTML markup for the displayProducts component:

<template>
    <div class="slds-card slds-var-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-var-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}>
                <p>There are no products matching your current selection</p>
            </template>
        </template>
        <template if:true={products.error}>
            <p>An error was encountered trying to get product data</p>
        </template>
    </div>
</template>

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

// This component is very similar to the productTilelist LWC in the E-Bikes Sample app
// https://github.com/trailheadapps/ebikes-lwc

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

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

export default class DisplayProducts extends LightningElement {
    // Corresponds to the targetConfig properties in the meta file
    @api searchBarIsVisible = false;
    @api tilesAreDraggable = false;

    pageNumber = 1;       // Current page in the product list
    totalItemCount = 0;   // The total number of items matching the selection.
    pageSize;             // The number of items on a page.
    filters = {};         // JSON.stringified version of filters to pass to apex  
   
    // Load the list of available products.
    @wire(getProducts, { filters: '$filters', pageNumber: '$pageNumber' })
    products;

    handleProductSelected(productId) {
        this.recordId = productId;
    }

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

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

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

}

Nested inside the displayProducts component are two other components – productTile and paginator. The paginator component is one you can find the code for in this GitHub repo. The productTile component will direct the user to the product detail page and the HTML for the component is below:

<template>
    <div draggable={draggable} ondragstart={handleDragStart}>
        <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>
    </div>
</template>

And the JavaScript associated with that component is below:

import { LightningElement, api } from 'lwc';

import { NavigationMixin } from 'lightning/navigation';
import PRODUCT_OBJECT from '@salesforce/schema/Product2';

/**
 * A presentation component to display a Product__c sObject. The provided
 * Product__c data must contain all fields used by this component.
 */
export default class ProductTile extends NavigationMixin(LightningElement) {
    /** Whether the tile is draggable. */
    @api draggable;

    _product;
    
    @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;
    }

    /** Product__c field values to display. */
    pictureUrl;
    name;
    msrp;

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

    }

    handleDragStart(event) {
        event.dataTransfer.setData('product', JSON.stringify(this.product));
    }
}

Building and Configuring Aura Components

For Experience cloud communities, Aura-based components will typically only come into play when there is a need to customize the communities theme and layout. The type of customizations you may want to make include:

  • Detailed layout structure
  • Design tokens bundle
  • Custom configuration properties used to customize the layout

For example, if I wanted to create a custom theme layout, I could create an Aura component bundle named customThemeLayout. This is what the markup for such a component might look like. Take special notice of the implements attribute as that part is key to exposing this to Experience Builder.

<aura:component implements="forceCommunity:themeLayout"
               access="global">
    <!-- Represent all the columns in the layout -->
    <aura:attribute name="left" type="Aura.Component[]"  />
    <aura:attribute name="right" type="Aura.Component[]"  />
    <aura:attribute name="navBar" type="Aura.Component[]"
                                  required="false"/>

    <!-- Configuration Properties in Design File   -->
    <aura:attribute name="leftColWidth" type="Integer" 
            default="7" />
    <aura:attribute name="rightColWidth" type="Integer" 
            default="5" />

    <div class="navigation">
        {!v.navBar}
    </div>
	<div class="mainContentArea">
        <lightning:layout>
            <lightning:layoutItem size="{!v.leftColWidth}">
                {!v.left}
            </lightning:layoutItem>
            <lightning:layoutItem size="{!v.rightColWidth}">
                {!v.right}
            </lightning:layoutItem>
    	</lightning:layout>
    </div>
</aura:component>

The layout theme above is designed for two columns and the sizes of those columns are configureable. Since I am using configureable attributes for the column sizes, I will need to include a design file in this bundle, such as this:

<design:component label="Two Column Configurable layout"> 
    <design:attribute name="leftColWidth" 
              label="Left Column Width" />
    <design:attribute name="rightColWidth" 
              label="Right Column Width" />
</design:component>

This will be the second of a series of posts I am doing on a new Pluralsight course titled, “Developing and Extending a Salesforce Community Experience with Code”.