Download as pdf or txt
Download as pdf or txt
You are on page 1of 466

Student Guide

Program Lightning Web


Components
SFDEX-602SU21v1-SG
Agenda
Program Lightning Web Components (DEX602)

Day One

15 minutes Introductions

120 minutes Course Introduction


▪ Course Objectives
▪ Course Prerequisites
▪ System Requirements
▪ Class Project
▪ Review the Application
▪ Introduction to Salesforce DX and Git
Exercise 1-1: Meet the Prerequisites (100 min)

265 minutes Lightning Web Components


▪ Get to Know the Lightning Web Components Model
Your Turn 2-1 (10 min): Review the Available
Components
▪ Create Lightning Web Components
Your Turn 2-2 (15 min): Create a
Lightning Web Component
▪ Style Components
Your Turn 2-3 (15 min): Theme a Lightning Web
Component
▪ Define Component Properties
Your Turn 2-4 (30 min): Work with
Component Properties
▪ Handle DOM Events
Your Turn 2-5 (15 min): Handle DOM Events

15 minutes Wrap Up
Agenda
Program Lightning Web Components (DEX602)

Day Two

360 minutes Lightning Web Components (continued)


▪ Debug Lightning Web Components
Your Turn 2-6 (15 min): Debug your Code
▪ Work with Salesforce Data
Your Turn 2-7 (20 min): Work with Apex
▪ Use Base Lightning Components
Your Turn 2-8 (30 min): Work with
Base Lightning Components
▪ Raise and Handle Events
Your Turn 2-9 (45 min): Work with Events
▪ Advanced Communication between Components
Your Turn 2-10 (40 min): Communicate between
Components

40 minutes Lab: Use Lightning Data Service


▪ Phase 1: Output Data from Lightning Data Service (40
min)

15 minutes Wrap Up
Agenda
Program Lightning Web Components (DEX602)

Day Three

230 minutes Surface Lightning Web Components


▪ Build Lightning Pages with Components and App
Builder
Your Turn 3-1: Surface a Component in App Builder
(35 min)
▪ Build Components for Lightning Experience
Record Pages
Your Turn 3-2: Build Components for Lightning
Experience Record Pages (30 min)
▪ Surface Lightning Web Components
Your Turn 3-3 (20 min): Surface a Component in
Lightning Experience
▪ Override Standard Actions
Your Turn 3-4 (20 min): Override Standard Actions
▪ Define a Lightning Application
Your Turn 3-5 (10 min): Create a Lightning Application
▪ Use Lightning in Visualforce Pages with Lightning Out
▪ Your Turn 3-6 (20 min): Surface Components in
Visualforce Pages
▪ Other Supported Experiences

170 minutes Navigation and Layouts


▪ Use <lightning:vertical-navigation>
Your Turn 4-1 (45 min): Implement
Vertical Navigation
▪ Use <lightning-data-table>
Your Turn 4-2 (35 min): Use <lightning-data-table>
▪ Implement Button Groups
Your Turn 4-3 (30 min): Use <lightning-button-group>

15 minutes Wrap Up
Agenda
Program Lightning Web Components (DEX602)

Day Four

