Aura Components: Still Alive & Kicking!

Do you know about Lightning Aura Components? These are still a part of Salesforce, and even though they’re not as popular as they once were, they’re still important. I work for a company that uses them a lot, and we don’t want to change them. This is because the newer version called LWC can’t do everything that Aura components can.

In the past months, I’ve been busy working with Aura components. I’m not upset about it. In fact, I enjoy the challenges my job brings. I think working with Aura components is fun, and I like solving problems with them.

But, I’ve noticed there aren’t many resources to help people learn how to make or fix these components. Even my own online class about them has been taken down. But don’t worry! You can still find the class and learn about Lightning Aura Components if you click on this link.

I also have a place online where you can find examples of how to use them. And, I’ve written a few posts about the class that you can find from this article.

I plan to write more about the problems I face while working with these components. I might even create more examples for you to use. I hope this helps other people who are also working with Aura components.

Aura components were used for 5 years before LWC came out. That means a lot of them were made during that time.

I hope that Salesforce sees this and works on making LWC better. Right now, it can’t do everything it needs to.

Moving to Lightning? Should you learn about Aura or LWC?

I have had a few people ask me this question and I think it is a good one, so I wanted to answer it in a post. First of all, for anyone not sure what the difference is between Aura and LWC, you might want to checkout this post I did.

As for the answer….wait for it…..It depends. It depends on:

  1. How experienced you are as a Developer with JavaScript and HTML5, or modern development tools such as Visual Studio Code, Command Line Interfaces, GitHub? If you are very experienced and have even done work with React or Angular, then I would suggest starting with LWC’s. Developing with Aura components is a legacy method that Salesforce is not actively enhancing.
  2. HOWEVER, If you are supporting or might be supporting a legacy Salesforce org? Knowing about Aura (and even Visualforce) is a very valuable skill set to have. For example, if you are doing work for a large org that is currently using many Aura components and there is no need to change these components, then having that Aura skillset can be invaluable. If you want to learn about Aura components, you night want to check out this course I did on Pluralsight. Most importantly, as of early 2023, LWC’s cannot be used to override standard buttons.

Like Salesforce Evangelists, I do think that LWC’s are the better alternative in terms of component performance, but if all you need to build are very simple components, then there is no reason why you cannot start with Aura and transition to LWC’s. To be entirely honest, LWC’s are more challenging to create for developers that are not already familiar with modern web development.

I hope this post helps anyone not sure about which technology to approach first. Feel free to reach out to me if you have additional questions or concerns.

Sara

Need to Learn How to Build Lightning Web Components?

Today my course about Getting Started with Salesforce Lightning Web Components was published on Pluralsight. This is my 14th course for Pluralsight. This not a tutorial. Instead, I tried to to explain why things were done the way they were and provide tips for how you can do the same when building your own solutions.

10 minute preview video

If you do not have a subscription to Pluralsight, you can always sign up for a free 10-day trial here. I have also created a GitHub repo that contains all the code for the course. Here you will find detailed instructions for how to install this in your own scratch org.

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.

Post 1 – Customizing your Salesforce Community with Code

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

Community Cloud (now known as Experience Cloud) has been part of the Salesforce platform for years and it is used by many companies to actively communicate with their communities of customers, partners and employees. Hence the community name.

Developing and Extending a Salesforce Community Experience with Code

Selecting a Template

The first step in creating a new community involves selecting a template. The template determines what type of community is created.

The important thing to realize is that there are two basic template types.

  • Lightning-based
  • Visualforce + tabs

To help you decide which type might be right for you, here is a comparison of the two.

Compare Community Template Types
Compare Community Template Types

I would suggest you select lightning-based templates unless there is some compelling reason to use a Visualforce template.

What you will learn in this course

In this course you will be learning about..

  • Developing custom Lightning components to extend a Lightning-based community
  • Building a Visualforce community with standard Visualforce pages and Apex classes.
  • What code-based sharing and visibility customizations you can make to secure your community.
  • Opportunities to extend your community through integrations with other communities and clouds

Stay tuned for additional posts that will cover the remaining modules for this course. You can access the course on Pluralsight here. If you do not have a subscription, then you can sign up for a free 10 day trial.

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.

New Pluralsight Course: Getting Started Customizing Salesforce with Lightning Aura Components

I am very proud to announce the release of my latest Pluralsight course, “Customizing Salesforce with Lightning Aura Components“. This is actually a total re-write of my most popular course, “Customizing Salesforce with Lightning Components”, released in 2017. The key word change in the title is the addition of the word, “Aura“.

New Pluralsight Course, which includes working with VS Code and the CLI
New Pluralsight Course, which includes working with VS Code and the CLI

This course is different because at the time that course was released, Lightning Components were not called Lightning Aura Components AND most importantly, the new modern toolset known as Salesforce DX was not yet available. There were also no Lightning Web Components available yet.

I personally believe that for many developers, transitioning to LWC would be much easier if they were to first learn to build simple Aura components using the new modern tools first.

In this course, all those great shiny new tools will be used to build very simple Aura Lightning Components. The original course used the online Developer Console, which is what Trailhead still 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.

Either way, you will be knowledgeable in all the latest that Salesforce has to offer. I personally believe that for some developers, transitioning to LWC would be much easier if they were to first learn to build simple Aura components using the new modern tools first.

This course includes a GitHub Repo that features all the code covered in the course. The material covered includes the following:

Understanding the Lightning Component Framework

Refer to the following blog post #1 . In this post/Pluralsight module), you will learn:

  1. Who Should be Building Aura Components?
  2. Where Can Aura Components Be Used?
  3. Anatomy of an Aura Component Bundle
  4. Creating an Aura Component Bundle

Creating Aura Components with the Salesforce CLI

Refer to the following blog post # 2 .

  1. Using an Interactive Development Environment (IDE)
  2. Getting Setup with an IDE
  3. Understanding Salesforce DX (SFDX)
  4. Creating an Aura Component in Visual Studio (VS) Code
  5. Exposing Aura Components to Lightning App Builder

Working with Data

Refer to the following blog post # 3 .

  1. Working with Controllers
  2. Working with Apex and DML
  3. Creating a New Open Cases Component
  4. Using the CLI to Load Data to Scratch Org
  5. Enforcing Apex Security
  6. What About Caching and Usability?

Working with Record Forms

Refer to the following blog post # 4 .

  1. Working with Record Form Base Lightning Components
  2. Creating a New Quick Case Component
  3. Creating Quick Actions

Working with Salesforce Mobile

Refer to the following blog post # 5 .

  1. Working with the new Salesforce Mobile App
  2. Using the Salesforce Mobile App QuickStart Page
  3. Wrapping Up

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.

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.