60 minutes Navigation and Layouts (con't)


▪ Build Responsive Layouts
Your Turn 4-4 (20 min): Create a Responsive Layout

340 minutes Advanced Components


▪ Create a Custom Datatable
Your Turn 5-1 (50 min): Create a Responsive Datatable
▪ Define Public Methods on Components
Your Turn 5-2 (25 min): Use Custom Events
and Public Methods
▪ Service Components and Toast Notifications
Your Turn 5-3 (35 min): Implement Toasts
▪ Slots and Modal Notifications
Your Turn 5-4 (30 min): Use Slots to Implement Modal
Notifications
▪ Localize Content
Your Turn 5-5 (20 min): Localize Your Components
▪ Renderers and Third-Party JS
Your Turn 5-6 (20 min): Add the "Certification
Popularity" bar chart

15 minutes Wrap Up
Agenda
Program Lightning Web Components (DEX602)

Day Five

250 minutes Work with Data


▪ Implement Forms
Your Turn 6-1 (20 min): Implement a Form using
Standard Controls
▪ Implement a Form using Custom Controls
Your Turn 6-2 (60 min): Implement a Form
using Custom Controls
▪ Validate Input Data
Your Turn 6-3 (30 min): Validate Form Data
▪ View and Edit Salesforce Records
Your Turn 6-4 (75 min): View and Edit
Salesforce Records
▪ Wait for Server Requests to Complete
Your Turn 6-5 (20 min): Display an Interstitial
While Data is Loading

60 minutes Source-Tracked orgs


▪ Org Development Model and Deployments
Your Turn 7-1 (15 min): Deploy a Component to a Dev
Hub

30 minutes LWC's for Aura Developers (optional)

75 minutes Lab: Use Lightning Data Service (Optional)


▪ Phase 2: Use Lightning Data Service CRUD Operations
(45 min)
▪ Phase 3: Read and Output Dynamic Data (30 min)
Program Lightning Web Components
(DEX602)
Copyright

© Copyright 2000-2021 salesforce.com, inc. All rights reserved. Various trademarks


held by their respective owners.
Rights of ALBERT EINSTEIN are used with permission of The Hebrew University of
Jerusalem. Represented exclusively by Greenlight.
This document contains proprietary information of salesforce.com, inc., it is provided
under a license agreement containing restrictions on use, duplication and disclosure
and is also protected by copyright law. Permission is granted to customers of
salesforce.com, inc. to use and modify this document for their internal business
purposes only. Resale of this document or its contents is prohibited.
The information in this document is subject to change without notice. Should you find
any problems or errors, please log a case from the Support link on the Salesforce home
page. Salesforce.com, inc. does not warrant that this document is error-free.

2
Forward Looking Statements

Statement under the Private Securities Litigation Reform Act of 1995:


This presentation contains forward-looking statements about the company’s financial and operating results, which may include expected GAAP and non-GAAP financial and other
operating and non-operating results, including revenue, net income, diluted earnings per share, operating cash flow growth, operating margin improvement, expected revenue growth,
expected current remaining performance obligation growth, expected tax rates, the one-time accounting non-cash charge that was incurred in connection with the Salesforce.org
combination; stock-based compensation expenses, amortization of purchased intangibles, shares outstanding, market growth and sustainability goals. The achievement or success of the
matters covered by such forward-looking statements involves risks, uncertainties and assumptions. If any such risks or uncertainties materialize or if any of the assumptions prove incorrect,
the company’s results could differ materially from the results expressed or implied by the forward-looking statements we make.
The risks and uncertainties referred to above include -- but are not limited to -- risks associated with the effect of general economic and market conditions; the impact of geopolitical
events; the impact of foreign currency exchange rate and interest rate fluctuations on our results; our business strategy and our plan to build our business, including our strategy to be
the leading provider of enterprise cloud computing applications and platforms; the pace of change and innovation in enterprise cloud computing services; the seasonal nature of our
sales cycles; the competitive nature of the market in which we participate; our international expansion strategy; the demands on our personnel and infrastructure resulting from significant
growth in our customer base and operations, including as a result of acquisitions; our service performance and security, including the resources and costs required to avoid unanticipated
downtime and prevent, detect and remediate potential security breaches; the expenses associated with new data centers and third-party infrastructure providers; additional data center
capacity; real estate and office facilities space; our operating results and cash flows; new services and product features, including any efforts to expand our services beyond the CRM
market; our strategy of acquiring or making investments in complementary businesses, joint ventures, services, technologies and intellectual property rights; the performance and fair value
of our investments in complementary businesses through our strategic investment portfolio; our ability to realize the benefits from strategic partnerships, joint ventures and investments;
the impact of future gains or losses from our strategic investment portfolio, including gains or losses from overall market conditions that may affect the publicly traded companies within
the company's strategic investment portfolio; our ability to execute our business plans; our ability to successfully integrate acquired businesses and technologies, including delays related
to the integration of Tableau due to regulatory review by the United Kingdom Competition and Markets Authority; our ability to continue to grow unearned revenue and remaining
performance obligation; our ability to protect our intellectual property rights; our ability to develop our brands; our reliance on third-party hardware, software and platform providers; our
dependency on the development and maintenance of the infrastructure of the Internet; the effect of evolving domestic and foreign government regulations, including those related to the
provision of services on the Internet, those related to accessing the Internet, and those addressing data privacy, cross-border data transfers and import and export controls; the valuation of
our deferred tax assets and the release of related valuation allowances; the potential availability of additional tax assets in the future; the impact of new accounting pronouncements and
tax laws; uncertainties affecting our ability to estimate our tax rate; the impact of expensing stock options and other equity awards; the sufficiency of our capital resources; factors related
to our outstanding debt, revolving credit facility, term loan and loan associated with 50 Fremont; compliance with our debt covenants and lease obligations; current and potential litigation
involving us; and the impact of climate change.
Further information on these and other factors that could affect the company’s financial results is included in the reports on Forms 10-K, 10-Q and 8-K and in other filings it makes with
the Securities and Exchange Commission from time to time. These documents are available on the SEC Filings section of the Investor Information section of the company’s website at
www.salesforce.com/investor.
Salesforce.com, inc. assumes no obligation and does not intend to update these forward-looking statements, except as required by law.

3
Introductions 4

Logistics Courseware and Your Fellow


Agenda Students
• Class etiquette • Agenda for this class • Your name
and participation • Layout of the manual • Goals for your time
• Breaks and exercises in this class
Access Your Student Guide

Download your student guide:


1 https://salesforce.mimeo.digital/

Code: Sent via email before


2
class
Student Guide

VIRTUAL CLASS NOTE:


If you have any questions, you can
chat with your instructor via WebEx
by typing into the box on the lower
left hand corner of your screen.
Get Started with Trailhead

1 Sign-up for Trailhead


https://trailhead.com
If you already have an account,
please log in with that.

Click ‘Sign Up’ in the


2 upper right corner.

Choose your preferred sign up


3 method and follow the prompts.
Program Lightning Web Components 7

LESSON 1:
Course Introduction
LESSON 2:
Lightning Web Components
LESSON 3:
Surface Lightning Web Components
LESSON 4:
Navigation and Layouts
LESSON 5:
Advanced Components
LESSON 6:
Work with Data
LESSON 7:
Source-Tracked Orgs
LESSON 8:
Lightning Web Components for Aura Developers
Lesson 1:
Course Introduction
Lesson 1: Course Introduction 9

• Course Objectives
• Course Prerequisites
• System Requirements
• Class Project
• Review the Application
• Introduction to Salesforce DX and Git
• Exercise 1-1: Meeting the Prerequisites
Course Objectives 10

• Develop Lightning web components for use in many Salesforce Experiences


using the Salesforce Extensions for VS Code
• Communicate between Lightning Web Components using custom events,
public methods, and LMS
• Read and write Salesforce data using wire service and imperative Apex
• Style Lightning web components with CSS and Salesforce Lightning Design
System
• Build advanced components using service components, localization, third-
party JavaScript, and external APIs
• Create forms using standard components and custom controls
• Develop in a source-tracked org and deploy to a non source-tracked orgs
• Compare and contrast Aura Components with Lightning Web Components
Target Audience 11

• Intermediate-level knowledge of JavaScript.


• Familiarity with HTML5 / CSS3 concepts.
• Basic understanding of the Salesforce Platform.
System Requirements 12

• Microsoft Windows 7+ / Mac OS X High Sierra or later


• Google Chrome 53 or later
• Salesforce org login credentials provided by your instructor
• Salesforce CLI
• Java 8 or 11 Platform
• VS Code
• Git
• Node.js LTS Version (optional, for unit testing with Jest)
Course Format 13

Concepts

Walkthroughs

Summaries

Reviews

Best Practices
Welcome 14

1 Market St. Industry Computers & Electronics


San Francisco, CA 94105 Employees 750
United States
Revenue USD 30,000,000
+1.415.901.7901
www.aw-computing.com Ownership Private
* Understand the Certification Application 15

Account Contact

(Service Vendor) (Technician)

Course Delivery Attendee

Certification Element Attempt Held


* Class Project – Gallery View 16

c-student-detail
c-student-browser

c-student-browser-form

c-student-tile c-student-tiles
Class Project – Grid View 17
Class Project – Student Certification View 18
Class Project – Trip Reports View 19
Class Project – Trip Reports Form 20
Salesforce DX 21

Build Together and Deliver Continuously

Source-Centric Development
Greater agility to test out features with confidence

Team Collaboration
Increased dev productivity, faster time to market

Continuous Integration and Delivery


Higher quality code, more automation

Open and Prescriptive


Build with the tools and processes you know and
love; bring together Lightning, Force.com, and
Heroku

SFDX Provides you with an integrated, end-to-end lifecycle


designed for high-performance agile development.
Open and Standards-Based Development Experience 22

Development Options
Visual Studio Code, IntelliJ,
Welkin, Cloud9, and more

Version Control Options


Git, CVS, Perforce, and more

Build, Test, Deploy Options


Jenkins, Travis CI, Bamboo,
JUnit, Selenium, Jest, Heroku Pipelines,
and more
In this class, we will use Visual Studio
Code and Git – both freely available!
What is a Scratch Org? 23

Scratch Org

1 Quick to create and dispose of for


development and continuous integration

Fully configurable to emulate different


2 editions, features, and preferences

Temporary; can last between 1 and 30


3 days; deleted when expire
What is a Dev Hub Org? 24

Dev Hub Org

1 Org with “Dev Hub” feature enabled

Can be enabled in production, trial, and


2 developer edition orgs

Used to create and manage


3 scratch orgs and packages
Enable Dev Hub in Your Org 25

Where you can


enable Dev Hub:

• Production
• Dev Hub trial
• Developer Edition
Functionalities of the Salesforce CLI 26

Create scratch orgs for development and CI


Sync source between org and file system
Import data for development and testing
Execute test suites and report code coverage
Control lifecycle of packages and artifacts

Key Benefits
 Single interface for all Salesforce DX features
 Improved developer productivity
 Integration with VCS, CI, and other toolsets
Salesforce CLI Commands and Parameters 27

Salesforce CLI Command Reference

-h, --help Parameter


Add help parameter to list commands, parameters, and descriptions

sfdx --help // lists all top-level topics


sfdx force --help // lists all the topics under force
sfdx force:org --help // lists all the commands in the topic force:org
sfdx force:org:open --help // detailed info about the force:org:open command

Command Line Autocomplete (third-party shell extensions) Mac Only


https://github.com/wadewegner/salesforce-cli-bash-completion
https://github.com/wadewegner/salesforce-cli-zsh-completion
Overview of Visual Studio Code 28

IDE for the exercises


Free to download
Overview of Visual Studio Code: Editor 29

Selecting a file in the


Explorer side bar opens
it for editing in the
Editor to the right.

Selecting another file


will open a second
editor tab.

Click the X on the editor


tab title to close it.
Overview of Visual Studio Code: Terminal 30

Terminal panel is used


for running commands
at the command line.

If not shown then


click View menu then
choose Integrated
Terminal.

In the exercises, the


Terminal is used to
interact with Git and
Salesforce CLI.
Overview of Visual Studio Code: Command Palette 31

Command Palette is
used to execute
commands.

In the exercises, the


Command Palette is
used to execute
commands provided by
the Salesforce plugins.
Overview of Visual Studio Code: Explorer 32

Explorer side bar lists


the files and folders of
the opened working
directory.

If not shown then


click View menu then
choose Explorer.
Overview of Visual Studio Code: Search 33

Search panel lets you


quickly search and
replace text in all files in
the workspace.
Overview of Visual Studio Code: Source Control 34

Source Control panel


lets you view changes to
the working directory
and commit changes.

Click the … button for


more actions.

Alternative to using Git


in the Terminal panel.
Overview of Visual Studio Code: Extensions 35

Extensions panel lets


you search for and
install third-party
plugins for the editor.

Salesforce Extensions
plugin bundle installed.
Overview of Visual Studio Code: GitLens 36

• GitLens allows you


to visualize code
authorship, navigate
and explore
repositories, and
perform code
comparison, all
through the built-in
Source Control UI.
• Use it to compare
two branches.
• See Appendix A
“Catch up with the
Git Repository” in
your Exercise Guide.
Version Control System (VCS) 37

Version Control System (VCS)

Records changes to files over time so


1 that you can recall specific versions later

Reverts files to previous state (backups)


2 and serves as an audit log of changes

Popular ones are Git, CVS, Mercurial,


3 Team Foundation Server (TFS), Perforce
What is Git? 38

GIT

A version control system that makes it easy to


1 track changes to files individually and as a team.

When editing a shared file, Git can help


2 determine exactly what changed, who
changed it, and why.
* Git Uses a Three-Tree Architecture 39

Clone to make Push to save


Remote Repository
local copy changes

Local Commit to add your new


Repository changes to the master repo.

Branch 1 Branch 2 Master


Checkout to
create or
switch to a Staging
Add some or all changed
different
files to send to Staging.
version
(branch). Working Directory
* Work with Salesforce DX and Git 40

We will not do this part in class

Deploy Production

Local
Remote Repository Sandbox
Repository

Staging

GitHub, Working Directory Scratch Org


Bitbucket Push

Pull
Development Environment 41

SFDX
c:\
Connect Create
Dev Hub Org Scratch Org
Push/Pull

Author
Code

Supplied Training Org Temporary Org


Trial Enterprise Edition Developer Edition
All development in here
Store
Versions
Code/Metadata
Repository
CLI Runtime Configuration Variables 42

You can set CLI runtime configuration values for your current project
(local variables) or for all projects (global variables).

Configuration Value Name Description

apiVersion Metadata API version for a specific project or all projects.

defaultusername Username for a scratch org that all commands run against by default.

defaultdevhubusername Username for a Dev Hub org that the force:org:create command uses.

instanceUrl URL of the Salesforce instance that is hosting your org.

TIPS:
Setting defaultusername in each Salesforce DX project simplifies CLI commands by not having
to always specify the scratch org’s alias or username in the command.
Global vs Local Values 43

Global Values
• Apply to all projects on your computer.

sfdx config:set name=<value> --global

Local Values
• Apply to a specific project
• Override global settings when commands are run from within a Salesforce
DX project directory.

sfdx config:set name=<value>


Metadata Package Directories 44

sfdx-project.json
1{ Indicates the targeted directories when
2 "packageDirectories": [ syncing to and from the source-tracked
3 { org.
4 "path": "force-app/Base",
5 "default": false
All directories are used
6 },
for PUSHING source.
7 {
8 "path": "force-app/Exercises",
Set the default
9 "default": true
property to true to
10 }
specify the directory
11 ],
for PULLING source.
12 "namespace": "",
13 "sfdcLoginUrl": "https://login.salesforce.com",
14 "sourceApiVersion": "52.0"
15 }
Base Metadata 45

The Base directory


includes starter metadata.
You will not change the
contents of this directory Apex Classes
during class.

Lightning web components

A permission set
Standard Object Customizations
and Custom Object Definitions
Exercise Metadata 46

The Exercises directory


will hold the code
that you write during
class.
Aura components

Metadata for the


project (including both
the Base and Exercises Lightning web
directories) is stored in components
source format.
Source Format 47

Source format breaks


down large source files to
make them easier to
manage with a version
control system.

Some parts of
objects are Object fields in
extracted into individual files
subdirectories
* Branch Structure for Class Exercises 48

Student

Start SLN_07
Solutions

Student2
Exercise 1-1: Meet the Prerequisites 49

Goal:
Install and configure the required software for the course.

Tasks
1. Download LWC_Exercises.zip 6. Register Dev Hub
2. Configure your User Account 7. Logout from Dev Hub
3. Enable Dev Hub 8. Setup Scratch Org
4. Import Org project into VSCode 9. Commit your changes
5. Create new Git Branch
100 minutes
Lesson Summary 50

• Salesforce DX is a set of tools and features that allows


you to develop new functionalities with minimal risk,
work collaboratively and deliver continuously on the
Lightning Platform.
• The Salesforce CLI allows you to build automation,
simplify the development process and improve
productivity when working with Salesforce orgs.
• Git is a distributed version control system which unlike
the typical VCS uses a three tree architecture: Working
Directory, Staging and Local Repository.
Lesson 2:
Lightning Web Components
Lesson 2: Lightning Web Components 52

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components
• Style Components
• Define Component Properties
• Handle DOM Events
• Debug Lightning Web Components
• Work with Salesforce Data
• Use Base Lightning Components
• Raise and Handle Events
• Advanced Communication between Components
Evolution of the Web Stack 53

2014 2019
Lightning Component Framework and Lightning Web Components
Aura programming model launched launched

Web Standards
Renaissance
What are Lightning Web Components? 54

• A new programming model for


building Lightning components that
leverages the web standards
breakthroughs of the last five years

• Provides a layer of specialized


Salesforce services on top of the
core stack

• Can coexist and interoperate with


the Aura model

• Unparalleled performance
Aura and Lightning Web Components Interoperability 55

Aura and LWC:

• Can coexist and interoperate


• Share the same high level services

• Can coexist on the same page


• Share the same Base Lightning components
• Share the same underlying services
• Aura can include LWC but LWC cannot
include Aura
Aura Components vs. Lightning Web Components 56
Aura Components vs. Lightning Web Components - 57
Markup
1 <template> LWC
2 <lightning-card title="LWC_Hello" icon-name="action:announcement">
3 <lightning-button label="Count" onclick={count} slot="actions"></lightning-button>
4 <div class="slds-var-m-around_medium">
5 <p>Hello, {greeting}!</p>
6 <lightning-input label="Name" value={greeting} onchange={handleChange}>
7 </lightning-input>
8 </div>
9 </lightning-card>
</template>

1 <aura:component implements="flexipage:availableForAllPageTypes" access="global"> Aura


2 <aura:attribute name="greeting" type="String" default="world" />
3 <lightning:card title="Aura_Hello" iconName="action:announcement">
4 <aura:set attribute="actions">
5 <lightning:button label="Count" onclick="{!c.count}" />
6 </aura:set>
7 <div class="slds-var-m-around_medium">
8 <p>Hello, {!v.greeting}!</p>
9 <lightning:input label="Name" value="{!v.greeting}" />
10 </div>
11 </lightning:card>
12 </aura:component>
Aura Components vs. Lightning Web Components - 58
Controllers
1 import { LightningElement } from 'lwc'; LWC
2 export default class HelloBinding extends LightningElement {
3 greeting = 'World';
4 handleChange(event) {
5 this.greeting = event.target.value;
6 }
7 count() {
8 alert(this.greeting.length + " letters");
9 }
10 }

1 ({ Aura
2 count: function(component, event, helper) {
3 let greeting = component.get("v.greeting");
4 alert(greeting.length + " letters");
5 }
6 })
webcomponents.dev 59

2
3
1

RESOURCE:
https://webcomponents.dev/
Lightning Mini Playground 60

The component definition


pages allow you to view
multiple examples, edit their
code in real time, and preview
the rendered output.
Learn More About Lightning Web Components 61

Explore the sample applications that provides a collection of easy-to-digest


code samples for Lightning Web Components:

https://trailhead.salesforce.com/sample-gallery
Review OOTB Lightning Web Components 62

Includes sample markup


and code, too!

RESOURCE:
https://<mySalesforceInstance>.lightning.force.com/docs/component-library
Exercise 2-1: Review the Available Components 63

Review the out-of-the-box Lightning Web Components.

Goals:
 Review OOTB Lightning Web Components.

10 minutes
Lesson 2: Lightning Web Components 64

• Get to Know the Lightning Web Components Model

• Create Lightning Web Components


• Style Components
• Define Component Properties
• Handle Events
• Debug Lightning Web Components
• Work with Salesforce Data
• Use Base Lightning Components
• Raise and Handle Events
• Advanced Communication between Components
Define a Lightning Web Component in VS Code 65

• Open Exercises | main | default


• Right-click on lwc
• Select:

SFDX: Create Lightning Web Component

• Enter a component name


Naming Rules 66

myComponent • Must begin with a lowercase letter


• Must contain only alphanumeric or
underscore characters
myComponent.html • Can’t contain a hyphen (dash)
• Must be unique in the namespace
• No other Aura or LWC can have
myComponent.js this name
• Can’t include whitespace
myComponent.css • Can’t end with an underscore
• Can’t contain two consecutive
myComponent.js-meta.xml underscores
• Folder and files names must have
same “prefix name”.
Which Files to Include? 67

myComponent UI Component Service Component


• Required • Required
• HTML • JavaScript
myComponent.html • JavaScript • Metadata
• Metadata
myComponent.js • Optional
• CSS
myComponent.css • SVG Icon

CSS Module
myComponent.js-meta.xml
• Required
• CSS
• Metadata
Client and Server Architecture 68

Lightning Web Components


Template (html) UI

Client Controller (js)


Logic + Properties

Logic
Server
Apex Classes

Data
Standard and
Custom Objects
Basic UI Component - JavaScript File 69

Custom Wrapper Core module for


of the standard Lightning Web
HTML element Components

myComponent.js
1 import { LightningElement } from 'lwc';
2 export default class MyComponent extends LightningElement {}

Class name in
PascalCase
Basic UI Component - HTML File 70

myComponent.js
1 import { LightningElement } from 'lwc';
2 export default class MyComponent extends LightningElement {}

parentComponent.html
1 <template>
2 <!–- HTML code -->
3 <c-my-component></c-my-component>
4 </template>

Use kebab-case to reference a


component in the markup

NOTE:
The template element is used to declare fragments of HTML that can be rendered at runtime.
Basic UI Component - CSS File 71

.boxLarge { • Needs to be created in the


width: 200px; component bundle
height: 200px; • Has the same name as the
border: 1px solid red; component
background-color: #344A5F; • Uses standard CSS syntax
} • Unlike Aura, no need for .THIS

.boxSmall {
width: 100px;
height: 100px;
margin: auto;
background-color: #F0F1F2;
}
PascalCase, camelCase or kebab-case ? 72

Case Name camelCase PascalCase kebab-case


Sample studentBrowserForm StudentBrowserForm student-browser-form
Where? • Folder Class name in JavaScript • Component reference
• File names in markup
• Property names in • HTML attribute names
JavaScript

parentComponent.html
1 <template>
2 <c-my-component one-attribute="1"></ c-my-component>
3 </template>

myComponent.js
1 import { LightningElement, api } from 'lwc';
2 export default class MyComponent extends LightningElement {
3 @api oneAttribute;
4 }
Use Lightning App Builder 73
Configuration File 74

myComponent.js-meta.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
3 <apiVersion>52.0</apiVersion>
4 <isExposed>true</isExposed>
5 <targets>
6 <target>lightning__AppPage</target>
7 </targets>
8 </LightningComponentBundle>

Targets specifies which Set to true to make


types of Lightning page the component
the component can be available in Lightning
added to. App Builder.

NOTE:
This is the basic metadata for a component that will be used on the Lightning App Builder.
Generate a Password to Allow Login 75
via the Salesforce App

$ sfdx force:user:password:generate
Successfully set the password ‘_3mafV3cqb’ for user ‘[email protected]

 By default, your user does not have a


defined password for the scratch org.
Generate one using
sfdx force:user:password:generate

 In the Salesforce App, select Sandbox


for the connection.

 On the web, log in at


test.salesforce.com
* Exercise 2-2: Create a Lightning Web Component 76

AwInstructors App Page (Main Region & Right Sidebar) added to Certification App

student-browser-form
new custom component
student-browser
new custom component
lightning-tabset
lightning-tab Chatter Feed
standard standard component
components
Exercise 2-2: Create a Lightning Web Component 77

During this exercise, you will create a Lightning web component


and Lightning Page that can be run from Salesforce mobile.

Tasks:
1. Define a Component that is 4. Deploy the Student Browser
Surfaced in Lightning App Builder Component as a Custom
2. Define the Student Browser Tab in Lightning Experience
Form Component
3. Invoke the Student Browser 25 minutes
Form Component
Lesson 2: Lightning Web Components 78

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components

• Style Components
• Define Component Properties
• Handle DOM Events
• Debug Lightning Web Components
• Work with Salesforce Data
• Use Base Lightning Components
• Raise and Handle Events
• Advanced Communication between Components
Style Lightning Web Components 79

Lightning Design System


Automatically available to Lightning web components that are running in the
following contexts:
• The Salesforce Mobile App
• Lightning Experience
• Experience Cloud

CSS
• Create a style sheet in the component folder
• The style sheet name must be the same as the name of the component
• Automatically applied
• Scoped to the current component ONLY (Shadow DOM)
CSS Basics in Lightning Web Components 80

studentBrowserForm.html studentBrowserForm.css

1 <lightning-card title="Filter Students" 1 .crazycomic {


2 icon-name="utility:search"> 2 font-family: Comic Sans MS;
3 <div class="slds-var-p-horizontal_small"> 3 text-shadow: 2px 2px
4 <h1 class="crazycomic"> 4 #c0c0c0;
5 Filter form 5 }
6 </h1>
7 </div>
8 </lightning-card> Custom style
defined in CSS file

Considerations
 LWC CSS does not require the .THIS keyword
 The .className selector is supported.
 The #ID selector is not supported because id values may be transformed into globally
unique values when the template is rendered.
CSS Scope 81

parent.html child.html
1 <template> 1 <template>
2 <h1>Parent h1</h1> 2 <h1>Child h1</h1>
3 <c-child></c-child> 3 </template>
4 </template>

parent.css Result in Browser


1 h1 {
2 font-size: xx-large;

Parent h1
3 }

Child h1

NOTE:
Due to the Shadow DOM (to be discussed later), CSS styles defined in a parent component
don’t reach into a child.
CSS :host Selector 82

child.html parent.html
1 <template> 1 <template>
2 <div>Hello from Child</div> 2 <div>Hello from Parent</div>
3 </template> 3 <c-child></c-child>
4 </template>

child.css Result in Browser


1 :host {
2 border:2px solid purple;
3 } Hello from parent
The :host selector Hello from child
applies styles to the
entire c-child element.

NOTE:
A component’s style sheet can reach up and style its own element. In this example, child.css is
used to style c-child, but instead of using a c-child selector, we use the :host selector.
Share CSS Rules between Components 83

1 Create a component that only contains a CSS file and configuration.


branding.css

1 h1 {
2 color: #FF0000;
3 }

2 Import the shared CSS file from another component's CSS file.
studentBrowserForm.css

1 @import 'c/branding' //shared CSS available


2 .crazycomic {
3 font-family: Comic Sans MS;
4 text-shadow: 2px 2px #c0c0c0;
5 }
Style Components with Custom Aura Design Tokens 84

Tokens allow you to ensure consistent design using CSS variables.


studentBrowserForm.html

1 Create a default token – must be named defaultTokens.token.


1 <aura:tokens> defaultTokens.token
2 <aura:token name="brandColor" value="#FF0000"/>
3 </aura:tokens>

2 Use the token in a CSS variable with --c- as the prefix in the name.
1 .crazycomic { studentBrowserForm.css
2 font-family: Comic Sans MS;
3 text-shadow: 2px 2px #c0c0c0;
4 color: var(--c-brandColor);
5 }
Style Components with Custom Styling Hooks 85

Style hooks allow you to customize the look and feel of base
components. studentBrowserForm.html

Consider this lightning-button:


1 <lightning-button variant="brand" styleHook.html
2 label="Normal Button"
3 class="my-css">
4 </lightning-button>
Salesforce Lightning Design System (SLDS) 86

URL PATH:
http://www.lightningdesignsystem.com
* Apply SLDS Classes 87

BEM (Block-Element-Modifier):
• Block represents a high-level component (e.g. slds-button)
• Element represents a descendent of a component (e.g. slds-button__icon)
• Modifier represents a different state of a block or element (e.g. slds-button_neutral)

.my-house { block or component name }


.my-house__door { element or component part }
.my-house_red { modifier or component variation }
.my-house__door_white { variation of a component part }
Apply SLDS Classes 88

1 <template>
2 <lightning-button-group>
3 <lightning-button label="Refresh"></lightning-button>
4 <lightning-button label="Edit"></lightning-button>
5 <lightning-button label="Save"></lightning-button>
6 </lightning-button-group>
7 </template>

1 <template>
2 <div style="width:300px; height:300px;
3 border:1px solid black; margin:5px;"
4 class="slds-align_absolute-center"> Centered Content
5 Centered Content
6 </div>
7 </template>
Use SLDS Icons 89

Refer to icons as
categoryname:iconname

1 <lightning-card icon-name="action:add_contact" title="Add Contact Information">


2 <div class="slds-var-p-horizontal_medium">
3 // content goes here
4 </div>
5 </lightning-card>
SLDS Component Blueprints 90

Variations

Output

Blueprints

Markup and SLDS

NOTE: Blueprints contain markup for different variations or states of the same component.
Change Background in Lightning Experience 91
Background Color and Image 92

There are two ways to adjust the background color and image.

Method 1: CSS class Method 2: Wrap component with lightning-card


1 .myBackground { 1 <lightning-card icon-name="standard:event"
2 background-color: #fff; 2 title="Days on the Market">
3 } 3 <div class="slds-var-p-horizontal_medium">
4 4 // content goes here
5 <template> 5 </div>
6 <div class="myBackground"> 6 </lightning-card>
7 ...
8 </div>
9 </template>
* Exercise 2-3: Theme a Lightning Web Component
lightning-card standard component

student-browser-form student-browser
custom component custom component
Exercise 2-3: Theme a Lightning Web Component 94

During this exercise, you will theme the components that you
developed in the previous exercise.

Tasks:
1. Review the capabilities of 4. Use SLDS to apply padding within
lightning-card a component
2. Use lightning-card to add contrast 5. Apply custom CSS
to the search form
3. Use SLDS to add margin to
a component 30 minutes
Lesson 2: Lightning Web Components 95

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components
• Style Components

• Define Component Properties


• Handle DOM Events
• Debug Lightning Web Components
• Work with Salesforce Data
• Use Base Lightning Components
• Raise and Handle Events
• Advanced Communication between Components
What is a Property? 96

Property

A property is a variable that is declared as a


1 member of the component's JavaScript class.

2 Properties can be private, public, or global.

All properties are reactive. A component’s template


3 rerenders when one of its properties has a value change.
Arrays and objects have special considerations.
Decorators 97

@api Use to expose a public reactive property

@track Observe object properties or array elements

@wire Use to read Salesforce data or invoke Apex


Access Controls for Properties 98

Private Public Global

 Only available within the  Decorate with @api  Decorate with @api
component.  Set <isExposed> false on  Set <isExposed> true
 Either no decorator or component. on component.
@track.

Maintain internal Passed from a parent Can be injected from an


component state component App Builder page
• recordId /
sObjectName
• Design properties

NOTE:
Setting <isExposed>true</isExposed> also makes the component global
and eligible for App Builder and other targets.
Reactivity in Private Properties with no Decorator 99

1 <template> hello.html
2 <div>{greeting}, {name.first}!</div>
3 <lightning-button label="Change Message" onclick={handleClick}>
4 </lightning-button>
5 </template>

1 import { LightningElement} from 'lwc'; hello.js


2 export default class HelloBinding extends LightningElement {
3 greeting = "Hello";
4 name = { first: "John", last: "Doe" };
5 handleClick() {
6 this.greeting = "Goodbye"; // primitive property change: rerender!
7 this.name.first = "Jane"; // nested object property change: no rerender.
8 }
9 }

NOTE:
JavaScript primitive types include string, integer, and boolean.
Use the @track Decorator on Objects and Arrays 100

@track Observe object properties or array elements

1 import { LightningElement, track } from 'lwc';


2 export default class HelloWebComponent extends LightningElement {
3
4 @track student = { first : 'John', last : 'Doe' };
5 @track classes = ['DEX450', 'CCD102'];
6
7 function updateProps {
8 // reassignments trigger rerender with or without @track
9 this.student = {first: 'Jane', last: 'Doe'};
10 this.classes = ['DEX601', 'DEX602'];
11
12 // the next two lines only trigger rerender because of @track
13 this.student.first = 'Jane';
14 this.classes.push('DEX470’);
15 } You may see @track applied to primitives for
16 } legacy reasons. This is harmless but unnecessary.
* Public Properties 101

The decorators (api, track, wire) must be


explicitly imported from the lwc module.

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


2 export default class StudentTile extends LightningElement {
3
4 @api student;
5 }

Annotate a property with @api to mark it as public.

NOTE:
A public property can be set in Lightning App Builder, or by a parent component that
uses the component in its markup.
Instructor Demo: Reactivity in Arrays 102

Your instructor will now use the propsDemo


component on the Certification App Home Page to
demonstrate the difference between an array
decorated with @track and one that isn't.

Review the following files in the lwc folder of the


Base metadata:

• propsDemo/propsDemo.html
• propsDemo/propsDemo.js
Getters 103

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


2 export default class MyComponent extends LightningElement {
3
4 student;
5 @api isSelected; A getter is a function that
6
computes a value for a property.
7 get tileSelected() {
8 return this.isSelected ? "tile selected" : "tile";
9 }
10 }

1 <template> myComponent.html
2 <div class={tileSelected} >
3 ...
4 </div>
5 </template>
Setters 104

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


2 export default class MyComponent extends LightningElement {
3 _studentName;
4 Getters and setters can be
5 @api decorated with @api – best
6 get studentName() {
practice is to annotate the getter.
7 return this._studentName;
8 }
9
10 set studentName(value) {
11 this._studentName = value.toUpperCase();
12 }
13 }

A setter is a function that executes


logic each time a property is set.
What is Data Binding?

Data Binding

The process of keeping the data and user


1
interface synchronized in one or both
directions.
Data UI

In Lightning Web Components, the data


2
binding for property values is one-way.
Input Field with No Data Binding 106

hello.html
1 <template>
2 <lightning-input label="Name" name="name"></lightning-input>
3 </template>

hello.js
1 import { LightningElement} from 'lwc';
2 export default class HelloBinding extends LightningElement {
3 greeting = 'World';
4 }
Input Field with One-Way Data Binding 107

Set the value of the lightning-input to the greeting property


to implement a one-way binding.

1 <template> hello.html
2 <lightning-input label="Name" value={greeting}>
3 </lightning-input>
4 </template>

1 import { LightningElement} from 'lwc'; hello.js


2 export default class HelloBinding extends LightningElement {
3 greeting = 'World';
4 }
Input Field with Two-Way Data Binding 108

Add an onchange handler and update the property to bind data both ways.

hello.html
1 <template>
2 <lightning-input label="Name" value={greeting} onchange={handleChange}>
3 </lightning-input>
4 </template>

hello.js
1 import { LightningElement} from 'lwc';
2 export default class HelloBinding extends LightningElement {
3 greeting = 'World';
4 handleChange(event) {
5 this.greeting = event.target.value;
6 }
7}
Define Properties and Functions 109

Decorator Sample Reactive Public Applicable to


Property/ Function?
@api @api prop1; YES YES Both
@track @track prop2; YES NO Property
@wire @wire() prop3; Maybe* NO Both
get get funct1() {…} Maybe* NO Function
set set funct2() {…} NO NO Function
No Decorator prop4; YES* NO Both

• Each time a reactive property is updated, all getter functions are re-
evaluated (even when that reactive property is not used in the getter).
• A wired service is reactive, if it uses a reactive variable (prefixed with $).
• Object and arrays have special reactivity rules when not using @track
Work with Boolean Values in Components 110

… child.js
@api isSelected = false;

Omitting an attribute is equivalent to Including a boolean attribute without


passing its default value. In this a value is the same as passing true.
example:
<c-child></c-child> <c-child is-selected></c-child>

is equivalent to is equivalent to

<c-child is-selected=false></c-child> <c-child is-selected=true></c-child>


Exercise 2-4: Work with Component Properties 111

lightning-tab standard component

student-tile student-browser
new custom custom component
component
Exercise 2-4: Work with Component Properties 112

During this exercise, you will start building the student gallery.

Tasks:
1. Define the studentTile component.
2. Define component properties.
3. Generate dynamic markup.

20 minutes
Lesson 2: Lightning Web Components 113

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components
• Style Components
• Define Component Properties

• Handle DOM Events


• Debug Lightning Web Components
• Work with Salesforce Data
• Use Base Lightning Components
• Raise and Handling Events
• Advanced Communication between Components
Handle DOM Events 114

studentTile.html
1 <template>
2 <button class={tileSelected} onclick={onStudentClick}>
3 …
4 </button>
5 </template>

studentTile.js

1 onStudentClick(){
2 alert(this.student.Name);
3 }

• Lightning Web Components support standard DOM events.


• Custom events can also be created and dispatched.
HTML Template Directives 115

A directive is a special attribute that adds dynamic behavior to an HTML template.

for:each={array} Iterates over an array and render a list.

for:item="currentItem" Accesses the current item.


for:index="index" Accesses the current item's zero-based index.

Conditionally renders DOM elements in a


if:true|false={expression} template.

iterator:iteratorName={array} Applies special behavior to the first or last item in


an array and renders a list.
Improves rendering performance by assigning a
key={uniqueId}
unique identifier to each item in a list.
* Iterate over an Array 116

Add the for:each directive to


a nested <template> tag that Use for:item to
encloses the HTML elements access the current
you want to repeat. element.
studentTiles.html
1 <template>
2 <template for:each={studentList} for:item="oneStdt">
3 <c-student-tile student={oneStdt} key={oneStdt.Id}></c-student-tile>
4 </template>
5 </template>

A key must be specified studentTiles.js


to assign a unique Id to
1 studentList = [ each element.
2 { Id: 1, Name: 'Andres' },
3 { Id: 2, Name: 'Rad' },
4 ...
5 ];
Iterate over an Array (Cont.) 117

for:each
1 <template>
2 <template for:each={studentList} for:item="student" for:index="idx">
3 <div key={student.Id}>
4 {idx}. {student.Name}
5 </div>
6 </template>
7 </template>

1 <template> iterator
2 <template iterator:it={studentList}>
3 <div key={it.value.Id}>
4 <div if:true={it.first} class="list-first"></div>
5 {it.index}. {it.value.Name}<br/>
6 <div if:true={it.last} class="list-last"></div>
7 </div>
8 </template>
9 </template>
What is a Lifecycle Hook? 118

Lifecycle Hook

1 A callback method triggered at a specific


phase of a component instance’s lifecycle.

Most LWC lifecycle hooks are built into the HTML


2
Custom element specification but LWC adds two:
renderedCallback() and errorCallback().
Define a Constructor 119

• The constructor() is a function that is executed when the


component is instantiated.
• super() is required after the constructor definition.

1 constructor() { studentBrowser.js
2 super();
3 const studentNames = ['Rad', 'Stuart', 'Andres', 'Rahul', 'Amit', 'Simon'];
4 this.studentList = studentNames.map( (studentName, index) => {
5 return {
6 'sobjectType': 'Contact',
7 'Name': studentName,
8 'PhotoUrl': '/services/images/photo/003B0FakePictId',
9 'Id': index
10 };
11 });
12 }
connectedCallback() function 120

The connectedCallback() function is fired when a component is


inserted into the DOM. It flows from parent to child.

studentDetail.js
1 connectedCallback() {
2 if(this.subscription){
3 return;
4 }
5 this.subscription = subscribe(this.messageContext,
6 SELECTED_STUDENT_CHANNEL, message => {
7 this.handleStudentChange(message);
8 });
9 }

NOTE: The connectedCallback() hook can fire more than once. For example, it will fire again
if you remove an element and then insert it into another position (example: reordering a list).
* constructor() vs connectedCallback() – Host Element 121

You can add attributes to the host element during any stage of the
component lifecycle other than construction.

1 import { LightningElement } from 'lwc';


2 export default class LifeCycleDemo extends LightningElement {
3 constructor() { Illegal, because it adds an
4 super(); attribute to the host element
5 this.classList.add('new-class'); in the constructor().
6 }
7 connectedCallback() { Legal, because it adds an
8 this.classList.add('new-class'); attribute to the host element
9 } in the connectedcallback().
10 }
* constructor() vs connectedCallback() – Property Access 122

You must wait until connectedCallback() to access @api properties.

1 import { LightningElement } from 'lwc';


2 export default class LifeCycleDemo extends LightningElement {
3
4 @api recordId;
5 constructor() { Illegal, because it attempts
6 super(); to access a public property
during construction.
7 console.log(this.recordId);
8 }
9
10 connectedCallback() { Legal, because public
11 console.log(this.recordId); properties are available
12 } during connectedCallback().
13 }
Lifecycle Hooks 123

Parent Component Child Component


Parent created

constructor() constructor()
called on parent called on child
Properties Properties
Assigned Assigned
connectedCallback() connectedCallback()
called on parent called on child

render() render()
called on parent called on child

renderedCallback() renderedCallback()
called on parent called on child
Lifecycle Hooks when a Component is 124
Removed from the DOM

Parent removed
from the DOM

disconnectedCallback()
called on parent

Child Removed
from DOM

disconnectedCallback()
called on child
Common Lifecycle Hook Use Cases 125

connectedCallback() – Establish communication with the current


document or container and coordinate behavior with the environment.
Perform initialization tasks, such as fetch data, set up caches, or listen for
events (such as publish-subscribe events).

disconnectedCallback() – Unsubscribe from publish-subscribe


mechanisms.

render() – Conditionally render templates.

renderedCallback() – Initialize third-party JavaScript.

errorCallback() – Create an error boundary component that captures errors


in all the descendent components in its tree.
Organize your Controller 126

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


2 import getStudents from '@salesforce/apex/StudentBrowser.getStudents';
3
4 export default class MyOrderDemo extends LightningElement {
5
6 @api prop1;
7 @track prop2;
8 @wire(getStudents) students;
9
10 constructor() {
11 super();
12 //constructor code;
13 }
14 connectedCallback() {
15 //connectedCallback has access to properties
16 }
17
18 get myProperty() {}
19 set myProperty(value) {}
20
21 myFunction() {}
22 }
* Exercise 2-5: Handle DOM Events 127

onclick

student-tile student-tiles
custom component new custom component student-browser
custom component
Exercise 2-5: Handle DOM Events 128

During this exercise, you will implement a click handler and use
sample data to test out the gallery function.

Tasks:
1. Handle an initialization event
2. Modify the contents of a tracked property
3. Iterate through a list of JavaScript objects
4. Handle a User Event
5. Access a property in the handler
30 minutes
EXTRA CREDIT Reinforce your Skills with a Challenge 129

Comfortable with
Components Challenge 1: Output the Date and Time

Scenario
Create a component challenge_currentDateTime that
displays the current date and time when the page
loads on a lightning-card.

Add a lightning-button-icon that updates the


date/time on click. Then, make the date/time update
• Working with App Builder
automatically every second. Display the component
• Creating JavaScript
on the custom Home Page for the Certification App.
Controllers

40 min
Lesson 2: Lightning Web Components 130

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components
• Style Components
• Define Component Properties
• Handle DOM Events

• Debug Lightning Web Components


• Work with Salesforce Data
• Use Base Lightning Components
• Raise and Handle Events
• Advanced Communication between Components
Lightning Component Framework Modes 131

Production Mode Debug Mode

Default Yes No

Optimized for Performance Code readability

Code Minified Yes No

Code Optimized Yes No


Additional warnings and
Messages Minimal
errors
Who benefits? Users Developers
How to Enable Debug Mode – Option 1 132

CLICK PATH:
Your Profile | Settings | Advanced User Details
How to Enable Debug Mode – Option 2 133
How to Enable Debug Mode – Option 3 134

1 //Enable debug mode for the default scratch org user


2 User defaultUser = [SELECT Id FROM User WHERE Name = 'User User'];
3 defaultUser.UserPreferencesUserDebugModePref = true;
4 update defaultUser;

This code exists in EXFiles/data/CreateUsers.txt, which runs in an anonymous


Apex block at the end of the scratch org creation script.
What is “Minified JavaScript”? 135
What is “Minified JavaScript”? (Prettified) 136

Pretty Print
Debug Mode Enabled 137
Custom Formatters 138

CLICK PATH:
Chrome DevTools | Settings | Enable Custom Formatters
Programmatically Setting Breakpoints 139

You can set programmatic breakpoints by inserting the


following command into your JavaScript:

debugger;

Chrome DevTools must be open.


Step Through Your Code 140
Throttle Network Speed when Testing Your Application 141

Click 'Online' in the


Network tab to reveal
throttling options
Instructor Demo: Using Git for Checkpoints in VS Code 142

Please commit your changes along the way when


told to do so in the manual!

Your instructor will now demo how committing your


changes impacts the Source Control tab in VS Code,
and how you can use commits to make your
debugging easier.
Use GitLens to Compare Two Branches 143

Compare any two branches

• View all commits

• View all file changes

• Click on a file to see the


cumulative changes

Compare your code to the solution for an


exercise by committing all of your changes
and comparing your current branch to the
solution branch.
* Write Jest Tests 144

Import the createElement method


1 import { createElement } from 'lwc';
import Hello from 'c/hello'; and the component to be tested
2
3 Add a describe block
4 describe('c-hello', () => {
5 afterEach(() => { Add an afterEach() method to
6 while (document.body.firstChild) { reset the DOM at the end of the test
7 document.body.removeChild(document.body.firstChild);
8 }
9 });
Add an it block to describe
10
11 it('displays greeting', () => { a single test
12 const element = createElement('c-hello', {
13 is: Hello Create the component under test
14 });
15 document.body.appendChild(element); Add the component to the DOM
16 const div = element.shadowRoot.querySelector('div');
expect(div.textContent).toBe('Hello, World!'); Add an assertion using the expect
17
18 }); statement
19 });
Instructor Demo: Test Lightning Web Components 145

Watch your instructor demonstrate how to test your


components using Jest using both single test runs
and watch mode.

Review with your instructor the following files:

• package.json
• studentTile.test.js

Review the Appendix if you wish to run these tests


yourself.
Exercise 2-6: Debug your Code 146

Experiment with Debugging and Logging

Tasks:
1. Enable Debug Mode.
2. Use console.log() and breakpoints.
3. Discard unneeded changes.
4. Compare branches using GitLens.

20 Minutes
Lesson 2: Lightning Web Components 147

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components
• Style Components
• Define Component Properties
• Handle DOM Events
• Debug Lightning Web Components

• Work with Salesforce Data


• Use Base Lightning Components
• Raise and Handling Events
• Advanced Communication between Components
Ways to Work with Salesforce Data 148

Most Complex Intermediate Difficulty Simple and Easy

User Interface API


Apex Base Components
Wire Adapters and Functions

• Custom logic in Server- • Custom logic in client-side • Custom logic not


Side Controllers (JavaScript) controllers required

• You implement the • Automatically adheres to • Automatically adheres to


security model yourself. security model security model

• Call your custom Apex • Many useful functions • Three components


methods o createRecord o lightning-record-form
o getRecord o lightning-record-edit-form
o updateRecord o lightning-record-view-form
o deleteRecord
… and more!
By Default, Apex Classes and Triggers… 149

… ignore Object CRED. … ignore FLS.

Course Delivery
Course Location Start Date … More fields
Number
Delivery_00000 [101] AWCA Server Tokyo, JP 2/15/2016 …
Delivery_00001 [101] AWCA Server San Francisco, CA 6/7/2016 …
Delivery_00002 [101] AWCA Server Paris, FR 3/22/2016 …

… can be programmed to respect or ignore the running user's record-level


access during data operations (SOQL, DML, dot notation traversal).
Enforce Sharing Rules 150

Respects the Sharing


1A public with sharing class RespectsSharing {} Model for the
running user.

Ignores the Sharing


1B public without sharing class IgnoresSharing {} Model for the
running user.

Uses the Sharing Model


1C public inherited sharing class UsesCallerSharing {}
of the calling class.

NOTE:
An Apex class with implicit (no sharing rules enforcement defined) or inherited sharing runs
as with sharing when used as a Lightning component controller.
Simple Object-Level and Field-Level Security 151

1 public with sharing class WithSecurityDemo { WithSecurityDemo.cls


2 @AuraEnabled(cacheable=true)
3 public static List<Contact> getContacts() {
4 return [
5 SELECT Name, Account.Name, SSN__c
6 FROM Contact Enable field-level and
7 WITH SECURITY_ENFORCED object level security
8 ORDER BY Name permissions checking.
9 ];
10 }
11 }

• If any fields or objects referenced are inaccessible to the user, an


exception is thrown, and no data is returned.
• Only available in Apex.
Sanitize Results and Prepare for DML Operations 152
using Security.stripInacessible()
1 public void AccessDemo() {
2
3 List<Contact> myContacts = [SELECT LastName, SSN__c FROM Contact];
4 Strip fields from
5 SObjectAccessDecision decision = SOQL results that
6 Security.stripInaccessible(AccessType.READABLE, myContacts);
fail FLS checks
7
8 for (Integer i = 0; i < myContacts.size(); i++) {
9 System.debug('Insecure access: '+ myContacts[i]); //includes SSN__c
10 System.debug('Secure access: ' + decision.getRecords()[i]); //doesn't include SSN__c
11 }
12
13 Contact c1 = new Contact(LastName='Smith', SSN__c='111-11-1111');
14 Contact c2 = new Contact(LastName='Williams');
15 List<Contact> newContacts = new List<Contact>{c1,c2};
16 SObjectAccessDecision securityDecision = Security.stripInaccessible(
Prepare for
17 AccessType.CREATABLE, DML operations
18 newContacts);
19 insert securityDecision.getRecords(); //inserted without SSN__c. No exceptions thrown!
20
21 System.debug(securityDecision.getRemovedFields().get('Contact')); // Prints "SSN__c"
22 }
Detailed Object and Field-Level Security using 153
Apex Describe Information
1 public ID createContact( String firstName, String lastName, String phone, ID accountId ) {
2
3 Set<SObjectField> fieldsToCheck = new Set<SObjectField>{
4 Contact.FirstName, Contact.LastName, Contact.Phone, Contact.AccountId
5 };
6 Respects the Object Access
7 NoAccessException ex = new NoAccessException(); for the running user.
8
9 if ( !Contact.sobjectType.getDescribe().isCreateable() ) {
10 ex.setMessage('User cannot create Contacts');
11 throw ex;
12 } Respects the Field Access
13
14 for ( SObjectField field : fieldsToCheck ) {
for the running user.
15 if ( !field.getDescribe().isCreateable() ) {
16 ex.setMessage('User cannot create Contact.' + field);
17 throw ex;
18 }
19 }
20
21 ... User has access, ok to insert new contact ...
22 }
Techniques for Implementing CRED 154
and Field-Level Security

WITH Apex Describe


stripInaccessible() User Interface API
SECURITY_ENFORCED Information

Apply a clause to a Pass a list of records Automatically


Implement behavior
SOQL query and throw and strip fields that apply CRED and
Purpose based on runtime
an exception if data is fail FLS checks for FLS without writing
schema examination.
inaccessible. the current user. Apex or SOQL.

LWC Controller /
Where SOQL query in Apex Apex Apex
Aura Markup*

Complexity Low Medium High Low

NOTE:
* The UI API can also be called from native mobile apps and custom web apps!
Expose an Apex Method to a Lightning Web Component 155

The method must be annotated


with @AuraEnabled

1 public with sharing class StudentBrowser { StudentBrowser.cls


2 @AuraEnabled(cacheable=true)
3 public static List<User> getInstructors(Integer maxRecords) {
4 return [SELECT Id FROM User LIMIT : maxRecords];
5 }
6 }
The method
The method must be must be static
global or public
Enable Client-Side Caching 156

1 public with sharing class StudentBrowser { StudentBrowser.cls


2 @AuraEnabled(cacheable=true)
3 public static List<User> getInstructors(Integer maxRecords) {
4 return [SELECT Id FROM User LIMIT : maxRecords];
5 }
6 }

• Adding (cacheable=true) to the @AuraEnabled annotation improves


performance by caching the method results on the client.
• Can be used only for methods that get data, it cannot mutate any data!
• DML operations are prevented.
• Must be set in order to call an Apex method via the @wire service.
What is Wire Service? 157

Wire Service

1 Read data using Apex classes or


uiRecordApi.

2 Decorate a property or a function.

3 Only for read – no DML operations.


* Wire an Apex Method to a JavaScript Property 158

1 public with sharing class StudentBrowser { StudentBrowser.cls


2 @AuraEnabled(cacheable=true)
3 public static List<Contact> getStudents(String instructorId, String courseDeliveryId){
4 // method body omitted.
5 }
6}

1 import { LightningElement, wire } from 'lwc'; studentBrowser.js


2 import getStudents from '@salesforce/apex/StudentBrowser.getStudents';
3
4 export default class StudentBrowser extends LightningElement {
5 @wire(getStudents, { instructorId: "", courseDeliveryId: ""})
6 students;
7}
8

Data goes to students.data These hardcoded empty strings will


Errors go to students.error be replaced with variables later
* Reference a Wired Property in Markup 159

1 <template> studentBrowser.html
2 <c-student-browser-form></c-student-browser-form>
3 <lightning-tabset variant="scoped">
4 <lightning-tab label="Gallery">
5 <c-student-tiles student-list={students.data}></c-student-tiles>
6 </lightning-tab>
7 </lightning-tabset>
8 </template>

1 <template> studentTiles.html
2 <template for:each={studentList} for:item="student">
3 <c-student-tile student={student} key={student.Id}></c-student-tile>
4 </template>
5 </template>
Must be an Array or an iterable Object. If
undefined, a console error will occur.
* Options for Wire Service 160

1 @wire(getStudents, { instructorId: "", courseDeliveryId: ""})


2 students;

QUESTION:
In the above example, what happens if we want to run some logic, such as
transforming the data, when the data returns?

Or, what if we want to perform specialized error handling?

Let’s wire a
FUNCTION!
* Wire an Apex Method to a JavaScript Function 161

1 public with sharing class StudentBrowser { StudentBrowser.cls


2 @AuraEnabled(cacheable=true)
3 public static List<User> getInstructors(Integer recordCount) {
4 return [SELECT Id FROM User LIMIT : recordCount];
5 }
6 }

1 import { LightningElement, wire } from 'lwc'; wireApex.js


2 import getInstructors from '@salesforce/apex/StudentBrowser.getInstructors';
3 export default class WireApex extends LightningElement {
4 instructors; errors; maxRecords = 3;
5 @wire(getInstructors, { recordCount: '$maxRecords' })
6 wired_findInstructors({ errors, data }) {
7 if (data) {
8
9
this.instructors = data; this.errors = undefined;
} else if (errors) {
Reactive variable
10 this.instructors = undefined; this.errors = errors;
11 }
12 }
13 }
When does Wire Service Run? 162

Default value for wired properties and functions


 A wired property, or a wired function's result object, is assigned a
default value after component construction and before any other
lifecycle event. The default value is an object with data and error
properties of undefined.
– Once data is available, it goes in data while error will be undefined.
– If an error occurs, it goes in error while data will be undefined.

Wired function execution


 The function is invoked whenever a value is available, which can be
before or after the component is connected or rendered.

NOTE:
If a reactive variable (prefixed with $) changes, wire service provisions new
data. A network request may not occur if the data is cached on the client.
* Exercise 2-7A: Working with Apex 163

Apex:
getStudents

@wire students

student-browser
custom component

@api studentList

student-tiles
custom component

@api student

student-tile
custom component
Exercise 2-7A: Work with Apex 164

Call an Apex method from a Lightning web component and


render the results.

Tasks:
1. Create an Apex Class and method 3. Output a set of Contacts as tiles.
that can be accessed by a
Lightning web component. 4. Explore Apex Logs.
2. Request data from the Salesforce
Platform.
25 minutes
Lightning Data Service 165

Lightning Web Components


Client

Lightning Data Service

User Interface API Apex Class


Server

Database
User Interface API or Apex? 166

User Interface API Apex


SOQL / SOSL / Record
Read data Record Id
Id*
Write data YES YES

Single Record YES YES*

Multiple Records NO YES

Secured data CRED + FLS + Record Access Record Access only

NOTE:
Apex allows you to work with a single record, but why bother!
lightning/uiRecordApi 167

getRecord()
 Retrieves a record’s data, used uiRecordApi has plenty of
in wire statements other methods – and we'll
use some of them later!

getFieldValue()
 Returns field data in its raw form

getFieldDisplayValue()
 Returns field data formatted and localized, based on locale settings

NOTE:
Raw field values can also be retrieved using the syntax
record.data.fields.fieldname.value
* Wire a Function for Lightning Data Service (JavaScript) 168

1 import { LightningElement, api, wire } from 'lwc'; wireSample.js


2 import { getRecord } from 'lightning/uiRecordApi';
3 import CONTACT_EMAIL_FIELD from '@salesforce/schema/Contact.Email';
4
5 export default class WireSample extends LightningElement {
6 @api recordId; record; error;
7
8 @wire(getRecord, { recordId: "$recordId", fields: [CONTACT_EMAIL_FIELD] })
9 wiredContact({ error, data }) {
10 if (data) {
11 this.record = data; this.error = undefined;
12 } else if (error) {
13 this.record = undefined; this.error = error;
14 }
15 }
16 get email() {
17 let output = "Not loaded yet...";
18 if (this.record && this.record.fields) {
19 output = this.record.fields.Email.value;
20 }
21 return output; Could @wire a property too.
22 }
23 }
Markup to Display Data Retrieved via Wire Service 169

wireSample.html
1 <template>
2 <lightning-card title="Wire Sample" icon-name="standard:contact">
3 <template if:true={recordId}>
4 <div class="slds-var-m-around_medium">
5 <p>{email}</p>
6 </div>
7 </template>
8 </lightning-card>
9 </template>
Wired Function Signatures 170

Option one – an object


1 @wire(getRecord, { recordId: "$recordId",
2 fields: [CONTACT_EMAIL_FIELD] })
3 wiredContact(result) {
4 //function definition goes here
5 //access result.data or result.error
6 }

Option two – 2 variables


1 @wire(getRecord, { recordId: "$recordId",
2 fields: [CONTACT_EMAIL_FIELD] })
3 wiredContact({ error, data }) {
4 //function definition goes here
5 //access error and data directly
6 }
Components used to Handle Client-Side Errors 171

ldsUtils
 Service component with reduceErrors() method.
 Can handle different error object structures. Already in Base
metadata
errorPanel
 Reusable component to display error messages.

NOTE:
Both of these components can be found in the lwc-recipes repo.
* Exercise 2-7B: Work with Lightning Data Service 172

student-detail
custom component
with valid hardcoded
Contact ID

student-detail
custom component
with invalid hardcoded
Contact ID

error-panel custom component

student-detail
custom component
with bad field name
Exercise 2-7B: Work with Lightning Data Service 173

Call the getRecord wire adapter to load a student's details.

Tasks:
1. Build the studentDetail
component.
2. Call getRecord() to fetch
information about a Contact.
3. Explore error handling.
20 minutes
Wire Service Summary 174

What can you decorate with @wire?


Task Code sample
Decorate a property @wire(adapterOrApex) students;
Decorate a method @wire(adapterOrApex)
(run logic whenever new data is wired_getStudents({error,data}) {
provided or an error occurs) console.log(data);
}

Whether you're decorating a property or a method, you can use the following.
Task Code sample
Use getRecord wire adapter @wire(getRecord) propertyOrMethod;
Use an Apex method (no DML) @wire(getInstructors) propertyOrMethod;

NOTE:
The @wire decorator is also used for PageReference which will be discussed later.
Lesson 2: Lightning Web Components 175

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components
• Style Components
• Define Component Properties
• Handle DOM Events
• Debug Lightning Web Components
• Work with Salesforce Data

• Use Base Lightning Components


• Raise and Handle Events
• Advanced Communication between Components
What is <lightning-combobox>? 176
* Use the <lightning-combobox> 177

1 <template> app.html
2 <lightning-combobox label="Country" value={value} options={options}
3 onchange={handleChange}></lightning-combobox><br/>
4 Selected Country: {value}
5 </template>

1 import { LightningElement} from 'lwc'; app.js


2 export default class ComboboxBasic extends LightningElement {
3 value = 'uk';
4
5 get options() {
6 return [
7 { label: 'UK', value: 'uk' },
8 { label: 'France', value: 'france' },
9 { label: 'Canada', value: 'canada' },
10 ];
11 }
12 handleChange(event) {
13 this.value = event.target.value;
14 }
15 }
Handle <lightning-combobox> Events 178

• onfocus
• onblur
• onchange
app.html app.js
1 <lightning-combobox 1 value;
2 label="Country" 2
3 value={value} 3 handleChange(event) {
4 placeholder="Select Country" 4 this.value = event.detail.value;
5 options={options} 5 }
6 onchange={handleChange}>
7 </lightning-combobox>

NOTE:
Unlike Aura components or Visualforce pages, the properties are not bi-directional bound!
Implement an Icon Button 179

1 <template>
2 <lightning-button variant="brand" label="Download"
3 icon-name="utility:download" icon-position="left"
4 onclick={handleClick}></lightning-button>
5 </template>

1 handleClick(event) {
2 alert("Button was clicked!");
3 }
Theme <lightning-button> with Variant Options 180

Button Variants:
• base
• neutral (default)
1 <lightning-button variant="base"
2 label="Base" onclick={handleClick}> • brand
3 </lightning-button> • destructive
4
5 <lightning-button variant="brand" • inverse
6 label="Neutral" • success
7 onclick={handleClick}>
8 </lightning-button>
9
10 <lightning-button variant="destructive"
11 label="Neutral"
12 onclick={handleClick}>
13 </lightning-button>
Use the <lightning-layout> Grid System 181
Use the <lightning-layout> Grid System 182

1 <lightning-layout horizontal-align="space">
2 <lightning-layout-item flexibility="grow">
3 This is Column 1
4 </lightning-layout-item>
5 <lightning-layout-item flexibility="grow">
6 This is Column 2
7 </lightning-layout-item>
8 </lightning-layout>
Specify Columns with Relative Sizing 183

1 <template>
2 <lightning-layout class="slds-wrap">
3 <!-- row 1 -->
4 <lightning-layout-item class="slds-align-top" size="12">
5 Header
6 </lightning-layout-item>
7
8 <!-- Row 2 -->
9 <lightning-layout-item size="3" >Nav</lightning-layout-item>
10 <lightning-layout-item size="6" >Body</lightning-layout-item>
11 <lightning-layout-item size="3" >Aside</lightning-layout-item>
12
13 <!-- Row 3 -->
14 <lightning-layout-item size="12">Bottom Align</lightning-layout-item>
15 </lightning-layout>
16 </template>
Demo: Responsive Column Sizes 184

1 <template>
2 <lightning-layout multiple-rows>
3 <lightning-layout-item size="12">
4 Header</lightning-layout-item>
5 <lightning-layout-item size="6" small-device-size="6" medium-device-size="4">
6 Nav</lightning-layout-item>
7 <lightning-layout-item size="6" small-device-size="6" medium-device-size="4">
8 Body</lightning-layout-item>
9 <lightning-layout-item size="12" small-device-size="12" medium-device-size="4">
10 Aside</lightning-layout-item>
11 <lightning-layout-item size="12">
12 Footer</lightning-layout-item>
13 </lightning-layout>
14 </template>

You cannot have device specific size attributes for component without
specifying the `size` attribute, and the size attribute must come before
the device specific sizes.
JavaScript Template Literals 185

• Template literals are string literals allowing embedded expressions.


• They are enclosed by back-ticks (``).

1 data.forEach(delivery => {
2 this.deliveries.push({
3 value: delivery.Id,
4 label: `${delivery.Start_Date__c} ${delivery.Location__c}
${delivery.Attendee_Count__c} students`
5 });
6 });
expression
* Exercise 2-8: Work with Base Lightning Components 186

lightning-combobox standard component

Apex:
getInstructors
getDeliveriesByInstructor
@wire @wire
instructors deliveries
student-browser-form
custom component

onchange

student-browser
custom component

student-tiles
custom component

lightning-layout
lightning-layout-item
standard
components
Exercise 2-8: Work with Base Lightning Components 187

Define the Filter Students form as a set of dynamically


populated select boxes.

Tasks:
1. Use lightning-layout to define a two- 3. Create the Student Browser Form's
column, horizontal layout. instructor selector.
2. Create an Apex Class and methods 4. Create the Course Delivery selector
that can be accessed by a Lightning which is dynamically populated with
web component in order to read data for the selected instructor.
data from Salesforce Platform.
25 minutes
Lesson 2: Lightning Web Components 188

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components
• Style Components
• Define Component Properties
• Handle DOM Events
• Debug Lightning Web Components
• Work with Salesforce Data
• Use Base Lightning Components

• Raise and Handle Events


• Advanced Communication between Components
Communication Between Components: Child to Parent 189

App Page

c-student-browser c-student-detail
c-student-browser-form
Custom event

c-student-tiles
c-student-tile

NOTE:
By default, custom events only propagate one level up. They do not bubble higher in the
component hierarchy.
* How Custom Events Work? (Child to Parent) 190

1 <c-lwc-child onsampleevent={handleSampleEvent}></c-lwc-child> parent.html

1 handleSampleEvent(event) { parent.js
2 alert(JSON.stringify(event.detail)); Must be named
3 } detail
Must start with on
lwcChild.html
1 <lightning-button label="Click Me" onclick={sendEvent}></lightning-button>

1 sendEvent() { lwcChild.js
2 const selectedEvent = new CustomEvent('sampleevent', {
3 detail: { author: "Rad", instructor: "EL Toro" }
4 });
5 Every letter must be
6
7 }
this.dispatchEvent(selectedEvent);
lowercase!

NOTE:
To preserve component encapsulation, send primitives or a copy of your object.
Declare an Event Listener Programmatically 191

eventListener.js
1 constructor() {
2 super();
3 this.template.addEventListener('sample', this.handleSample);
4 }
5
6 handleSample = (event) => {
7 // event handler logic here
8 };

An alternative to declaring an event handler in markup is to declare it


programmatically, though it’s normally better to leave it in your HTML
as it reduces the amount of code you need to write.
Communication Between Components: Parent to Child 192

App Page

c-student-browser c-student-detail

c-student-browser-form

c-student-tiles
c-student-tile

• Option 1: Assign child properties via HTML attributes.


• Option 2: Call child’s public methods.
Communication Between Components: Parent to Child
* Assigning Child Properties via HTML Attributes 193

1 import { LightningElement, api } from 'lwc'; studentTiles.js


2 export default class StudentTiles extends LightningElement {
3 @api studentList;
4 }

1 <template for:each={studentList} for:item="student"> studentTiles.html


2 <c-student-tile student={student} key={student.Id}></c-student-tile>
3 </template>

1 import { LightningElement, api } from 'lwc'; studentTile.js


2 export default class StudentTile extends LightningElement {
3 @api student;
4}
Communication between Components: Parent to Child
* Calling Child’s Public Methods 194

studentBrowser.html
1 <lightning-button variant="natural" label="Unselect Student"
2 onclick={handleUnselectStudent}></lightning-button>
3 <c-student-tiles student-list={students.data}
4 onstudentselected={handleNotify}></c-student-tiles>

1 handleUnselectStudent(){ studentBrowser.js
2 this.template.querySelector('c-student-tiles').unselectStudent();
3 }
A standard DOM API function that
returns the first element that
matches the component name.

Public methods are decorated with @api


and they are part of a component’s API.

1 @api unselectStudent(){ studentTiles.js


2 this.selectedStudentId = "";
3 }
* Add Functionality without Multiple Inheritance 195

export default class MyCustomElement extends LightningElement {}

• Every Lightning web component with a user interface extends the


LightningElement class.

• JavaScript does not allow you to extend more than one class at a time.

• How can we add methods from a second class to MyCustomElement if


we are already extending LightningElement?

Apply a MIXIN to LightingElement


before extending it!
What is a Mixin? 196

Mixin

A mixin is a class that lets us add functionality


1 to another class without using multiple NavigationMixin
inheritance.
LightningElement
Since JavaScript doesn’t support multiple
2
inheritance, mixins are useful when a class
already extends a parent class.
MyLWC
NavigationMixin Methods 197

1 export default class MyCustomElement


2 extends NavigationMixin(LightningElement) {}

NavigationMixin has been applied to LightningElement

Applying NavigationMixin adds two methods to LightningElement, which


is then extended by MyCustomElement:

• Navigate() - navigate to another page in the application.


• GenerateUrl() - get a promise that resolves to the resulting URL.
Navigation Service Implementation 198

Lightning navigation service allows you to navigate in Lightning


Experience, Experience Cloud and the Salesforce app. studentBrowserForm.html

Import lightning/
1 1 import { NavigationMixin } from 'lightning/navigation'; navigation

Apply the NavigationMixin


2 1 export default class MyCustomElement
2 extends NavigationMixin(LightningElement) {}
function to your component’s base
class.

1 onAddNewDelivery() { Call the navigation service’s


3 2 this[NavigationMixin.Navigate]({ [NavigationMixin.Navigate]
3 type: 'standard__objectPage', function to dispatch the navigation
4 attributes: { request and open the New Course
5 objectApiName: 'Course_Delivery__c', Delivery modal dialog.
6 actionName: 'new'
7 }
8 });
9}
Pass Default Values to Navigation Service 199

1 Import encodeDefaultFieldValues.
import { encodeDefaultFieldValues } from 'lightning/pageReferenceUtils';

2 Include a state object with a parameter defaultFieldValues


1 const pageInfo = {
2 type: "standard__objectPage",
3 attributes: { Wrap the values object in a
4 objectApiName: "Course_Delivery__c", call to encodeDefaultValues
5 actionName: "new"
6 },
7 state: {
8 defaultFieldValues: encodeDefaultFieldValues({
9 Instructor__c: this.selectedInstructorId
10 })
11 }
12 };
13 this[NavigationMixin.Navigate](pageInfo);
* Exercise 2-9: Work with Events lightning-button standard component
200

Enabled when instructor selected

student-browser-form
navigate
custom component

filterchange custom event

student-browser
custom component

@wire students

Apex:
getStudents
Exercise 2-9: Work with Events 201

Define, raise and handle a component event, triggering an Apex


request and filtering of the gallery output.

Tasks:
1. Define two custom component 4. Use data passed by a component
events event in an Apex transaction
2. Raise both custom events
3. Handle both custom events

45 minutes
Lesson 2: Lightning Web Components 202

• Get to Know the Lightning Web Components Model


• Create Lightning Web Components
• Style Components
• Define Component Properties
• Handle DOM Events
• Debug Lightning Web Components
• Work with Salesforce Data
• Use Base Lightning Components
• Raise and Handle Events

• Advanced Communication between Components


Communication between Components: 203
Bubbles and Composed

App Page

c-student-browser c-student-detail

c-student-browser-form

c-student-tiles
c-student-tile
Shadow DOM 204

Document Tree

Document The DOM tree


Shadow Tree inside the
shadow DOM.
Shadow Shadow
Host Root The root node
of the shadow
tree.

A DOM node that


the shadow DOM The place where
is attached to. the shadow
Shadow Boundary DOM ends.
Shadow DOM (Cont.) 205

1 <template> studentBrowser.html 1 <c-student-tiles> studentTiles.html


2 <c-student-tiles> 2 #shadow-root
3 <div> 3 <div>
4 <p>Gallery</p> 4 <p>Gallery</p>
5 </div> 5 </div>
6 <c-student-tile></c-student-tile> 6 <c-student-tile>
7 </c-student-tiles> 7 #shadow-root
8 </template> 8 <div>
9 <h1>{student.Name}</h1>
1 <template> studentTile.html
10 </div>
2 <div> 11 </c-student-tile>
3 <h1>{student.Name}</h1> 12 </c-student-tiles>
4 </div>
5 </template>

LWC uses a Shadow DOM polyfill and the #shadow-root is not


visible in Chrome developer tools.
Shadow DOM (Cont.) 206

c-student-tiles
1 <c-student-tiles> studentTiles.html
2 #shadow-root
3 <div>
c-student-tile
4 <p>Gallery</p>
5 </div>
6 <c-student-tile>
div 7 #shadow-root
8 <div>
9 <h1>{student.Name}</h1>
h1 10 </div>
11 </c-student-tile>
12 </c-student-tiles>
Shadow Boundary

NOTE:
The shadow tree encapsulate the elements in each Lightning web component and it affects how
we work with CSS, events and the DOM.
Define Event Propagation 207

c-student-tiles
bubbles (Boolean)
Indicates whether the event
c-student-tile can pass through the DOM
or not.

div
composed (Boolean)
h1
Indicates whether the event
can pass through the
Shadow Boundary
Shadow DOM or not.
Configure Bubbles and Composed 208

studentTile.js
1 const selectedEvent = new CustomEvent('studentselected', {
2 bubbles: true,
3 composed: true,
4 detail: { studentId: this.student.Id }
5 });
Event Retargeting 209

c-student-tiles
For bubbling events that cross
the shadow boundary, the value
c-student-tile
of Event.target changes to
match the scope of the listener.

div The listener can’t see into the


Event.target is
c-student-tile shadow DOM of the component
h1 that dispatched the event.

Shadow Boundary
Event retargeting and scoped
CSS preserve shadow DOM
Event.target is h1
encapsulation
What is the LMS? 210

LIGHTNING MESSAGE SERVICE

An API to publish and subscribe to messages throughout


1 Lightning Experience.

First service to allow communication between Visualforce,


2 Aura Components, and Lightning Web Components.
Clearing up Publish/Subscribe 211

PUBLISH/SUBSCRIBE

Pub Sub pattern - Software concept of publish/subscribe,


1 with multiple applications in SFDC.

LMS - the proper way to implement a Publish Subscribe


2 pattern in all experiences where it's supported.
c/pubsub - a component students may come across in
3 legacy code that can be used as a fallback for
communication in unsupported experiences.
Component Communication : Different DOM Branch 212

App Page

c-student-browser c-student-detail
c-student-browser-form
LMS
c-student-tiles
c-student-tile

• Use Publish-Subscribe pattern via Lightning Message Service for


components that are not in same branch of the DOM tree.
• Frequent architecture for Lightning App Pages.
Message Channel File 213

SelectedStudentChannel.messageChannel-meta.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
3 <apiVersion>52.0</apiVersion> Set to true to expose
4 <masterLabel>SelectedStudentChannel</masterLabel> to components in
5 <isExposed>true</isExposed> other namespaces.
6 <description>Lightning Message Channel for Selected Student.</description>
7 <lightningMessageFields>
8 <fieldName>studentId</fieldName>
9 <description>This is the record Id for the selected student.</description>
10 <fieldName>studentName</fieldName>
11 <description>The full name of the student.</description>
12 </lightningMessageFields>
lightningMessageFields
13 </LightningMessageChannel> specifies the fields that will
be part of the published
message.
NOTE:
This is the basic layout for a Lightning Message Channel.
Communication between Components through
* Lightning Message Service 214

1 import { publish, MessageContext } studentBrowser.js


2 from 'lightning/messageService';
3 import SELECTED_STUDENT_CHANNEL from
4 '@salesforce/messageChannel/SelectedStudentChannel__c';
5
6 export default class StudentBrowser extends LightningElement {
7 @wire(MessageContext) messageContext;
8
9 handleNotify(event){
10 const message = {
11 studentId: event.detail.studentId
12 }
13 publish(this.messageContext, SELECTED_STUDENT_CHANNEL, message);
14 }…
Communication between Components through
* Lightning Message Service 215

1 import { subscribe, unsubscribe, MessageContext } studentDetail.js


2 from 'lightning/messageService';
3 import SELECTED_STUDENT_CHANNEL
4 from '@salesforce/messageChannel/SelectedStudentChannel__c';
5
6 export default class StudentDetail extends LightningElement {
7 studentId;
8 @wire(MessageContext) messageContext;
9 subscription = null;
10 connectedCallback() {
11 if(this.subscription){
12 return;
13 }
14 this.subscription = subscribe(
15 this.messageContext, SELECTED_STUDENT_CHANNEL,
16 (message) => this.handleStudentChange(message));
17 }
18 disconnectedCallback() {
19 unsubscribe(this.subscription);
20 this.subscription = null;
21 }
22 handleStudentChange(message) {
23 this.studentId = message.studentId;
24 }
* Exercise 2-10: Communicate between Components 216

student-browser LMS student-detail


custom component pass selected student ID custom component

student-tiles
custom component

student-tile custom component


onclick: dispatch studentselected
custom event
MOU8

Exercise 2-10: Communicate between Components 217

Use the publish-subscribe pattern to load a Contact record and


display the value of the record’s Name field in a card.

Tasks:
1. Define and surface a new component in
Lightning App Builder.
2. Leverage LMS to use publish-subscribe
pattern to load a Contact record from the
Salesforce Platform.
20 minutes
Slide 217

MOU8 Replace "pub-sub" to "publish-subscribe" to remove the confusion with the Pub-Sub library we had before LMS
Microsoft Office User, 5/14/2021
Code Review 218

Review with your instructor the following:


• SelectedStudentChannel xml
• studentBrowser component
• studentDetail component
Lesson Summary 219

• The Salesforce Lightning Component Framework is based


on web standards.

• Lightning Web Components are comprised of a multi-file


bundle for implementing output to the browser's DOM
through declarative markup, handling client-side events in
JavaScript, styling components and surfacing via the
Lightning App Builder.

• Reactive properties force your component to rerender


every time their value changes.

• Lightning web components use reactive wire service, built


on Lightning Data Service, to read Salesforce data.
Lesson Review 220

1. List the different files in a Lightning web component and


when you would use each.

2. Describe the syntax for invoking a child component from


a parent component.

3. What is the purpose of the configuration file?

4. What is the way to communicate between components


which don’t belong to the same branch of the DOM tree?

5. When must we use Apex methods to work with Salesforce


data?
Lab Phase 1
Output Data from Lightning Data Service
Lab Phase 1: Output Data from Lightning Data Service 222

Not visible if no
studentTile selected

student-detail
custom component

lightning-button

navigate
lightning-card to standard
lightning-button record page
lightning-formatted-email
lightning-formatted-phone
Standard components
Lab Phase 1: Output Data from Lightning Data Service 223

Modify the Student Detail component to add additional


information, formatted with Lightning web components.

Tasks:
1. Use the lightning-formatted-email component to
output the contact's Email.
2. Use the lightning-formatted-phone component to
output the contact's phone number.
3. Output the Contact description field underneath
the phone number.
60 minutes
Lab Phase 1: Output Data from Lightning Data Service
(Continued)
224

Modify the Student Detail component to add additional


information, formatted with Lightning web components.

Tasks:
1. Add a button to the card that, when
pressed, uses the navigation service to
display the detail record page.
2. Hide the contents of studentDetail if
no student has been selected.

60 minutes
EXTRA CREDIT Reinforce your Skills with a Challenge 225

Delighted
with Data Challenge 2: Recent Worldwide Deliveries

Scenario
Create a component challenge_recentDeliveries that
retrieves the 3 most recent course deliveries into a
wired property deliveries. Use a method named
getRecentDeliveries in an Apex class named
CourseDeliveries.

• Creating Apex Classes


Output the deliveries on a lightning-card with the
• Retrieving Salesforce Data utility:world icon. Display the component in your
• Displaying Dynamic Data Challenge Gallery.
30 min
Lesson 3:
Surface Lightning Web Components
Lesson 3: Surface Lightning Web Components 227

• Build Lightning Pages with Components and App Builder


• Build Components for Lightning Experience Record Pages
• Surface Lightning Web Components
• Override Standard Actions
• Define a Lightning Application
• Use Lightning in Visualforce Pages with Lightning Out
• Other Supported Experiences
Use <lightning-map> 228

1 <lightning-map map-markers={mapMarkers}> deliveryListMap.html


2 </lightning-map>

1 mapMarkers = [{ deliveryListMap.js
2 location: {
3 'City': 'London',
4 'Country': 'UK',
5 },
6 description: 'London, UK, 5 sessions',
7 }]

Considerations
• No API key required.
• Hard limit: 10 Geocoded addresses
• Recommended max: 100 markers
Expose Properties to App Builder 229

deliveryListMap.js-meta.xml

1 <isExposed>true</isExposed>
2 <targets>
3 <target>lightning__AppPage</target>
4 </targets>
5 <targetConfigs>
6 <targetConfig targets="lightning__AppPage">
7 <property name="listView" label="List View"
8 type="String" datasource="visible,hidden" />
9 <property name="markersTitle" type="String"
10 label="List Title (if visible)” />
11 </targetConfig>
12 </targetConfigs>

Add <property> entries to the config file and decorate the properties with
@api to allow them to be configured at design time in Lightning App Builder.
Populate App Builder Picklists Dynamically (1 of 2) 230

1 Create a property with datasource pointing to an Apex class.

… primaryCitySelector.js-meta.xml
1 <targetConfigs>
2 <targetConfig targets="lightning__AppPage">
3 <property name="city" label="Primary City"
4 type="String" datasource="apex://CityPicklist" />
5 </targetConfig>
6 </targetConfigs>

This is a component
property in App Builder,
and the option list is
dynamic!
Populate App Builder Picklists Dynamically (2 of 2) 231

2 Create an Apex class that extends VisualEditor.DynamicPickList


and implements the getDefaultValue and getValues functions.
1 global class CityPicklist extends VisualEditor.DynamicPickList { CityPicklist.cls
2 global override VisualEditor.DataRow getDefaultValue(){
3 VisualEditor.DataRow defaultValue = new VisualEditor.DataRow('Paris', 'Paris');
4 return defaultValue;
5 }
6
7 global override VisualEditor.DynamicPickListRows getValues() {
8 List<AggregateResult> myCities = [SELECT City__c, COUNT(Id) myCount
9 FROM Course_Delivery__c group by City__c ];
10 VisualEditor.DynamicPickListRows myRows = new VisualEditor.DynamicPickListRows();
11 for (AggregateResult ar : myCities) {
12 String city = String.valueOf(ar.get('City__c'));
13 VisualEditor.DataRow myDataRow = new VisualEditor.DataRow(city, city);
14 myRows.addRow(myDataRow);
15 }
16 return myRows; The constructor for DataRow takes
17 }
18 } (label,value) – we are passing city
for each in this example
Define an Apex Class that Returns a List of Locations 232

CourseDeliveryLocations.cls
1 public with sharing class CourseDeliveryLocations {
2
Must be set in order
3 @AuraEnabled(cacheable=true) to use @wire to call
4 public static List<AggregateResult> getLocations() { the Apex method.
5
6 return [SELECT City__c, Country__c, COUNT(Id) numDeliveries
7 FROM Course_Delivery__c group by City__c, Country__c];
8 }
9 }

1 @wire(getLocations) locationComponent.js
2 wired_getLocations({ error, data }) {
3 this.mapMarkers = [];
4 if (data) {
5 data.forEach(loc => {
6 this.mapMarkers.push({location:{City: loc.City__c, Country: loc.Country__c}});
7 });
8 }
9 }
Create an Aura DeliveryListAction Component 233

1 <aura:component DeliveryListAction.cmp
2 implements="flexipage:availableForAllPageTypes,
3 force:lightningQuickAction" access="global">
4
5 <c:deliveryListMap />
6 </aura:component>

Set the name of the name of the interfaces you are implementing.

flexipage:availableForAllPageTypes
Makes your component available to Lightning App.

force:lightningQuickAction
Makes your component available to be used as a custom action in Lightning
Experience.
Create an Aura DeliveryListAction Component (Cont.) 234

1 <aura:component DeliveryListAction.cmp
2 implements="flexipage:availableForAllPageTypes,
3 force:lightningQuickAction" access="global">
4 <c:deliveryListMap />
5 </aura:component>

Use Aura Notation:


Use camel case with a colon separating the namespace and the component name, to
refer to a Lightning web component from an Aura component.
Exercise 3-1: Surface a Component in App Builder 235

Custom App Page


(Main Column and Right Sidebar)

Quick Action

delivery-list-map
Filter List
custom
standard
component
component

Chatter Feed
standard
component
Exercise 3-1: Create a Lightning Component 236
Global Action with Aura

NOTE:
Currently, Global Actions only support Aura Components. To use a Lightning web component in
a global action, you must wrap it in an Aura component.
Exercise 3-1: Surface a Component in App Builder 237

Use Lightning App Builder to Create a Lightning Page with a


Quick Action.

Tasks: the Lightning Page and observe the


1. Create a DeliveryListMap component. effects of modifying its configuration
properties.
2. Make the component available in the
Lightning App Builder. 5. Add a Chatter Feed, Filtered List, and
quick action to the Lightning Page.
3. Define a Lightning Page that is surfaced
in Salesforce mobile and Lightning 6. Activate and test your Lightning Page in
Experience. both Lightning Experience and
Salesforce mobile.
4. Add the DeliveryListMap component to
35 minutes
Code Review 238

Review with your instructor the following


components:
• deliveryListMap (LWC)
• DeliveryListAction (Aura)
Lesson 3: Surface Lightning Web Components 239

• Build Lightning Pages with Components and App Builder

• Build Components for Lightning Experience Record Pages


• Surface Lightning Web Components
• Override Standard Actions
• Define a Lightning Application
• Use Lightning in Visualforce Pages with Lightning Out
• Other Supported Experiences
Add a Map to the Course Delivery Record Page 240
Map – Configuration File 241

deliveryDetailMap.js-meta.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <LightningComponentBundle
3 xmlns="http://soap.sforce.com/2006/04/metadata" fqn="map">
4 <apiVersion>52.0</apiVersion>
5 <isExposed>true</isExposed>
6 <targets>
7 <target>lightning__RecordPage</target>
8 </targets>
9 </LightningComponentBundle>

Add a property named recordId decorated with @api to access the current ID.
Configuring Form Factors 242

deliveryDetailMap.js-meta.xml

1 <isExposed>true</isExposed>
2 <targets>
3 <target>lightning__AppPage</target>
4 </targets>
5 <targetConfigs>
6 <targetConfig targets="lightning__AppPage">
7 <property name="prop1" label="Prop 1" type="String" />
8 <supportedFormFactors>
9 <supportedFormFactor type="Small" />
10 </supportedFormFactors>
11 </targetConfig>
12 </targetConfigs>

Add <supportedFormFactor> entries to the config file to define under which form
factors a component will be surfaced to a user. Small is phone, Large is desktop.
Quick Actions for Lightning Web Components 243

updateDeliveryLocation.js-
meta.xml
1 …
2 <isExposed>true</isExposed>
3 <targets>
4 <target>lightning__RecordAction</target>
5 </targets>
6 <targetConfigs>
7 <targetConfig targets="lightning__RecordAction">
8 <actionType>ScreenAction</actionType>
9 </targetConfig>
10 </targetConfigs>
11 …

A quick action allows us to open a component directly from a record page, or to execute
code silently.
Exercise 3-2: Build Components for Lightning 244
Experience Record Pages
Course Delivery Custom Record Page
Highlights Panel (Header and Right Sidebar)

Record delivery-detail-map
Detail new custom
standard component
component
Exercise 3-2: Build Components for Lightning
Experience Record Pages
245

Build a component that will render a lightning-map centered on


the city and country provided in the course delivery record.

Tasks:
1. Define a new Lightning web 3. Use Lightning App Builder to deploy
component for Lightning the component to a custom
Experience Record Pages. Lightning Record Page.
2. Retrieve Course Delivery record
data using the wire service.
25 minutes
Code Review 246

Review with your instructor the following


components:
• deliveryDetailMap
Lesson 3: Surface Lightning Web Components 247

• Build Lightning Pages with Components and App Builder


• Build Components for Lightning Experience Record Pages

• Surface Lightning Web Components


• Override Standard Actions
• Define a Lightning Application
• Use Lightning in Visualforce Pages with Lightning Out
• Other Supported Experiences
Surface Components in App Builder Pages vs Tabs 248

App Builder Pages


 Layouts built using drag/drop templates.
 Can be built and configured by
business users.
 Activation automatically creates tab and allows easy
addition to apps and mobile nav menu.
 Includes title bar which may include quick actions.

Tabs
 Layout fully controlled by developer.
 Cannot be customized by business users.
Surface Lightning Web Components in Salesforce 249
Mobile and Lightning Experience

1. Add a target of lightning__Tab to the component's


configuration file.
2. Create a New Lightning Component tab.
3. Add the Lightning Component Tab to the Salesforce
Mobile Navigation Menu and Lightning Experience.
LayoutManager – Configuration File 250

layoutManager.js-meta.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <LightningComponentBundle
3 xmlns="http://soap.sforce.com/2006/04/metadata" fqn="map">
4 <apiVersion>52.0</apiVersion>
5 <isExposed>false</isExposed>
6 <targets>
7 <target>lightning__Tab</target>
8 </targets>
9 </LightningComponentBundle>

Add a target of lightning__Tab to make a component available


as a Lightning Tab.
Create a Lightning Component Tab 251

Lightning Component Tabs can be created manually in the Setup interface.

CLICK PATH:
Setup | User Interface | Tabs
Create a Two Column Layout in Code 252

1 <template> layoutManager.html

2 <lightning-layout>
3 <lightning-layout-item padding="around-small" size="8">
4 <c-student-browser></c-student-browser>
5 </lightning-layout-item>
6 <lightning-layout-item padding="around-small" size="4">
7 <c-student-detail></c-student-detail>
8 </lightning-layout-item>
9 </lightning-layout>
10 </template>
Add a Lightning Component Tab 253
to Salesforce Mobile Navigation
Lightning Component Tabs can be manually added to the Mobile Navigation
menu in the Setup interface.

CLICK PATH:
Setup | Apps | Mobile Apps | Salesforce Navigation
Add Lightning Component Tabs to the App 254
Launcher

CLICK PATH:
Setup | Apps | App Manager
Create a Container Component that Controls Layout 255

c-layout-manager

c-student-browser c-student-detail

c-student-browser-form

c-student-tiles

c-student-tile
Exercise 3-3: Surface a Component Lightning Experience 256

layout-manager
new custom component Custom component tab

student-detail
custom
component
student-browser
custom
component
Exercise 3-3: Surface a Component in Lightning
Experience
257

Surface a Lightning Web Component in Lightning Experience.

Tasks:
1. Define a top-level Lightning web 4. Define a Lightning Component tab.
component that defines the layout. 5. Surface a Lightning Component tab
2. Add a target to the metadata file. in a Lightning Experience App.
3. Upload a custom tab icon.

20 minutes
Lesson 3: Surface Lightning Web Components 258

• Build Lightning Pages with Components and App Builder


• Build Components for Lightning Experience Record Pages
• Surface Lightning Web Components

• Override Standard Actions


• Define a Lightning Application
• Use Lightning in Visualforce Pages with Lightning Out
• Other Supported Experiences
Override Standard Actions 259

NOTE:
Currently, Standard Actions Overrides only support Aura Components. To use a Lightning web
component to override Standard Actions, you must wrap it in an Aura component.
Wrap a Lightning Web Component in an Aura 260
Component

Override_View_CourseAttendee.cmp
1 <aura:component implements="lightning:actionOverride,
2 force:hasRecordId,force:hasSObjectName" access="global" > This is a
3 <c:courseAttendee recordId="{!v.recordId}" />
Lightning web
4 </aura:component>
component

Set the name of the name of the interfaces you are implementing.

lightning:actionOverride
Makes your component available for action overrides.

force:hasRecordId, force:hasSObjectName
Inject attribute values for recordId and sObjectName when possible.
Invoke Lightning Web Components from Aura 261

1 <aura:component
2 implements="lightning:actionOverride,force:hasRecordId">
3 <c:courseAttendee recordId="{!v.recordId}" />
4 </aura:component>

Use Aura syntax to pass


the id of the record
currently displayed.

NOTE:
When referencing a child Lightning web component from an Aura component, we use the Aura
syntax and NOT:
<c-course-attendee record-id={recordId}></c-course-attendee>
Override a Standard Action 262

1. Select Setup | Object Manager


2. Click the name of the object for
which you want to override a
standard action.
3. Select Buttons, Links, and Actions
Exercise 3-4: Override Standard Actions 263

Standard Record Page

course-attendee
new custom
component

Override_View_CourseAttendee
new custom Aura component
Exercise 3-4: Override Standard Actions 264

Create a component that overrides the standard View action for


the Course Attendee Custom Object.

Tasks:
1. Create a Custom Tab for the
Course_Attendee Custom Object
2. Define the Override Component
3. Register the Override
4. Test the Override
20 minutes
Lesson 3: Surface Lightning Web Components 265

• Build Lightning Pages with Components and App Builder


• Build Components for Lightning Experience Record Pages
• Surface Lightning Web Components
• Override Standard Actions

• Define a Lightning Application


• Use Lightning in Visualforce Pages with Lightning Out
• Other Supported Experiences
Create a Lightning Application in VS Code 266

• Open:
Exercises | main | default
• Right-click on aura
• Select:
SFDX: Create Aura App
• Enter a component name
Define and Browse to a Lightning App 267

1 <aura:application extends="force:slds">
2 <c:deliveryListMap />
3 </aura:application>

Make SLDS classes available to your app

Apps have their own URL located at


/c/YourAppName.app
Browse directly to your app from the CLI:
sfdx force:org:open --path "/c/YourAppName.app"
Create App Templates 268

1 <aura:component
2 isTemplate="true"
3 extends="aura:template"> Title of browser tab
4
5 <aura:set attribute="title" value="My App"/>
6 <aura:set attribute="auraResetStyle" value=""/>
7 ...
8 </aura:component> Prevent a reset.css
from interfering with SLDS

1 <aura:application template="c:mytemplate">
2 ...
3 </aura:application>
Set Application Attributes via the Query String 269

1 <aura:application extends="force:slds">
2
3 <aura:attribute name="title" type="String" default="Title"/>
4 <aura:attribute name="message" type="String" default="Message"/>
5
6 <div class="panel panel-default">
7 <div class="panel-heading">{!v.title}</div>
8 <div class="panel-body">
9 {!v.message}
10 </div>
11 </div>
12
13 </aura:application>

URL PATH:
https://mydomain.lightning.force.com/c/
HelloWorld.app?title=Hello&message=World
Exercise 3-5: Create a Lightning App 270

AwApp.app Custom Lightning Application URL delivery-list-map


custom Aura Lightning custom component
Application
Exercise 3-5: Create a Lightning App 271

Define a Lightning Application.


Set attribute values via the URL query string.

Tasks
1. Define a Lightning Application
2. Invoke an Aura Component
3. Execute a Lightning Application from a Url.
4. Modify query string parameters and view the
impact.
15 minutes
Code Review 272

Review with your instructor the following


components:
• AwApp.app
Lesson 3: Surface Lightning Web Components 273

• Build Lightning Pages with Components and App Builder


• Build Components for Lightning Experience Record Pages
• Surface Lightning Web Components
• Override Standard Actions
• Define a Lightning Application

• Use Lightning in Visualforce Pages with Lightning Out


• Other Supported Experiences
Use Lightning in Visualforce Pages with Lightning Out 274

Lightning Out enables you to


directly embed Lightning web
components in different
domains.

Components run directly in


your DOM:
• CORS issues automatically
resolved.
• No need to use <iframe>.

NOTE:
Lightning Components for Visualforce pages is the only way to surface Lightning web
components inside of Salesforce Classic.
Make your Components Available for Invocation from 275
Visualforce
Makes app available
for Lightning Out

1 <aura:application
2 extends="ltng:outApp">
3
4 <aura:dependency resource="c:deliveryListMap" />
5 <aura:dependency resource="lightning:badge" />
6
7 </aura:application>
Lists other components that will be needed by the
Lightning Out application so all dependencies can be
loaded once, at startup time, for efficiency.

NOTE:
A Lightning dependency app isn’t a normal Lightning app. It does not support templates,
and any markup in them won’t be rendered. Only use <aura:dependency> tags in the body.
Instantiate Lightning Web Components on a 276
Visualforce Page
1 <apex:page>
2
3 <!-- <script src="/lightning/lightning.out.js" /> -->
4 <apex:includeLightning/>
5
6 <!-- target div for placing component -->
7 <div id="mapDiv" />
8
9 <script>
10 // load the components into memory
11 $Lightning.use("c:vfDependency", function() {
12 // invoke the components and place on page
13 $Lightning.createComponent(
14 "c:deliveryListMap", // name of component to create
15 {listView:"hidden"}, // optional component props
16 "mapDiv" // HTML ID where to put component
17 );
18 });
19 </script>
20 </apex:page>
Exercise 3-6: Surface Components in 277
Visualforce Pages
DeliveryListVF
Visualforce Page

delivery-list-map
custom component

lightning-badge
standard component
Exercise 3-6: Surface Components in
Visualforce Pages
278

Surface the deliveryListMap in Visualforce.

Tasks
1. Define an Application to load
components into memory.
2. Instantiate Lightning web
components on a Visualforce
page.
15 minutes
Lesson 3: Surface Lightning Web Components 279

• Build Lightning Pages with Components and App Builder


• Build Components for Lightning Experience Record Pages
• Surface Lightning Web Components
• Override Standard Actions
• Define a Lightning Application
• Use Lightning in Visualforce Pages with Lightning Out

• Other Supported Experiences


Components in Experience Builder 280

Properties can
be exposed at
design time

Experience Builder and


Lightning App Builder have
different sets of standard
components, but can share
custom components.
Components in Experience Builder – Config file 281

1 <?xml version="1.0" encoding="UTF-8"?>


2 <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
3 <apiVersion>52.0</apiVersion> Enables the component to
4 <isExposed>true</isExposed> be used on a page in
5 <masterLabel>Best Component Ever</masterLabel> Experience Builder.
6 <description>This is a demo component.</description>
7 <targets>
8 <target>lightningCommunity__Page</target> Enables the component to
9 <target>lightningCommunity__Default</target> included configurable
10 </targets> properties.
11 <targetConfigs>
12 <targetConfig targets="lightningCommunity__Default">
13 <property name="prop1" type="String" />
14 </targetConfig> Enables the component to
15 </targetConfigs> included configurable properties.
16 </LightningComponentBundle>
Open Source Lightning Web Components 282

Lightning Web Components Open Source web site


https://lwc.dev

Recipes Live Version


https://recipes.lwc.dev
To install Lightning Web Components and create your
first app, use the open source lwc-create-app tool.

$ npx lwc-create-app my app


$ cd my-app
$ npm run watch

NOTE:
Base lightning web components like <lightning-button> can be installed by running the
command npm install lightning-base-components
Base Components Recipes 283

• Source code for


many components
from the lightning
namespace is
transpiled into the c
namespace
• Jest tests included for
most components
• Explore the inner
workings of these
base components
and customize them
to your needs!
Lesson Summary 284

• Lightning App Builder enables you to • Set a target of lightning__AppPage


build Lightning Pages visually from a to make an LWC available in App
drag-and-drop GUI. Launcher, as a custom tab in Lightning
Experience, and in the mobile
• You can make your Lightning web navigation menu.
component available to App Builder
with this configuration: • Lightning web components can be
<isExposed>true</isExposed> invoked from an <aura:application>.

• You must wrap a Lightning web • Use JavaScript in conjunction with a


component in Aura components in Lightning Out dependency app to use
order to use them with the currently a Lightning web component in a
unsupported experiences and tools, Visualforce Page.
such as quick actions.
Lesson Review 285

1. You can use a Lightning web 5. In a component's property editor


component directly in a Lighting in Lightning App Builder, a
Component tab (true/false). picklist property's options can be
populated dynamically
2. Under which circumstances (true/false).
should you consider using a
Lightning web component 6. You can access the current
wrapped in an Aura component? recordId in a custom Lightning
web component on a record
3. What is an Aura Interface and detail page by declaring a @track
how do you add one to an Aura property named recordId, which
component? causes the value to be injected
automatically (true/false).
4. You can only add one interface to
an Aura component (true/false).
Lesson 4:
Navigation and Layouts
Lesson 4: Navigation and Layouts 287

• Use lightning-vertical-navigation
• Use lightning-datatable
• Implement Button Groups
• Build Responsive Layouts
<lightning-vertical-navigation> 288

• lightning-vertical-navigation outputs a vertical list


of links that either take the user to another page or
to parts of the page the user is in.

• Represents a list of links that's only one level deep.

• Supports overflow sections that collapse and


expand.
– Must be created using lightning-vertical-navigation-overflow
– Does not adjust automatically based on the viewport
* Use Navigation Items 289

1 <lightning-vertical-navigation selected-item="recent">
2 <lightning-vertical-navigation-section
3 label="Reports">
4 <lightning-vertical-navigation-item label="Recent"
5 name="recent" >
6 </lightning-vertical-navigation-item> Section
7 <lightning-vertical-navigation-item
8
9
label="All Reports" name="all" >
</lightning-vertical-navigation-item>
Collapsible
10 </lightning-vertical-navigation-section> overflow
11 <lightning-vertical-navigation-overflow>
12 <lightning-vertical-navigation-item
13 label="Regional Sales East" name="east" >
14 </lightning-vertical-navigation-item>
15 <lightning-vertical-navigation-item
16 label="Regional Sales West" name="west" >
17 </lightning-vertical-navigation-item>
18 </lightning-vertical-navigation-overflow>
19 </lightning-vertical-navigation>
Add Navigation Items to Sections and Overflows 290

1 <lightning-vertical-navigation selected-item="recent"> Required


2 <lightning-vertical-navigation-section label="Reports">
3 <lightning-vertical-navigation-item label="Recent" name="recent" >
4 </lightning-vertical-navigation-item>
5 <lightning-vertical-navigation-item label="All Reports" name="all" >
6 </lightning-vertical-navigation-item>
7 </lightning-vertical-navigation-section>
8 <lightning-vertical-navigation-overflow>
9 <lightning-vertical-navigation-item label="Regional Sales East" name="east" >
10 </lightning-vertical-navigation-item>
11 <lightning-vertical-navigation-item label="Regional Sales West" name="west" >
12 </lightning-vertical-navigation-item>
13 </lightning-vertical-navigation-overflow>
14 </lightning-vertical-navigation>

NOTE:
Other sub-components lightning-vertical-navigation is used together with:
• lightning-vertical-navigation-item-badge
• lightning-vertical-navigation-item-icon
Handle Navigation Item Selection 291

1 <lightning-vertical-navigation onselect={handleSelect} selected-item="recent">


2 <lightning-vertical-navigation-section label="Reports">
3 <lightning-vertical-navigation-item label="Recent" name="recent">
4 </lightning-vertical-navigation-item>
5 <lightning-vertical-navigation-item label="Created by Me" name="created">
6 </lightning-vertical-navigation-item>
7 <lightning-vertical-navigation-item label="All Reports" name="all">
8 </lightning-vertical-navigation-item>
9 </lightning-vertical-navigation-section>
10 </lightning-vertical-navigation>

1 import { LightningElement } from 'lwc';


2
3 export default class VerticalNavExample extends LightningElement {
4 handleSelect(event) {
5 const selectedName = event.detail.name;
6 }
7 }
Include Dynamic Information in the Name Attribute 292

1 <lightning-vertical-navigation-section label="Certification"> awNavigation.html


2 <lightning-vertical-navigation-overflow>
3 <template for:each={certifications} for:item="cert">
4 <lightning-vertical-navigation-item
5 key={cert.Id}
6 label={cert.Name}
7 name={cert.compoundKey}>
8 </lightning-vertical-navigation-item>
9 </template>
10 </lightning-vertical-navigation-overflow>
11 </lightning-vertical-navigation-section>

1 if (data) { awNavigation.js
2 this.certifications = data.map(cert => ({
3 Id: cert.Id,
4 Name: cert.Name,
5 compoundKey: `certification|${cert.Id}|${cert.Name}`
6 }));
7}
Exercise 4-1: Implement a Vertical Navigation 293

layout-manager custom component student-browser student-detail custom component


custom component

aw-navigation
new custom
component
Exercise 4-1: Implement a Vertical Navigation 294

Pull the list of certifications and dynamically generate


navigation links.

Tasks:
1. Create the Apex class to retrieve 5. Handle Navigation Selection .
certifications. 6. Deploy the Component.
2. Create the Navigation Component. 7. Modify the Layout.
3. Fetch the Certifications.
4. Dynamically Generate the Vertical
Navigation. 45 minutes
Lesson 4: Implement Navigation and Layouts 295

• Use lightning-vertical-navigation

• Use lightning-datatable
• Implement Button Groups
• Build Responsive Layouts
Introducing <lightning-datatable> 296

1 <template>
2 <div style="height: 300px;">
3 <lightning-datatable
4 key-field="id"
5 data={data}
6 columns={columns}>
7 </lightning-datatable>
8 </div>
9 </template>

NOTE:
Not supported on mobile devices.
Example <lightning-datatable> JavaScript Code 297

1 import { LightningElement } from 'lwc';


2 const columns = [
3 {label: 'Opportunity name', fieldName: 'opportunityName', type: 'text'},
4 {label: 'Contact Phone', fieldName: 'phone', type: 'phone'}
5 ];
6
7 const data = [
8 {
9 id: 'a',
10 opportunityName: 'Cloudhub',
11 phone: '2352235235'
12 },
13 {
14 id: 'b',
15 opportunityName: 'Quip',
16 phone: '2352235235'
17 }
18 ];
19
20 export default class DatatableExample extends LightningElement {
21 data = data;
22 columns = columns; Make the properties that were declared outside of
23 } class declaration available to the HTML template.
Specify Rows and Columns 298

1 <lightning-datatable Associates each row with a


2 key-field="contactId" unique identifier.

3 data={certifiedStudents} Populates the row data.


4 columns={columnConfig}>
Populates the column data.
5 </lightning-datatable>

NOTE:
The checkbox column is displayed by default and it can be hidden by using the
hideCheckboxColumn attribute.
Grid Column Data Types 299

boolean Displays the icon utility:check if the value is true, and a blank value otherwise.
button Displays a button using lightning-button
currency Displays a currency using lightning-formatted-number
date Displays a date and time based on the locale using lightning-formatted-date-time.
See Displaying Date and Time Using Type Attributes.
email Displays an email address using lightning-formatted-email
location Displays a latitude and longitude of a location using lightning-formatted-
location
number Displays a number using lightning-formatted-number
percent Displays a percentage using lightning-formatted-number
phone Displays a phone number using lightning-formatted-phone
text Displays text using lightning-formatted-text
url Displays a URL using lightning-formatted-url

NOTE:
The data table formats the data cells of a column based on the type you specify for the column.
Each data type is associated with a base Lightning component.
Handle Row Selection 300

1 <lightning-datatable
2 key-field="contactId"
3 data={certifiedStudents}
4 columns={columnConfig}>
5 onrowselection={onRowSelection}
6 </lightning-datatable>

1 onRowSelection(evt) {
2 const numRowsSelected = evt.detail.selectedRows.length;
3 this.btnGroupDisabled = (numRowsSelected === 0);
4 }
* Exercise 4-2: Use <lightning-datatable> 301

onselect - dispatch @api certificationId


layout-manager @api certificationName certified-student-list
navitemselected lightning-datatable
custom event custom component new custom component standard component

aw-navigation
custom component
Exercise 4-2: Use <lightning-datatable> 302

Use lightning-datatable to output a list of contacts who have


gotten certified in the selected topic.

Tasks:
1. Create the Apex class to retrieve
certifications.
2. Create the Datatable Component.
3. Retrieve and transform the data.
4. Define the Datatable Columns
35 minutes
5. Instantiate the Component.
Lesson 4: Navigation and Layouts 303

• Use lightning-vertical-navigation
• Use lightning-datatable

• Implement Button Groups


• Build Responsive Layouts
Use <lightning-button-group> 304

1 <lightning-button-group>
2 <lightning-button label="E-mail" data-btn-id="btn1" onclick={onCertActions}>
3 </lightning-button>
4 <lightning-button label="Send Cert." data-btn-id="btn2" onclick={onCertActions}>
5 </lightning-button>
6 <lightning-button label="Delete" data-btn-id="btn3" onclick={onCertActions}
7 variant="destructive">
8 </lightning-button> Custom attributes must
9 </lightning-button-group> be formatted as data-*

1 onCertActions (event) {
2 const btn = event.target.getAttribute('data-btn-id');
3 alert(`${btn} Clicked`);
4 }

NOTE:
The body of the component can contain lightning-button or lightning-button-menu.
Mutual Exclusivity Behavior 305

To implement mutually exclusive behavior, use


lightning-radio-group type="button"
instead of lightning-button-group.

1 <lightning-radio-group 1 get reviewTypes() {


2 type="button" 2 return [
3 name="reviewType" 3 { label: 'Hotel', value: '1' },
4 label="Review of:" 4 { label: 'Restaurant', value: '2' },
5 options={reviewTypes} 5 { label: 'Training Center', value: '3'},
6 onchange={revChange}> 6 { label: 'Students', value: '4' }
7 </lightning-radio-group> 7 ];
8 }
Imperative Apex 306

1 import deleteStudentCertification from


'@salesforce/apex/CertifiedStudentList.deleteStudentCertification';
2 import { refreshApex } from '@salesforce/apex';
1 onDelete() {
2 const certificationIds = this.getSelectedIDs(); Methods with DML
3 deleteStudentCertification({certificationIds})
.then( () => {
statements can only be
4
5 refreshApex(this._wiredStudentResult); called imperatively and
6 }) cannot be cached.
7 .catch(error => {
8 this.error = 'Unknown error';
9 if (Array.isArray(error.body)) {
10 this.error = error.body.map(e => e.message).join(', ');
11 } else if (typeof error.body.message === 'string') {
12 this.error = error.body.message;
13 }
14 });
15 }
Refresh Wired Data 307

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


1 @wire(getCertifiedStudents, {certificationId:'$certificationId'})
2 wired_getCertifiedStudents(result) {
3 this._wiredStudentResult = result; Use (result) instead of
4 if (result.data) { ({error, data})
5 …
1 onDelete() {
2 const certificationIds = this.getSelectedIDs();
3 deleteStudentCertification({certificationIds})
4 .then( () => {
5 refreshApex(this._wiredStudentResult);
6 })
7 .catch(error => {
8 this.error = 'Unknown error’;
9 });
10 }

NOTE:
For getRecord, you can use getRecordNotifyChange instead of refreshApex.
Exercise 4-3: Implement <lightning-button-group> 308

aw-navigation certified-student-list
layout-manager custom component
custom component custom component

lightning-button-group
standard component

Disabled unless at least


one row selected
Exercise 4-3: Implement Button Groups 309

Add a button group to the certifiedStudentList card.

Tasks:
1. Add Buttons to the Certification 4. Handle the Delete Button Action.
List. 5. Refresh the Data after Delete.
2. Enable/Disable Buttons Based on
Grid Row Selection.
3. Define the Apex Method to Delete
Certifications. 25 minutes
EXTRA CREDIT Reinforce your Skills with a Challenge 310

Talented
with Tables Challenge 3: Create a Contact Directory

Scenario
Create a component challenge_contactDirectory that
uses a lightning-datatable to output all contact
names, email addresses, and phone numbers. Display
the component on the custom Home Page for the
Certification App.

• Using Base Components


• Working with Data
• JSON configuration objects

30 min
Lesson 4: Navigation and Layouts 311

• Use lightning-vertical-navigation
• Use lightning-datatable
• Implement Button Groups

• Build Responsive Layouts


See <lightning-layout> in Action 312

1. Go to:
https://developer.salesforce.com/docs/component-
library/bundle/lightning-layout
2. Click Example tab.
3. Select different example options.
4. Click Specification tab.
5. What happens when “Multiple Rows” is set to active?
Use the <lightning-layout> Grid System 313

Narrow Page Width

Layout responds
dynamically to
available space to split
across multiple rows.

Wide Page Width


Implement Responsive Relative Column Sizing 314

<lightning-layout class="slds-wrap">
<lightning-layout-item size="12">Header</lightning-layout-item>
<lightning-layout-item size="6" small-device-size="6"
medium-device-size="4">
Nav
</lightning-layout-item>
<lightning-layout-item size="6" small-device-size="6"
medium-device-size="4">
Body
</lightning-layout-item>
<lightning-layout-item size="12" small-device-size="12"
medium-device-size="4">
Aside
</lightning-layout-item>
<lightning-layout-item size="12">Footer</lightning-layout-item>
</lightning-layout>

You cannot have device specific size attributes for component without
specifying the size attribute, and the size attribute must come before
the device specific sizes.
Implement Responsive, Relative Column Sizing (SLDS 315
Markup)
1 <div class="slds-grid slds-wrap">
2 <header class="slds-col slds-size_1-of-1">Header</header>
3 <nav class="slds-col slds-size_6-of-12 slds-small-size_6-of-12
4 slds-medium-size_4-of-12">
5 Nav
6 </nav>
7 <div class="slds-col
8 slds-size_6-of-12 slds-small-size_6-of-12
9 slds-medium-size_4-of-12 ">
10 Body
11 </div>
12 <aside class="slds-col slds-size_12-of-12 slds-small-size_6-of-12
13 slds-medium-size_4-of-12 ">
14 Aside
15 </aside>
16 <footer
17 class="slds-col slds-size_1-of-1">Footer</footer>
18 </div>
Conditional Column Reordering – What is it? 316

Our responsive implementation shows studentDetail below the gallery at


mobile. Can we make it show above for a better user experience?
Conditional Column Reordering – What is it? 317

Without much work, we can make studentDetail show


above the studentBrowserForm instead.
Conditional Column Reordering – Implementation 318

1 <template> layoutManager.html
2 …
3 <lightning-layout-item class="slds-order_1"
4 padding="around-small" size="12" small-device-size="12"
5 medium-device-size="3" large-device-size="2">
6 <c-aw-navigation onnavitemselected={handleNavItemSelected}>
7 </c-aw-navigation> slds-order_x
8
classes set the
</lightning-layout-item>
9 <template if:true={studentBrowserView}>
10
11
<lightning-layout-item class="slds-order_3 slds-medium-order_2"
padding="around-small" size="12" small-device-size="12" order for
12
13
medium-device-size="5" large-device-size="7">
<c-student-browser></c-student-browser>
different screen
14 </lightning-layout-item> sizes.
15 <lightning-layout-item class="slds-order_2 slds-medium-order_3"
16 padding="around-small" size="12" small-device-size="12"
17 medium-device-size="4" large-device-size="3">
18 <c-student-detail></c-student-detail>
19 </lightning-layout-item>
20 </template>
21 …
22 </template>
Create Independently Scrolling Regions 319

1 <template> studentBrowser.html
2 …
3 <div class="slds-scrollable scrollerSize" >
4 <c-student-tiles student-list={students} onstudentselected={handleNotify}>
5 </c-student-tiles>
6 </div>
7 …
8 </template>

studentBrowser.css
1 .scrollerSize {
2 height:300px;
3}

NOTE:
slds-scrollable is Desktop Only.
Exercise 4-4: Create a Responsive Layout 320

Wide View

Narrow View

scrollbar
Exercise 4-4: Create a Responsive Layout 321

Modify the lightning-layout in your awNavigation component to


make the application responsive to different screen widths.
Make the Student Gallery independently scrollable.

Tasks:
1. Configure a responsive layout.
2. Enable scrolling.

15 minutes
Lesson Summary 322

• lightning-vertical- • The Lightning Design System grid,


navigation outputs a set of links based on Flexbox, provides a
that can be nested 1-level deep. flexible, mobile-first, device-
agnostic scaffolding system for the
• Use lightning-datatable to layout and aligning of components.
output data in a grid with columns
that can be optionally resized and • SLDS grids and containers enable
sorted. you to create x-device compatible
responsive layouts.
• Button groups enable you to
organize and group available user • Grid behavior is based on a 12-
actions. column layout.
Lesson Review 323

1. You can restrict the number of records that may be


selected in a lightning-datatable (true/false).

2. Describe a use-case for lightning-button-group.

3. How can you identify which button was clicked in a


lightning-button-group if each button uses the same
controller action?

4. What two steps allow you to create a scrollable area


on desktop?
Lesson 5:
Advanced Components
Lesson 5: Advanced Components 325

• Create a Custom, Responsive Datatable


• Define Public Methods on Components
• Service Components and Toast Notifications
• Slots and Modal Notifications
• Localization
• Renderers and Third-Party JS
Make an HTML Table Responsive 326

Table rows become stacked groups, and fields (for example, Title) are
optionally hidden.

Wide View Narrow View


Use slds-max-medium-table_stacked 327

1 <template>
2 <table class="slds-table slds-table_bordered slds-max-medium-table_stacked">
3 <thead>
4 <tr class="slds-text-heading_label">
5 <template for:each={columnConfig} for:item="col">
6 <th key={col.fieldName} scope="col" class={col.class}>
7 <span class="slds-truncate">{col.label}</span></th>
8 </template>
9 </tr>
10 </thead>
11 <tbody>
12 …
13 </template>

NOTE:
Apply slds-max-medium-table_stacked to modify table layout by stacking
cells to accommodate smaller viewpoints.
Apply a Media Query for Hidden CSS 328

Media Query
Media queries can be used to hide elements depending on the screen size.

responsiveDatatable.css If the screen size is 767px


1 @media (max-width: 767px) { or less, hide the element.
2 .hiddenOnMobile {
cols = [ studentBrowser.js
3 display: none !important; {
4 } fieldName:"Name",
5 } label: "Name"
},
responsiveDatatable.js {
1 if(colItems[j].hiddenOnMobile{ fieldName:"Title",
colClass = "hiddenOnMobile"; label: "Title",
2
hiddenOnMobile: true
3 } }
]
Use Public Getters/Setters 329

Annotate either the getter or the setter with @api, but not both.
It’s a best practice to annotate the getter.
1 rows;
2 @api
3 get rowData() {
4 return this.rows;
5 }
6 set rowData(value) {
7 if (typeof value !== "undefined") {
8 this.rows = this.reformatRows(value,this.columnConfig);
9 }
10 }

reformatRows accepts data passed in as a one-dimensional array


and reformats it in two dimensions for use in a nested for-loop
that outputs our <tr> and <td> elements.
Reformat Data for the Grid 330

[{ Id: 1, [{ pk: 1,
Name: "John Doe", data:
Order of items in data[] is consistent
Title: "Tech", [{ and matches columnConfig
Email: "[email protected]" label: "Name",
}] value: "John Doe",
reformatRows() class: "",
isEmail: false, isOther: true,
columnConfig = [ type: undefined
{ },{
fieldName:"Name", label: "Title",
label: "Name" value: "Tech",
}, class: "hiddenOnMobile",
{ isEmail:false, isOther:true,
fieldName:"Title", type: undefined
label: "Title", }, {
hiddenOnMobile: true label: "E-Mail",
}, value: "[email protected]",
{ class: "",
fieldName:"Email", isEmail: true, isOther: false,
label: "E-Mail", type: "email"
type: "email" }]
}]; }]
Implement Data Pagination 331

Implement pagination to improve LIMIT


performance and stability when The number of records to
working with large datasets. fetch in a single request
… paginationExample.cls
r.data = [ OFFSET
SELECT
Student__r.email, Where to begin in the recordset
Student__r.name,
Student__r.Account.Name,
Course_Delivery__r.Start_Date__c,
Course_Delivery__r.Course__r.Name
EXAMPLE
FROM Course_Attendee__c
ORDER BY Student__r.name If there are 1000 rows total,
LIMIT :numRecords passing a limit of 50 and an
OFFSET :offset
]; offset of 200 will request
… records 200-249
* Exercise 5-1: Create a Responsive Datatable 332

aw-navigation Wide View layout-manager


custom component custom component

Narrow View

@api columnConfig
@api rowData
student-browser responsive-datatable
custom component new custom
component
Title field hidden in
narrow view
Exercise 5-1: Create a Responsive Datatable 333

Create and deploy custom, responsive datatable that fires


custom click and double-click events.

Tasks:
1. Install a component. 5. Deploy the component
2. Re-raise DOM events. 6. Hide “Hidden” detail fields
3. Highlight the selected row 7. Open an editor modal on double-
4. Use a custom setter to reformat click
row data
40 minutes
Lesson 5: Advanced Components 334

• Create a Custom, Responsive Datatable

• Define Public Methods on Components


• Service Components and Toast Notifications
• Slots and Modal Notifications
• Localization
• Renderers and Third-Party JS
Define Public Methods on Components 335

1 updateSelectedStudent(studentId) {
2 const grid = this.template.querySelector('c-data-grid-table');
3 const gallery = this.template.querySelector('c-student-tiles');
4 if (gallery) {
5 gallery.setSelectedStudent(studentId);
6 }
7 if (grid) {
8 grid.setSelectedRecord(studentId);
9 }
10 }

1 @api setSelectedRecord(recordId) {
2 const mySelector = `tr[data-pk='${recordId}']`;
3 const selectedRow = this.template.querySelector(mySelector);
4 if (selectedRow) {
5 this.highlightSelectedRow(selectedRow);
6 }
7 }
Exercise 5-2: Use Custom Events and Public Methods 336

student-browser: student-detail:
handle rowclick handle studentChange via LMS
• publish LMS - reloadRecord
• call setSelected public method in responsiveDatatable
• call setSelectedStudent public method in studentTiles

onclick: Fire custom


event rowclick

responsive-datatable
custom component
Exercise 5-2: Use Public Methods 337

Update your solution to keep the grid and gallery tabs in sync by
calling public methods.

Tasks:
1. Define a rowclick event.
2. Define two public methods on
components.
3. Invoke two public methods on
components.
4. Discuss deferred instantiation. 25 minutes
Lesson 5: Advanced Components 338

• Create a Custom, Responsive Datatable


• Define Public Methods on Components

• Service Components and Toast Notifications


• Slots and Modal Notifications
• Localization
• Renderers and Third-Party JS
What is a Service Component? 339

Service Component

Components with no user interface, meant for


1
code reuse.

Creating using ES6 module syntax. Export the


2
variables or functions that you want to expose.

3 Use either a default export or named exports.


* Service Components – Default Export 340

A module can defaultUtils.js


export a single
default 1 export default class defaultUtils {
2 alertMessage = (msgToAlert) => {
function or 3 alert(msgToAlert);
class. 4 }
5 logMessage = (msgToLog) {
6 console.log(msgToLog);
7 }
8 }
The importing Different names
component allowed myApp.js
chooses any
name to refer to 1 import utils from 'c/defaultUtils';
the default 2 const myUtils = new utils();
export. 3 myUtils.alertMessage('Hello world!');
4 myUtils.logMessage('Astro was here.');
Service Components – Named Export 341

namedUtils.js

1 function alertMessage(msgToAlert) {
2 alert(msgToAlert); A module can also
3 } export named
4 function logMessage(msgToLog) {
5 console.log(msgToLog);
functions, variables,
6 } and classes.
7 export {alertMessage, logMessage};

Same
name myApp2.js The module that
required imports the functions
1 import {alertMessage, logMessage}
2 from 'c/namedUtils'; or variables must use
3 alertMessage('Hello user!'); the exported names.
4 logMessage('Hello console!');
Examples of Default and Named Exports 342

Importing an Apex method uses the default export syntax.


import getStudents from '@salesforce/apex/StudentBrowser.getStudents';

Importing wire adapters from uses named export syntax.


import { getRecord, getFieldValue, getFieldDisplayValue }
from 'lightning/uiRecordApi';
What is a Toast Message? 343

TOAST MESSAGE

Unobtrusive status messages that pop down from


1 the top of the screen and do not halt script
execution.

Configurable modes include:


2
• dismissible (default)– visible 3 seconds or until
clicked, whichever comes first
• pester – visible for 3 seconds
• sticky – visible until clicked
Implement Toasts using a Service Component 344

1 <template>
2 <lightning-button
3 label="Show Toast"
4 onclick={btnClick}>
5 </lightning-button>
6 </template>

1 import { ShowToastEvent } from 'lightning/platformShowToastEvent'


2 export default class Utils {
3
4 static showToast = (firingComponent, toastTitle, toastBody, variant) => {
5 const event = new ShowToastEvent({
6 title: toastTitle,
7 message: toastBody,
8 variant: variant
9 }); A static method is used
10 firingComponent.dispatchEvent(event); here so that we don't
11 }
12 }
have to instantiate the
Utils class.
Show a Toast by Calling the Service Component 345

1 import { LightningElement} from 'lwc';


2 import Utils from 'c/utils';
3
4 export default class ToastDemo extends LightningElement {
5 connectedCallback() {
6 Utils.showToast(
7 this,
8 'Welcome',
9 "Check back here for updated class schedules and assignments",
10 'info'
11 );
12 }
13 }
Exercise 5-3: Use Toasts and Service Components 346

connectedCallback()
call Utils.showToast static method utils custom service component calls
layout-manager ShowToastEvent standard event
custom component
Exercise 5-3: Use Toasts and Service Components 347

Create a service component and define a function to show toast


notifications.

Tasks:
1. Create the Service Component.
2. Define a Function to Show Toast
Notifications.
3. Display a Toast Notification at
Startup.
4. Refactor some code. 35 minutes
Lesson 5: Advanced Components 348

• Create a Custom, Responsive Datatable


• Define Public Methods on Components
• Service Components and Toast Notifications

• Slots and Modal Notifications


• Localization
• Renderers and Third-Party JS
Slots 349

A slot (<slot></slot>) is a placeholder for markup that can be passed into a


component’s body.

1 <lightning-card title={name}> The actions slot in a


2 <lightning-button lightning-card
3 slot="actions"
4 label="Go to record" allows you to put
5 onclick={onGoToRecord}> content in the top-right
6 </lightning-button> corner.
7 <div class="slds-var-p-horizontal_small">
8 <p><lightning-formatted-email value={email}>
9 </lightning-formatted-email></p>
10 <p><lightning-formatted-phone value={phone}>
11 </lightning-formatted-phone></p>
12 <p>{description}</p>
13 </div>
14 </lightning-card>
Default Slot 350

slotWrapper.html slotDemo.html
1 <template> 1 <template>
2 <c-slot-demo> invokes 2 <h1>Add content to slot</h1>
<p>Content from parent</p> 3 <div>
3 <slot></slot>
4
4 </c-slot-demo> </div>
5
5 </template> 6 </template>

When <c-slot-demo> is rendered, the unnamed slot is replaced


with the markup passed into the body of <c-slot-demo>. Here’s the
output of <c-slot-wrapper>.

1 <h1>Add content to slot</h1>


2 <div>
3 <slot>
4 <p>Content from parent</p>
5 </slot>
6 </div>
Named Slot 351

namedSlots.html
1 <template>
2 <p>First Name: <slot name="firstName">Default first name</slot></p> Define named
3 <p>Last Name: <slot name="lastName">Default last name</slot></p> slots
4 <p>Description: <slot>Default description</slot></p>
5 </template>
1 <template> usesNamedSlots.html
2 <c-named-slots>
3 <span slot="firstName">El</span>
4 <span slot="lastName">Toro</span> Pass markup
5 <span>Global Master Instructor</span> into slots
6 </c-named-slots>
7 </template>

1 <c-named-slots> usesNamedSlots.html
2 <p>First Name:
3 <slot name="firstName"><span slot="firstName">El</span></slot></p>
Rendered
4 <p>Last Name:
5 <slot name="lastName"><span slot="lastName">Toro</span></slot></p> output
6 <p>Description:
7 <slot><span>Global Master Instructor</span></slot></p>
8 </c-named-slots>
Modals 352

Named slot (header)


Unnamed slot
Named slot (footer)
* Exercise 5-4: Implement Modals using Slots 353

modal layout-manager custom component onclick – dispatch showmodal


certified-student-list Handle showmodal event
new custom custom event via
custom component Call modal.show public method
component Utils.showModal static method

Call modal.hide
public method
Exercise 5-4: Implement Modals using Slots 354

When the Email or Send Certification button is clicked in our


certifiedStudentList button group, add “Not Available”
functionality that shows a message to the user in a modal.

Tasks:
1. Install the Modal Component. 5. Show a Notification when a Feature
2. Define a Function to Show Modal is not Available.
Notifications.
3. Handle the Custom Event.
4. Invoke the Modal in the Root
Component. 30 minutes
Lesson 5: Advanced Components 355

• Create a Custom, Responsive Datatable


• Define Public Methods on Components
• Service Components and Toast Notifications
• Slots and Modal Notifications

• Localization
• Renderers and Third-Party JS
Localize Content 356

You can use the following techniques to localize your components to


support multiple languages, currency, and date formats:

• Define dynamic labels that can be translated into multiple languages.

• Output date and number values using <lightning-formatted-


date-time> and <lightning-formatted-number> components.
User Locale Settings 357

• Users can set their locale from within Lightning Experience.


• <lightning-formatted-number> and
<lightning-formatted-date-time> automatically read the user’s locale
setting and adjust their output accordingly.
Custom Labels 358

Custom labels are custom text values


that can be translated into any
language that Salesforce supports.

• Enable you to create multilingual


applications by automatically
present information in a user's
native language.
• May contain up to 1000 characters.
• Up to 5000 labels per org.

CLICK PATH:
Setup | User Interface | Custom Labels
Translation Workbench 359

Manual Label Translation includes


the following steps:

1. Enabling Translation Workbench.


2. Selecting users to act as
translators.
3. Manually translating labels.
Create a Translation 360

When the assigned users log in to Lightning Experience and edit a custom
label, they’ll be presented with an option to translate that label into their
assigned languages, as illustrated by the figure.
Programmatically Access Labels 361

Syntax:
import labelName from '@salesforce/label/labelReference';

Example:

1 import { LightningElement } from 'lwc';


2 import LABEL_FEATURE_NOT_AVAILABLE
3 from '@salesforce/label/c.Feature_Not_Available';

5 notAvailable() {
6 Utils.showModal(this,'Not Available',
7 LABEL_FEATURE_NOT_AVAILABLE,'success');
8 }
Externalize your Imports in a Separate File 362

myLabels.js
1 import LABEL1 from '@salesforce/label/c.label1';
2 import LABEL2 from '@salesforce/label/c.label2'; Import labels
3 import LABEL3 from '@salesforce/label/c.label3';
4
5 export default {
6 LABEL1, Export labels as object
7 LABEL2,
8 LABEL3
9 }

useLabels.js
1 import { LightningElement } from 'lwc';
2 import labels from './myLabels'; Import object
3
4 export default class UseLabels extends LightningElement {
5 labels = labels;
6 } Make object available to HTML template as
{labels.LABEL1}, {labels.LABEL2}, {labels.LABEL3}
Use @salesforce/i18n Scoped Module 363

Lightning web components can use internationalization properties to be


adapted for users worldwide, across languages, currencies, and time zones.

import internationalizationPropertyName from


'@salesforce/i18n/internationalizationProperty';
1 import { LightningElement } from 'lwc';
2 import LOCALE from '@salesforce/i18n/locale';
3 import CURRENCY from '@salesforce/i18n/currency';
4
5 export default class Example extends LightningElement {
6 const number = 123456.78;
7 const numberFormat = new Intl.NumberFormat(LOCALE, {
8 style: 'currency',
9 currency: CURRENCY,
10 currencyDisplay: 'symbol'
11 });
12 numberFormat.format(number);
13 }
Localize Dates in Markup with 364
<lightning-formatted-date-time>
A lightning-formatted-date-time component displays formatted
date and time using the Intl.DateTimeFormat JavaScript object to format
date values. The default formatting is determined by the locale set in the
Salesforce user preferences.

1 <template> Locale Setting:


2 <lightning-formatted-date-time English (United States)
3 value="1547250828000"
4 year="2-digit" Friday, Jan 11, 19
5 month="short"
6 day="2-digit"
7 weekday="long"
Locale Setting:
8 time-zone="UTC">
French (France)
9 </lightning-formatted-date-time>
10 </template> vendredi, 11 janvier 19
<lightning-formatted-number> 365

A lightning-formatted-number component displays formatted


numbers for decimals, currency, and percentages. The locale set in the
Salesforce user preferences determines how numbers are formatted.

Locale Setting:
English (United States)
1 <template>
2 <lightning-formatted-number $5000.00
3 value="5000"
4 format-style="currency"
5 currency-code="USD">
6 </lightning-formatted-number> Locale Setting:
7 </template> French (France)
5000,00 $US
NOTE:
Currencies default to 2 decimal places.
Exercise 5-5: Localize Content 366

c-aw-navigation c-layout-manager
custom component custom component

c-modal now includes


a localized message
rather than a
hardcoded string

c-certified-student-list
custom component
Exercise 5-5: Localize Content 367

Define a dynamic label for use in the “Not Available” message.

Tasks:
1. Define a label.
2. Deploy the label.
3. Translate the label.
4. Verify that the label is output for
Spanish-speaking users.
15 minutes
Lesson 5: Advanced Components 368

• Create a Custom, Responsive Datatable


• Define Public Methods on Components
• Service Components and Toast Notifications
• Slots and Modal Notifications
• Localization

• Renderers and Third-Party JS


RenderedCallback 369

1 renderedCallback() { Use a Boolean


2 if (this._chartjsInitialized) { property to execute
3 return;
} logic only the first
4
5 this._chartjsInitialized = true; time rendering
… completes.

The renderedCallback() is unique to``Lightning Web Components. Use it to


perform logic each time a component has finished the rendering phase.
Use a Static Resource as JS with loadScript 370

1 Download the library from the third-party library's site.

2 Upload the library to your Salesforce organization as a static resource.

3 Import the static resources and the methods from the


platformResourceLoader module :
import resourceName from '@salesforce/resourceUrl/resourceName';
import { loadStyle, loadScript } from 'lightning/platformResourceLoader';

4 Call loadScript to load the JavaScript library:

loadScript(this, resourceName);

NOTE:
You cannot load JavaScript resources from a third-party site, even a CSP Trusted Site.
Use an External Style Sheet with loadStyle 371

1 Author a CSS file that can be used in multiple components.

2 Upload the CSS file to your Salesforce organization as a static resource.

3 Import the static resources and the methods from the


platformResourceLoader module :

import resourceName from '@salesforce/resourceUrl/resourceName';


import { loadStyle, loadScript } from 'lightning/platformResourceLoader';

4 Call loadStyle to load the CSS :

loadStyle(this, resourceName);

NOTE:
External style sheets allow you to share styles across components despite the Shadow DOM.
Work with External APIs 372

Options for making API calls


To communicate with an external API, you can proxy your request through
an Apex class OR you can call the external API directly.

The approach you select will impact what setup changes you must make.

Named Credentials / Remote Site Settings CSP Trusted Sites


Use named credentials or add a site to Remote Add a site to CSP trusted
Site Settings to allow Apex classes to make sites and select 'Allow site
callouts. Then, from your LWC, call an Apex for connect-src' to allow
method that calls the API. calls directly from your
Lightning web components.
Call External API's - the Code 373

1 fetch(apiURL)
2 .then((result) => {
3 // fetch may not throw an error if the request fails.
4 // So, let’s check the OK property.
5 if (!result.ok) {
6 this.error = response;
7 }
8 return result.json(); The Fetch API is currently not
9 } polyfilled for usage in IE11.
10 .then((jsonResponse) => {
11 this.someData = jsonResponse;
12 } Use XMLHttpRequest instead
.catch(error => {
13
this.error = error;
in that case.
14
15 this.books = undefined;
16 });
Pass Apex Data into ChartJS using Promises 374

1 import { LightningElement} from 'lwc';


2 import getCertPopularity from
3 '@salesforce/apex/CertPopularity.getCertPopularity';
4 import { loadScript } from 'lightning/platformResourceLoader';
5 import chartjs from '@salesforce/resourceUrl/chart’;

1 loadScript(this, chartjs)
.then(getCertPopularity)
The name you gave the
2
3 .then((result) => { Apex method in the import
4 const certData = result; statement above.
5 const certLabels = [];
6 const certCounts= [];
7 for (let i=0; i < certData.length; i++) {
8 certLabels.push(certData[i].Name);
9 certCounts.push(certData[i].Number_of_Certified_Professionals__c);
10 }
Lightning Locker 375

• Powerful security architecture for Lightning components.


• Enhances security by isolating Lightning components that belong to one
namespace from components in a different namespace.

• Promotes best practices that improve the supportability of your code by


only allowing access to supported APIs and eliminating access to non-
published framework internals.
– JavaScript ES5 Strict Mode Enforcement
– DOM Access Containment
– Stricter Content Security Policy
– Restrictions to Global References
– Access to Supported JavaScript API Framework Methods Only
Test Third Party JS with Locker Console 376

• Check your JavaScript code’s • Compare how it runs with


compatibility with Lightning Locker Lightning Locker enabled
and disabled.
• Evaluate or benchmark your JS
without creating an app • Accessed via the
Component Library
Use Locker API Viewer 377

• Available in the
Component Library

• View which standard DOM


APIs are supported by Locker

• The SecureWindow,
SecureDocument, and
SecureElement wrappers
prevent use of APIs that are
labeled "Not Supported".

• Vote on DOM API's you'd like


to see supported in a future
release

NOTE:
To prevent security risks, you can’t use third-party web components on the Salesforce platform.
Locker and DOM Access Containment 378

A component can only traverse the DOM and access elements that it created.
Lightning web components cannot use the window or document
global properties to query for DOM elements.

domAccess.html
1 <template>
2 <div class="c1">Testing querySelector</div><BR />
3 <lightning-button onclick={go} label="Go"></lightning-button>
4 </template>

domAccess.js
1 go(){
2 alert(this.template.querySelector('.c1')); //SecureElement (div)
3 alert(document.querySelector('.c1')); //null
4 }
Exercise 5-6: Renderers and Third-Party JS 379

c-aw-navigation c-layout-manager
custom component custom component

c-cert-popularity
custom component
Exercise 5-6: Renderers and Third-Party JS 380

Create a custom component that uses a third party JavaScript


library to render a chart showing the most popular AW
Computing certifications.

Tasks:
1. Create a Static Resource. 6. Deploy the Component.
2. Create an Apex class. 7. Render the Chart.
3. Create the Component Markup.
4. Create the CSS File.
5. Create the JS Controller.
35 minutes
Lesson 5 Summary 381

• Using a public setter allows • A slot is a placeholder for markup


execution of custom logic when a that a parent component passes
property value changes. into a component’s body.

• Public methods are decorated • Lightning web components


with @api and are part of a support localization via Custom
component’s API. Labels and components that are
locale-aware
• Service components encapsulate
reusable logic and have no user • Upload third-party JavaScript
interface. libraries as static resources to use
them in your components.
Lesson 5 Review 382

1. How can you execute custom 4. List two ways to reuse code in
logic when the value of a public Lightning web components.
property changes?
5. Every Lightning web component
2. List three ways a parent has an unnamed slot
component can communicate (true/false).
with a child component.
6. How does Salesforce determine
3. If you allowlist a domain in CSP the locale for logged in users?
Trusted Sites, you can load
resources from that domain
using the <script> tag
(true/false).
EXTRA CREDIT Reinforce your Skills with a Challenge 383

Awesome
with APIs Challenge 4: Website Visits per Month

Scenario
Create a component challenge_lineChart that uses a
chartJS to output a line chart using data from an
external URL that can be configured at design time in
App Builder.

Add an entry to CSP trusted sites, use fetch() to make


• Making AJAX requests
the request, and add the component to your
• Chaining calls with Promises Challenge Gallery.
• Using 3rd party JavaScript

40 min
Lesson 6:
Work with Data
Lesson 6 : Work with Data 385

• Implement Forms
• Implement Forms with Custom Controls
• Validate Input Data
• View and Edit Salesforce Records
• Wait for Server Requests to Complete
Lightning-record-form vs Lightning-record-edit-form vs 386
Lightning-record-view-form - a Feature Comparison

Feature lightning- lightning- lightning-


record-form record-view-form record-edit-form

Create Records
Edit Records
View Records
Read-Only Mode
Layout Types
Multi Column Layout
Custom Layout for
Fields

Custom Rendering of
Record Data
<lightning-record-form> 387

<lightning-record-form> Supports edit, view, and read-only modes.

1 <lightning-record-form
2 record-id={recordId}
3 object-api-name="Account"
4 fields={fields}
5 columns="1"
6 mode="view">
7 </lightning-record-form>
<lightning-record-edit-form> 388

<lightning-record-edit-form> Displays an editable form.

1 <lightning-record-edit-form Displays errors


2 record-id={recordId}
3 object-api-name="Contact">
4 <lightning-messages></lightning-messages>
5 <lightning-output-field field-name="AccountId">
6 </lightning-output-field>
7 <lightning-input-field field-name="FirstName">
8 </lightning-input-field>
9 <lightning-button type="submit" name="save" label="Save">
10 </lightning-button>
11 </lightning-record-edit-form>
No controller needed for save
<lightning-record-view-form> 389

<lightning-record-view-form> Displays a read-only form.

1 <lightning-record-view-form
2 record-id={recordId}
3 object-api-name="My_Contact__c">
4 <div class="slds-box">
5 <lightning-output-field field-name="Name">
6 </lightning-output-field>
7 <lightning-output-field field-name="Email__c">
8 </lightning-output-field>
9 </div>
10 </lightning-record-view-form>
<lightning-output-field> 390

A lightning-output-field component displays the field value in the


correct format based on the field type.

1 <lightning-record-view-form
2 record-id="001XXXXXXXXXXXXXXX"
3 object-api-name="Contact">
4 <div class="slds-box slds-theme_default">
5 <lightning-output-field field-name="Name">
6 </lightning-output-field>
7 <lightning-output-field field-name="Phone">
8 </lightning-output-field>
9 <lightning-output-field field-name="Email">
10 </lightning-output-field>
11 </div>
12 </lightning-record-view-form>

NOTE:
In orgs that support multiple languages, lightning-output-field automatically shows the
translated labels and picklist values.
Exercise 6-1: Work with Data 391

aw-navigation layout-manager trip-report-form lightning-record-edit-form


custom component custom component new custom component standard component
Exercise 6-1: Work with Data 392

Use lightning-record-edit-form with lightning-input-field to


create the TripReportForm component to allow instructors to
create trip reports.

Tasks:
1. Create the TripReportForm
Component.
2. Define Form Fields
3. Setup the Controller.
4. Deploy the Component.
25 minutes
Lesson 6 : Work with Data 393

• Implement Forms

• Implement Forms with Custom Controls


• Validate Input Data
• View and Edit Salesforce Records
• Wait for Server Requests to Complete
<lightning-input> Types 394

A lightning-input component creates an HTML input element.


Supported types:
Checkbox 1 <template>
Date 2 <lightning-input type="checkbox"
3 label="Red"
Datetime 4 checked>
Time 5 </lightning-input>
Email 6 <lightning-input type="color"
File 7 label="Color"
Password 8 value="#EE12EE">
9 </lightning-input>
Search 10 </template>
url
Number
Radio
Toggle
Default: Text
<lightning-input-rich-text> 395

A lightning-input-rich-text component creates a rich text editor based


on the Quill JS library, enabling you to add, edit, format, and delete rich text.
You can create rich text editors with different toolbar configurations.
1 <template>
2 <lightning-input-rich-text
3 value={myVal}
4 formats={formats}>
5 </lightning-input-rich-text>
6 </template>

1 import { LightningElement } from 'lwc';


2 export default class ColorFormatExample extends LightningElement {
3 myVal = 'Hello Toronto!';
4 formats = ['font', 'size', 'bold', 'italic', 'underline',
5 'strike', 'list', 'indent', 'align', 'link',
6 'image', 'clean', 'table', 'header', 'color'];}
<lightning-slider> 396

A lightning-slider component is a horizontal or vertical slider for


specifying a value between two specified numbers.

1 <template>
2 <lightning-slider
3 label="Volume"
4 step="10"
5 value="10"
6 onchange={handleChange}>
7 </lightning-slider>
8 </template>

1 import { LightningElement } from 'lwc';


2
3 export default class MyDemo extends LightningElement {
4 handleChange(event) {
5 alert(event.target.value);
6}
Use lightning/uiObjectInfoApi 397

The lightning/uiObjectInfoApi module includes wire adapters to get


object metadata and picklist values. studentBrowserForm.html

getObjectInfo()
 Get metadata about a specific object. Includes metadata describing
fields, child relationships, record type, and theme.

getPicklistValues()
 Get the picklist values for a specified field.

getPicklistValuesByRecordType()
 Get the values for every picklist of a specified record type.
* Get Picklist Values without Apex using UiObjectInfoApi 398

1 import { getObjectInfo, getPicklistValues} from 'lightning/uiObjectInfoApi';


2 import OBJECT_TRIP_REPORT from '@salesforce/schema/TripReport__c';
3 import FIELD_REVIEWTYPE from '@salesforce/schema/TripReport__c.ReviewType__c';

1 //Get Object Info
2 @wire (getObjectInfo, {objectApiName: OBJECT_TRIP_REPORT})
3 objectInfo;
4
5 //Get ReviewType picklist values
6 @wire (getPicklistValues, {recordTypeId: '$objectInfo.data.defaultRecordTypeId',
7 fieldApiName: FIELD_REVIEWTYPE})
8 wired_getPicklistValues({ error, data }) {
9 this.reviewTypes = [];
10 if (data) {
11 data.values.forEach(reviewType => { You can use one
12 this.reviewTypes.push({ @wire output as
value: reviewType.value,
another @wire input.
13
14 label: reviewType.label
15 });
16 });
17 }
18 }
Prepare to Save with Lightning Data Service 399

The createRecord and updateRecord wire adapters can be


used to save single records.

1 import { createRecord, getFieldValue, getRecord, updateRecord } from


2 'lightning/uiRecordApi';
3
4 import OBJECT_TRIP_REPORT from '@salesforce/schema/TripReport__c';
5 import FIELD_ID from '@salesforce/schema/TripReport__c.Id';
6 import FIELD_DATE from '@salesforce/schema/TripReport__c.Date__c';
7 import FIELD_INSTRUCTOR from '@salesforce/schema/TripReport__c.Instructor__c';
8 import FIELD_NAME from '@salesforce/schema/TripReport__c.Name’;
9 import FIELD_RATING from '@salesforce/schema/TripReport__c.Rating__c';
10 import FIELD_REVIEWTYPE from '@salesforce/schema/TripReport__c.ReviewType__c';
11 import FIELD_REVIEW from '@salesforce/schema/TripReport__c.Review__c';
Outline the Save Process with Create/Update 400

1 saveTripReport() {
2 const fieldsToSave = {}
fieldsToSave[FIELD_DATE.fieldApiName] = this.dateVisited;
3
fieldsToSave[FIELD_INSTRUCTOR.fieldApiName] = this.instructorId;
When creating, send
4
5 fieldsToSave[FIELD_RATING.fieldApiName] = this.rating; the object API name.
6 …
7 if (!this.recordId) {
8 const recordInput = { fields:fieldsToSave, apiName: OBJECT_TRIP_REPORT.objectApiName};
9 createRecord(recordInput)
10 .then(tripReport => { this.recordId = tripReport.id; } After creating, store
11 .catch(…) the new recordId.
12 } else {
13 fieldsToSave[FIELD_ID.fieldApiName] = this.recordId;
14 const recordInput = { fields:fieldsToSave}
15 updateRecord(recordInput)
16 .then(…)
17 .catch(…) When updating,
18 } send the recordId.
19 }
Compare Forms 401

record-edit-form Custom form


Every control is a Every control is a
lightning-input-field different component.

Control type based Control type based on


on object schema input component

Picklist and lookup Picklist and lookup


values auto- values retrieved in
populated. code.

Save logic built-in. Save logic is coded.

TripReportForm TripReportFormAdvanced
Exercise 6-2: Implement a Form using Custom Controls 402

c-aw-navigation c-trip-report-form-advanced
custom component custom component

lightning-combobox

lightning-input

lightning-input

lightning-radio-group

lightning-slider

lightning-input-
rich-text
Exercise 6-2: Implement a Form using Custom Controls 403

Create a form that allows heavy customization by using a


<form> tag with custom coded controls. Write the create /
update logic using Lightning Data Service.

Tasks:
1. Create the tripReportFormAdvanced 6. Define a Slider Control.
Component. 7. Define a Rich Text Field.
2. Setup the form shell. 8. Add Save and Cancel Buttons.
3. Implement the Instructor Selection 9. Save the record using Lighting Data
Box. Service.
4. Define Additional Form Fields. 10. Deploy the Component.
5. Implement a Radio Group. 40 minutes
Lesson 6 : Work with Data 404

• Implement Forms
• Implement Forms with Custom Controls

• Validate Input Data


• View and Edit Salesforce Records
• Wait for Server Requests to Complete
Client-side Validation vs Server-side Validation 405

Client-side Validation Server-side Validation

Apply Attributes in Markup Define Field Properties in Schema


• required, min, max, pattern • Required fields
• Unique values
Work with Validity State • Length, min, max
• checkValidity(), reportValidity(),
setCustomValidity() Validation rules
• Make a field conditionally required.
Custom Validation in JavaScript • Prevent the entry of invalid data
values directly by users and other
• Clean up data, format dates, etc.
systems.

Validation in Apex Code


• Custom validation, addError(), etc.
Input Validation 406

1 <template>
2 <lightning-input label="First name"
3 placeholder="First name"
4 required>
5 </lightning-input>
6 <lightning-input label="Last name"
7 placeholder="Last name"
8 required>
9 </lightning-input>
10 <lightning-button type="submit"
11 label="Submit"
12 onclick={handleClick}>
13 </lightning-button>
14 </template>
Validation Rules and Strings 407

lightning-input has many customizable message-when-* error strings

Supported messages: 1 <lightning-input


2 …
• bad-input 3 required
message-when-value-missing="Please select a location">
• pattern-mismatch 4
5 </lightning-input>
• type-mismatch 6 <lightning-input
• value-missing 7 …
required
8
• range-overflow 9 message-when-value-missing="Please enter a date" >
• range-underflow 10 </lightning-input>

• step-mismatch
• too-long

NOTE:
Check other input components’ specification to see
which messages they support.
Programmatically Determine Field Validity 408

1 validateFields() {
2 const fields = Array.from(this.template.querySelectorAll(
3 '.validateMe'));
4 return fields.every((currentField) => currentField.checkValidity());
5 }

Code Highlights
Array.from() Static method that converts a NodeList into an Array.
querySelectorAll() Retrieve a NodeList of DOM elements.
validateMe CSS class added to all components we will inspect. Your strategy may differ!
every() Instance method of Array that checks if every element passes a test.
Public method that can return false for various reasons, including valueMissing,
checkValidity()
tooLong, tooShort. Behavior depends on attributes set on input field in markup.
Not used here. Similar to checkValidity() but forces components to display
reportValidity()
message if invalid.
Exercise 6-3: Validate Input Data 409

c-trip-report-form-advanced
custom component

onblur event
handlers

Disabled unless
all fields valid
Exercise 6-3: Validate Input Data 410

Add onblur handlers that fire a function that loops through all
fields that need to be examined and calls checkValidity() to
make sure that the form is valid before allowing a submission.

Tasks:
1. Prepare the component for
validation.
2. Define a Field Validation Function.
3. Define the onBlur function.

20 minutes
Lesson 6 : Work with Data 411

• Implement Forms
• Implement Forms with Custom Controls
• Validate Input Data

• View and Edit Salesforce Records


• Wait for Server Requests to Complete
Output Salesforce Records with 412
<lightning-record-view-form>
When using lightning-output-field, you don’t control any rendering
logic for the field.

… // wrapper lightning-card with edit button omitted for brevity


1 <lightning-record-view-form record-id={selectedRecordId}
2 object-api-name="TripReport__c">
3 <lightning-output-field field-name="Name">
4 </lightning-output-field>
5 <lightning-output-field field-name="Date__c">
6 </lightning-output-field>
7 <lightning-output-field field-name="ReviewType__c">
8 </lightning-output-field>
9 <lightning-output-field field-name="Rating__c">
10 </lightning-output-field>
11 <lightning-output-field field-name="Review__c">
12 </lightning-output-field>
13 </lightning-record-view-form>

* Exercise 6-4: View and Edit Salesforce Records 413

layout-manager trip-reports trip-report-browser lightning-record-view-form


custom component New custom component New custom component standard component

responsive-datatable
custom component

trip-report-form-advanced
custom component
Exercise 6-4: View and Edit Salesforce Records 414

Create a browser that allows you to view a list of existing Trip


Reports and easily toggle back and forth between a list view and
and editor view.

Tasks:
1. Create the TripReport Browser. 5. Use lightning-record-view-form to
2. Enable Context Switching. Display Record Details.
3. Enable the Cancel Button.
4. Load Data into the Form when a
Grid Row is double-clicked.
35 minutes
Lesson 6 : Work with Data 415

• Implement Forms
• Implement Forms with Custom Controls
• Validate Input Data
• View and Edit Salesforce Records

• Wait for Server Requests to Complete


Use <lightning-spinner> 416

A lightning-spinner displays an animated spinner image to indicate that a


feature is loading. This component can be used when retrieving data or
anytime an operation doesn't immediately complete.

1 <template>
2 <lightning-spinner
3 variant="brand"
4 size="large"
5 alternative-text="Loading...">
6 </lightning-spinner>
7 </template>
Placement of <lightning-spinner> 417

The markup for the spinner is at the top of <c-layout-manager> and the
value of the loading property determines whether or not the spinner is
displayed.

layoutManager.html
1 <template if:true={loading}>
2 <lightning-spinner
3 alternative-text="Loading">
4 </lightning-spinner>
5 </template>
Event Flow for Spinner Show/Hide 418

studentBrowser.js
Fires in handleFilterChange 1 this.dispatchEvent(new CustomEvent(
2 'loading', {bubbles:true, composed:true}
3 ));
4 …
Fires in wired_getStudents 5 this.dispatchEvent(new CustomEvent(
6 'doneloading', {bubbles:true, composed:true}
7 ));
layoutManager.html
1 <span onshowmodal={handleShowModal}
onloading={handleLoading}
Declarative handlers on 2
3 ondoneloading={handleDoneLoading}>
<span> at app root …
4
5 </span>

layoutManager.js
Listeners toggle 1 handleLoading() { this.loading = true;}
this.loading 2 handleDoneLoading() { this.loading = false;}
lightning-spinner for a Section 419

Use the class slds-is-relative to restrict the space taken by the spinner.
This works well with a lightning-card. The code below is an example and is not
part of your exercise.

certifiedStudentList.html
1 <lightning-card title={certificationName}>
2 <!-- button group omitted for brevity -->
3 <div class="slds-is-relative">
4 <lightning-spinner alternative-text="Loading"
5 variant="brand"></lightning-spinner>
6 <lightning-datatable
7 key-field="contactId"
8 data={certifiedStudents}
9 columns={columnConfig}
10 onrowselection={onRowSelection}>
11 </lightning-datatable>
12 </div>
13 </lightning-card>
Exercise 6-5: Wait for Server Requests to Complete 420

lightning-spinner standard component displays


when user filters by instructor or delivery
Exercise 6-5: Wait for Server Requests to Complete 421

Display an Interstitial While Data is Loading.

Tasks:
1. Define the Markup.
2. Raise the Events.
3. Handle the Events.

20 minutes
Lesson 6 Summary 422

• <lightning-record-form> • The Lightning namespace


<lightning-record-edit-form> contains components that map to
<lightning-record-view-form> nearly all native HTML5 form
make it easy to create forms that fields.
let users view, edit, and create
Salesforce records
• <lightning:input>
components support the output
• Build specialized forms using of data validation error messages.
custom controls and use the
createRecord and
updateRecord methods from • Use lightning-spinner to
Lightning Data Service to save create an interstitial that displays
records without writing any Apex. while Apex transactions are in
progress.
Lesson 6 Review 423

1. Which lightning-*-form 4. How must you approach saving


component is the quickest to use, differently when you’re creating a
can toggle between read and edit new record vs. updating an
modes, and can have its field existing record, using Lighting
selection be driven by a page Data Service?
layout?
5. What attribute for <lightning-
2. lightning-record-view-form input> allows you to specify the
automatically takes care of field- error message when a user doesn’t
level security and sharing enter a value?
(true/false)

3. What is one advantage of creating


a form with custom controls?
Lesson 7:
Source-Tracked Orgs
Lesson 7 : Source-Tracked Orgs 425

• Org Development Model and Deployments


What is Source Tracking? 426

With source-tracking Without source-tracking

Push/pull delta changes Deploy/retrieve specific changes

force:source:push force:source:deploy
Local Files Org Local Files Org

force:source:pull force:source:retrieve

Delta calculated by .sfdx folder (local)


City__c.xml
Files specified
source-tracking
SourceMember by developer Country__c.xml
algorithm (Tooling API)
What is the Org Development Model? 427

ORG DEVELOPMENT MODEL

Manually request which file or set of files


1
you would like to retrieve or deploy

Used with orgs that don’t have source


2 tracking, like sandboxes, DE orgs, or
Trailhead Playgrounds

Use a manifest (package.xml) for easy,


3 basic retrieval and deployment
Package.xml (manifest) 428

package.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
This example pulls Apex <Package
Classes, Lightning xmlns="http://soap.sforce.com/2006/04/metadata">
Components and Static <types>
<members>*</members>
Resources. <name>ApexClass</name>
</types>
<types>
The default package.xml <members>*</members>
from VS Code will include <name>LightningComponentBundle</name>
</types>
much more. <types>
<members>*</members>
<name>StaticResource</name>
</types>
<version>52.0</version>
</Package>

NOTE:
AuraDefinitionBundle is for Aura components whereas LightningComponentBundle
is for Lightning web components.
Package.xml Generator Extension 429

Free third-party VS Code


Extension

• Uses your org’s


metadata

• You select which


metadata to include
using a simple UI

• It generates
package.xml for you
Org Browser 430

• Displays the available metadata


types and their corresponding
components in your default org.

• Simpler to retrieve metadata


source, without having to use a
manifest file.

• Available only in non-scratch orgs


such as sandboxes or dev orgs.
What do we need to build a scratch org? 431

Metadata Sample Data Automation Script

Retrieve your Base using: Create yours using: Include these steps:
• Package.xml • ETCopyData 1. Create org with shape
• Org Browser • force:data:tree:export / import 2. Push metadata
• Unmanaged • Datafall 3. Assign permission set
Package • Hard-coded Apex Classes 4. Create data
Retrieve Your Metadata 432

1 Create a Project with a Manifest


> SFDX: Create Project with Manifest

2 Authorize your Org


> SFDX: Authorize an Org
Authoring an org (not Dev Hub) allows you to
select production, sandbox, or custom URLs.

3 Use package.xml to Retrieve Metadata


Right-click package.xml and select
Retrieve source in manifest
Generate Sample Data using ETCopyData 433

• Install using sfdx plugins:install etcopydata.

• JSON files for DEX602 data can be found at EXFiles\data\ETCopyData\dhDEX602.

• Your CreateOrg script has a commented-out command that you can use.
How do we Deploy Back to Another Org? 434

Deploy to your authorized, non


source-tracked org by either:

• Setting the default org in the UI


in VS Code, right clicking the
appropriate file, and selecting
SFDX: Deploy Source to Org

• Running sfdx
force:source:deploy
Exercise 7-1: Deploy a Component to a Dev Hub 435

Use a VS Code to generate a package.xml and deploy a


component back to your Dev Hub.

Tasks:
1. Set the Default Org to the Dev Hub. 5. Deploy a Component using
2. Verify that the Component is Not package.xml
Available in the Dev Hub. 6. Review the Concepts
3. Deploy Fields Individually.
4. Generate a package.xml
15 minutes
EXTRA CREDIT Reinforce your Skills with a Challenge 436

Fantastic
with Forms Challenge 5: Delivery Location Editor

Scenario
Create a component challenge_courseDeliveryForm
that uses a lightning-record-form to allow users to edit
the City and Country fields on the Course Delivery
Object. Place the component below the
deliveryDetailMap component on the custom Course
Delivery record page you created earlier.

• Working with Forms


• Customizing Record Pages Restrict the component to Course Delivery record
• Configuring Components pages using <targetConfigs> in the component
metadata.
25 min
Lesson 8:
Lightning Web Components for
Aura Developers
Migration Strategy 438

• Start with components that only render UI.


• Focus on Aura components which would benefit from
performance improvements.
• Gain performance and developer productivity by
migrating larger trees of components.

NOTE:
Aura components with slow user interface are a great option for migration.
* Map Aura and Lightning Web Component Bundle 439

Aura component UI Lightning


web component
Component
HTML file
Controller
JavaScript file
Helper
Metadata file
Documentation
SVG
Renderer
CSS
Design
SVG
Style
Migrate Markup 440

Aura
1 <aura:attribute name="recordId" type="Id" />
2 <aura:attribute name="property" type="Property__c" access="private" />

LWC
1 import { LightningElement, api, track } from 'lwc';
2 export default class PropertySummary extends LightningElement {
3 @api recordId;
4 @track property;
5 ...
6}

NOTE:
Aura attributes get converted to JavaScript properties.
Aura CSS Becomes Standard CSS 441

Aura LWC
1 .THIS .truncate { 1 .truncate {
2 width: 100%; 2 width: 100%;
3 white-space: nowrap; 3 white-space: nowrap;
4 overflow: hidden; 4 overflow: hidden;
5 text-overflow: ellipsis; 5 text-overflow:ellipsis;
6 } 6}

NOTE:
- LWC encapsulates the elements using a Shadow DOM.
- CSS Styles defined in a component ONLY APPLY to that component.
- They do not leak, as they did in Aura.
ES6 Format 442

• ECMAScript (or ES) is a scripting-language specification created to


standardize JavaScript.
• ES6 is also known as ECMAScript 6 and ECMAScript 2015.

LWC
1 import { LightningElement, api, track } from 'lwc';
2 export default class PropertySummary extends LightningElement {
3
4 @api recordId;
5 @track property;
6 ...
7 }
Access an ES6 Module in an Aura Component 443

1 export function isFunction(value) { utils.js (lwc)


2 return typeof value === 'function';
3 }

1 <aura:component> Aura (cmp)


2 <c:utils aura:id="utils" />
3 </aura:component>

1 ({ Aura (controller)
2 myFunction: function(component, event, helper) {
3 console.log(component.find('utils').isFunction(...));
4 }
5 })

NOTE:
• utils.js is a LWC service component with code in ES6 format.
• We added an aura:id so that we can get a reference to the module in JavaScript code.
Use Third-Party JavaScript Libraries 444

Aura
1 <ltng:require scripts="{!$Resource.resourceName}"
2 afterScriptsLoaded="{!c.afterScriptsLoaded}" />

LWC
1 import resourceName from
2 '@salesforce/resourceUrl/resourceName';

NOTE:
To use a third-party JavaScript library in an Aura component or a Lightning web
component, you must upload the library as a static resource.
Composed Components 445

1 <!-- owner.html --> owner.html


2 <template>
3 <c-child first-name={fName}></c-child>
4 </template>

NOTE:
In Lightning web components, the data binding for property values is ONE WAY.
If the property value changes in the owner component, the updated value
propagates to the child component. The reverse is not true.
Component Events Become DOM Events 446

catchAndRelease.js
1 import { LightningElement } from 'lwc';
2 export default class CatchAndRelease extends LightningElement {
3 handleFireMyToast(evt) {
4 const eventName = 'notification';
5 const event = new CustomEvent(eventName, {
6 detail: { message: 'See you on the other side.' }
7 });
8 this.dispatchEvent(event);
9 }
10 }

catchAndReleaseWrapper.cmp
1 <aura:component implements="force:appHostable">
2 <c:catchAndRelease onnotification="{!c.handleCustomEvent}"/>
3 </aura:component>
Toggle CSS classes 447

In Aura, we use the following Aura utility methods to add and remove CSS
classes: $A.Util.addClass, $A.Util.removeClass $A.Util.toggleClass

In LWC, use querySelector and call the native classList.add / remove / toggle
The example below comes from the modal.
modal.js
1 const CSS_CLASS = 'modal-hidden';
2 …
3 @api show() {
4 const outerDivEl = this.template.querySelector('div');
5 outerDivEl.classList.remove(CSS_CLASS);
6 }
7
8 @api hide() {
9 const outerDivEl = this.template.querySelector('div');
10 outerDivEl.classList.add(CSS_CLASS);
11 }
Expressions in Aura vs Expressions in LWC 448

In aura .cmp files, you can have an expression in your markup, like:

<aura:if isTrue="{!v.viewMode == 'students'}">

In LWC you cannot put logic in the html. So, do something like:
<template if:true={studentBrowserView}>

and then put your logic in the controller:

get studentBrowserView() {
return (this.viewMode === 'students');
}
Aura:id and component.find() vs data-id and 449
querySelector()

In Aura components, we assign an aura:id in the .cmp file


(markup) and use component.find('theAuraId')

In LWC’s, use this.template.querySelector() and use


any selector you like. It can be the name of an element, the
value of an attribute, etc. If needed, you can assign a data-
* attribute in the html and use that. Do not try to use an id
because DOM id’s are overwritten.
Lab Phase 2 (optional)
Lightning Data Service CRUD Operations
Lab Phase 2: Lightning Data Service CRUD Operations 451

c-student-detail custom
component

lightning-tabset
lightning-tab

lightning-combobox

lightning-input-rich-text

Contact lightning-button

(Technician) onclick – save


Course_Attendee__c
record
Course Course
Course
Delivery Attendee student-note-editor
new custom component
Lab Phase 2: Lightning Data Service CRUD Operations 452

Using Lightning Data Service to Load and Update Data

Tasks: in a private component attribute


1. Define an Apex method that loads named History and use the results
Course_Attendee__c records that to populate a select box as
are related to the selected illustrated.
student.
2. In the studentChange handler, call
the Apex method, store the results 75 minutes
Lab Phase 2: Lightning Data Service CRUD Operations
(CONTINUED)
453

Using Lightning Data Service to Load and Update Data.

Tasks:
1. Define component iii. A lighting-input-rich-text field that is
bound to the InstructorNotes__c field.
studentNoteEditor that uses:
iv. A button to save instructor notes.
i. A select box populated with related
Course_Attendee__c records. v. On save, output a success message
using a Toast message.
ii. LDS to load the InstructorNotes__c
data.
75 minutes
Lab Phase 2: Lightning Data Service CRUD Operations
(CONTINUED)
454

Using Lightning Data Service to Load and Update Data.

Tasks:
1. Modify the studentDetail.html file tab to display a message indicating
to include a tab panel with two that the student has not registered
tabs labeled HISTORY and EDIT for any courses.
NOTES. If the student has no 2. In the Edit Notes tab, invoke the
history, hide the EDIT NOTES tab studentNoteEditor component.
and use the body of the HISTORY 75 minutes
Lab Phase 3 (optional)
Read and Output Dynamic Data
Lab Phase 3: Read and Output Dynamic Data 456

c-student-detail custom
component lightning-button
lightning-formatted-email
lightning-formatted-phone
lightning-formatted-text

c-student-note-editor
custom component
lightning-select
lightning-tabset
lightning-tab

lightning-input-rich-text
lightning-tile

lightning-icon c-student-history lightning-button


lightning-formatted-rich-text custom component
Lab Phase 3: Read and Output Dynamic Data 457

Display Instructor Notes for a Student.

Tasks: 3. In the studentHistory component,


1. Create component studentHistory. iterate through the history array,
generating lightning-tile instances.
2. Invoke studentHistory in the History
tab of studentDetail.html, passing
the history object.

20 minutes
Certification 458

The Salesforce JavaScript Developer I Credential consists of two parts.

Superbadge
Lightning Web Components Specialist

JavaScript Developer I
Multiple-Choice Exam

NOTE:
The JavaScript Developer I exam does not have questions about Lightning Web Components.
Thanks for Attending!
Your opinion matters, and we want to hear from you. Navigate to the
Survey provided by your instructor to give us your feedback.

What’s Next?
LEARN ALL THE SKILLS YOU NEED
Build on your skills with self-paced learning or another expert-led class.
https://sfdc.co/learnsalesforce

EARN SKILL-BASED CREDENTIALS


Get rewarded for the skills you learn and get industry-wide recognition
for your expertise.
https://trailhead.salesforce.com/credentials/

CONNECT WITH FELLOW TRAILBLAZERS


Community: https://trailblazer.salesforce.com
Twitter: @Trailhead
Facebook: /SalesforceTrailhead

© Copyright 2021 salesforce.com, inc.


All rights reserved. Various trademarks
held by their respective owners.

You might also like