I am providing practical example of GraphQL with LWC. In this article I will explain below points
# Get Data from Salesforce Object using GrapghQL
# Passing Dynamic value as parameter in GraphQL where clause
# Retrieve Multiple object(Parent and Child) data in Single GraphQL
# Limit on data retrieve, Order by Clause, And, OR, Not operator in where Clause of GraphQL.
# Passing Parameter to Child Component and display child component details in model popup
Here I am considering example of Account, Contact, Opportunity. In first Page I will show high level account info list and when I click on any account then second page will show Account , Related Opportunity and Related Contacts.
Final Output Screen As Below
Create two custom label as shown in below screen
Step 1. Create ad_Account_Info LWC Component and copy paste below code for html, js, css and meta file.
# ad_Account_Info.html
<template>
<template if:true={isAccountInfo}>
<lightning-card>
<h1 slot="title">
<lightning-icon icon-name="custom:custom18" size="medium"></lightning-icon>
Account Info
</h1>
</lightning-card>
<div class="slds-p-around_small slds-theme_default">
<lightning-tabset>
<lightning-tab label={masterResultsSize}>
<lightning-layout>
<lightning-layout-item size="12" data-item="inlineblock">
<template if:true={results}>
<template for:each={results} for:item="account">
<div key={account.Id}>
<div class="slds-m-around_medium slds-box slds-theme_default">
<table>
<tr onclick={navigateToAccountDetail} data-item={account.Id}>
<th data-item={account.Id}>
<lightning-icon data-item={account.Id}
icon-name="standard:task" size="small">
</lightning-icon>
{account.Name}
<b data-item={account.Id} class="statussyle">{account.Account_Status__c}</b>
</th>
</tr>
<tr onclick={navigateToAccountDetail}>
<td data-item={account.Id}>
<b data-item={account.Id} class="lablelineitemstyle">Country: </b>
<span data-item={account.Id} class="lablelineitemstyle">{account.Country__c}</span>
</td>
</tr>
<tr onclick={navigateToAccountDetail}>
<td data-item={account.Id}>
<b data-item={account.Id} class="lablelineitemstyle">Account Number: </b>
<span data-item={account.Id} class="lablelineitemstyle">{account.AccountNumber}</span>
</td>
</tr>
<tr onclick={navigateToAccountDetail}>
<td data-item={account.Id}>
<b data-item={account.Id} class="lablelineitemstyle">Account Source: </b>
<span data-item={account.Id} class="lablelineitemstyle">{account.AccountSource}</span>
</td>
</tr>
<!--SH-1299 : added Account# Start -->
<tr onclick={navigateToAccountDetail}>
<td data-item={account.Id}>
<b data-item={account.Id} class="lablelineitemstyle">Account Email: </b>
<span data-item={account.Id} class="lablelineitemstyle">{account.AccountEmail__c}</span>
</td>
</tr>
</table>
</div>
<!-- </lightning-card> c -->
</div>
</template>
</template>
</lightning-layout-item>
</lightning-layout>
</lightning-tab>
<lightning-tab label="Not Started (0)">
<lightning-layout>
<lightning-layout-item size="12" data-item="inlineblock">
<template if:true={results}>
<template for:each={results} for:item="account">
<div key={account.Id}>
<div class="slds-m-around_medium slds-box slds-theme_default">
</div>
<!-- </lightning-card> c -->
</div>
</template>
</template>
</lightning-layout-item>
</lightning-layout>
</lightning-tab>
</lightning-tabset>
</div>
</template>
<template if:true={isAccountDetailData}>
<c-ad_-account_-details record-id={accountid} onclose={handleCancelModalClose} account-id={accountid}>
</c-ad_-account_-details>
</template>
<!-- Spinner Code -->
<div class="spinner">
<template if:true={isLoading}>
<lightning-spinner alternative-text="Loading" variant="brand" size="large">
</lightning-spinner>
</template>
</div>
<!-- Spinner Code Complete -->
</template>
# ad_Account_Info.js
import { LightningElement, wire, api } from "lwc";
import { gql, graphql } from "lightning/uiGraphQLApi";
import LightningAlert from 'lightning/alert';
import AD_Account_Status from "@salesforce/label/c.AD_Account_Status";
import AD_Opportunity_Stage from "@salesforce/label/c.AD_Opportunity_Stage";
export default class Ad_Account_Info extends LightningElement { //Get all the custom labels from sh_FIM_Constants LWC Js
@api isAccountDetailData=false;
isAccountInfo = true;
accountid;
isLoading = false;
results;
masterResults;
masterResultsSize;
errors;
accountStatus = AD_Account_Status.split(",");//In Progress,Not Started,On Hold,Complete
oppStage = AD_Opportunity_Stage.split(",");// Prospecting,Qualification,Closed Won,Closed Lost;
connectedCallback() {
this.isLoading = true;
}
// Clickable cycle count card to open scan product page..
navigateToAccountDetail(event) {
this.accountid = event.target.getAttribute('data-item');
this.isAccountDetailData = true;
}
@wire(graphql, {
query: gql`
query getAccountList($accountStatus: [Picklist]){
uiapi {
query {
Account (
first:100
where: {
Account_Status__c: { in: $accountStatus }
Active__c: { eq: "Yes" }
Name: { like: "Avinash%" }
}
orderBy: {
Name: { order: ASC }
}
)
{
edges {
node {
Id
Name{value}
AccountNumber{value}
AccountSource{value}
Country__c{value}
Account_Status__c{value}
AccountEmail__c{value}
}
}
}
}
}
}`,
variables: '$myVariables',
operationName: 'getAccountList',
})
graphqlQueryResult({ data, errors }) {
if (data) {
this.results = data.uiapi.query.Account.edges.map((edge) => ({
Id: edge.node.Id,
Name: edge.node.Name.value,
Account_Status__c: edge.node.Account_Status__c.value,
Country__c: edge.node.Country__c.value,
AccountNumber: edge.node.AccountNumber.value,
AccountSource: edge.node.AccountSource.value,
AccountEmail__c: edge.node.AccountEmail__c.value,
}));
this.masterResults = this.results;
this.errors = errors;
this.isLoading = false;
this.masterResultsSize ='In Progress ('+ Object.keys(this.masterResults).length+')';
}
}
get myVariables() {
return {
accountStatus: this.accountStatus
};
}
finalResult;
get finalRes(){
this.finalResult=this.results;
console.log('finalRes() -- '+ this.results);
return this.masterResults;
}
handleCancelModalClose() {
this.isAccountDetailData = false;
}
/* Below are example of where clause in GraphQL
# where: { Id: { eq: "001xx000003GYQxAAO" } };
# where: { Id: { in: [ "001xx000003GYQxAAO", "001xx000003GYQxAA1" ] } };
# where: { NextStep: { ne: null } };
# where: { Name: {like: "%erv" } };
# where: { SchedStartTime: { gte: { range: { last_n_months: 4 } } } }
# where: { CreatedDate: { lte: { range: { n_days_ago: 2 } } } }
# where: { CreatedDate: { eq: { literal: TODAY } } }
# where: { CreatedDate : { CALENDAR_YEAR: { value: { eq: 2022 } } } }
# where: { CreatedDate : { CALENDAR_YEAR: { value: { eq: 2022 } } } }
# where: { LastModifiedDate: { gte: { value: "2022-06-12T03:29:56.901Z"} } }
# where: { AnnualRevenue: { gt: 10000, lt: 100000 } }
# Case (orderBy: { Account: { Name: { order: ASC } } } ) {
# Contact (where: {
or: [
{ LastName: { eq: "Young" } },
{ Account : { Name: { eq: "Edge Communications" } } }
]
})
# Case (where: { Contact: { LastName: { eq: null } } } )
Opportunity(
where: {
or: [
{
Probability: { eq: 100 },
StageName: { eq: "Closed Won" }
},
{
Type: { eq: "New Customer" },
ExpectedRevenue: { gt: 1000000 }
}
]
}
)
# where: {
not: {
ShippingCity: {
eq: { "Tokyo" }
}
}
}
# Opportunity(
where: {
not: { Probability: { eq: 100 }
StageName: { eq: "Closed Won" }
}
}
)
# Opportunity(
where: {
or: [
{
Probability: { eq: 100 },
StageName: { eq: "Closed Won" }
},
{
Type: { eq: "New Customer" },
ExpectedRevenue: { gt: 1000000 }
}
]
}
)
*/
async handleAlertClick(msg) {
await LightningAlert.open({
message: ' this.accountid => ' + msg,
theme: 'error', // a red theme intended for error states
label: 'Error!', // this is the header text
});
//Alert has been closed
}
}
# ad_Account_Info.css
.dummy{
color : black;
}
.pointer{
cursor:pointer;
}
.header{
padding-left: var(--lwc-spacingSmall);
font-weight: var(--lwc-fontWeightLight);
font-size: 170%;
color: var(--lwc-colorTextDefault);
vertical-align: middle;
}
.capital{
text-transform: capitalize;
}
.iconsize{
vertical-align: middle;
}
.labelcolor{
color: #009cde;
}
.slds-size_1-of-2{
--sds-c-card-color-background: #009cde;
--sds-c-card-color-background: #009cde;
}
.backcolor{
background-color: #009cde;
}
.lablelineitemstyle{
float:left;
}
.statussyle{
float: right;
}
.scrolling{
height:50rem;
}
.shadow {
box-shadow: 1px 1px 5px 3px grey;
text-align: center;
padding:2%;
color:blue;
font-size: 1em;
}
.labelHeading{
font-size: 1.5em;
padding-left: 1%;
vertical-align: middle;
}
# ad_Account_Info.js-meta.xml
<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>57.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
<target>lightning__RecordAction</target>
</targets>
</LightningComponentBundle>
Step 2 : Create another LWC component ad_Account_Details and copy paste below code for html, js, css and meta file.
# ad_Account_Details.html
<template>
<!-- Modal/Popup Box LWC starts here -->
<section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true"
aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
<div class="slds-modal__container">
<!-- Modal/Popup Box LWC header here -->
<header class="slds-modal__header">
<button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse" title="Close"
onclick={closeModal}>
<lightning-icon icon-name="utility:close" alternative-text="close" variant="inverse" size="small">
</lightning-icon>
<span class="slds-assistive-text">{adClose}</span>
</button>
<h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">
{adAccountDetails}</h2>
</header>
<!-- Modal/Popup Box LWC body starts here -->
<div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
<!--SPINNER-->
<template if:true={isSpinner}>
<div class="slds-is-relative">
<lightning-spinner alternative-text="Loading..." size="medium"></lightning-spinner>
</div>
</template>
<!--SPINNER END-->
<template if:true={results}>
<div><b>Account:</b></div>
<template for:each={results} for:item="account">
<div key={account.Id}>
<div class="slds-m-around_medium slds-box slds-theme_default">
<table>
<tr data-item={account.Id}>
<th data-item={account.Id}>
<lightning-icon data-item={account.Id}
icon-name="standard:task" size="small">
</lightning-icon>
{account.Name}
<b data-item={account.Id} class="statussyle">{account.Account_Status__c}</b>
</th>
</tr>
<tr >
<td data-item={account.Id}>
<b data-item={account.Id} class="lablelineitemstyle">Country: </b>
<span data-item={account.Id} class="lablelineitemstyle">{account.Country__c}</span>
</td>
</tr>
<tr >
<td data-item={account.Id}>
<b data-item={account.Id} class="lablelineitemstyle">Account Number: </b>
<span data-item={account.Id} class="lablelineitemstyle">{account.AccountNumber}</span>
</td>
</tr>
<tr>
<td data-item={account.Id}>
<b data-item={account.Id} class="lablelineitemstyle">Account Source: </b>
<span data-item={account.Id} class="lablelineitemstyle">{account.AccountSource}</span>
</td>
</tr>
<!--SH-1299 : added Account# Start -->
<tr>
<td data-item={account.Id}>
<b data-item={account.Id} class="lablelineitemstyle">Account Email: </b>
<span data-item={account.Id} class="lablelineitemstyle">{account.AccountEmail__c}</span>
</td>
</tr>
</table>
</div>
<!-- </lightning-card> c -->
</div>
</template>
</template>
<template if:true={oppresults}>
<div><b>Opportunity:</b></div>
<template for:each={oppresults} for:item="opp">
<div key={opp.Id}>
<div class="slds-m-around_medium slds-box slds-theme_default">
<table>
<tr data-item={opp.Id}>
<th data-item={opp.Id}>
<lightning-icon data-item={opp.Id}
icon-name="standard:opportunity" size="small">
</lightning-icon>
{opp.Name}
<b data-item={opp.Id} class="statussyle">{opp.StageName}</b>
</th>
</tr>
<tr>
<td data-item={opp.Id}>
<b data-item={opp.Id} class="lablelineitemstyle">Closed Date: </b>
<span data-item={opp.Id} class="lablelineitemstyle">{opp.CloseDate}</span>
</td>
</tr>
<tr>
<td data-item={opp.Id}>
<b data-item={opp.Id} class="lablelineitemstyle">Probability: </b>
<span data-item={opp.Id} class="lablelineitemstyle">{opp.Probability}</span>
</td>
</tr>
</table>
</div>
<!-- </lightning-card> c -->
</div>
</template>
</template>
<template if:true={contresults}>
<div><b>Contact:</b></div>
<template for:each={contresults} for:item="con">
<div key={con.Id}>
<div class="slds-m-around_medium slds-box slds-theme_default">
<table>
<tr data-item={con.Id}>
<th data-item={con.Id}>
<lightning-icon data-item={con.Id}
icon-name="standard:contact" size="small">
</lightning-icon>
{con.Name}
</th>
</tr>
<tr>
<td data-item={con.Id}>
<b data-item={con.Id} class="lablelineitemstyle">Title: </b>
<span data-item={con.Id} class="lablelineitemstyle">{con.Title}</span>
</td>
</tr>
<tr>
<td data-item={con.Id}>
<b data-item={con.Id} class="lablelineitemstyle">Email: </b>
<span data-item={con.Id} class="lablelineitemstyle">{con.Email}</span>
</td>
</tr>
<tr>
<td data-item={con.Id}>
<b data-item={con.Id} class="lablelineitemstyle">Phone: </b>
<span data-item={con.Id} class="lablelineitemstyle">{con.Phone}</span>
</td>
</tr>
</table>
</div>
<!-- </lightning-card> c -->
</div>
</template>
</template>
<!--ADDS SPACE AT THE BOTTOM OF THE PAGE-->
<lightning-layout class="slds-p-bottom_small">
<lightning-layout-item size="4" padding="around-large">
</lightning-layout-item>
</lightning-layout>
<!--ADDS SPACE AT THE BOTTOM OF THE PAGE END-->
</div>
<!-- Modal/Popup Box LWC footer starts here -->
<footer class="slds-modal__footer">
<button class="slds-button slds-button_neutral" onclick={closeModal}
title={adCancel}>{adCancel}</button>
</footer>
</div>
</section>
<div class="slds-backdrop slds-backdrop_open"></div>
</template>
# ad_Account_Details.js
import { LightningElement,wire, api } from 'lwc';
import { gql, graphql } from "lightning/uiGraphQLApi";
import LightningAlert from 'lightning/alert';
import AD_Account_Status from "@salesforce/label/c.AD_Account_Status";
import AD_Opportunity_Stage from "@salesforce/label/c.AD_Opportunity_Stage";
export default class Ad_Account_Details extends LightningElement {
@api accountId;
adClose = 'Close';
adAccountDetails ='Account Details New';
isSpinner=false;
adCancel='Cancel';
results;
oppresults;
contresults;
accountStatus = AD_Account_Status.split(",");//In Progress,Not Started,On Hold,Complete
oppStage = AD_Opportunity_Stage.split(",");// Prospecting,Qualification,Closed Won,Closed Lost;
//closes modal popup
closeModal() {
this.dispatchEvent(new CustomEvent('close', { detail: { value: false } }));
}
@wire(graphql, {
query: gql`
query getAccountOppContactDetails($accountId:ID, $accountStatus: [Picklist], $oppStage: [Picklist]){
uiapi {
query {
Account (
where: {
Id: { eq: $accountId}
Account_Status__c: { in: $accountStatus }
Active__c: { eq: "Yes" }
Name: { like: "Avinash%" }
}
orderBy: {
Name: { order: ASC }
}
)
{
edges {
node {
Id
Name{value}
AccountNumber{value}
AccountSource{value}
Country__c{value}
Account_Status__c{value}
AccountEmail__c{value}
}
}
}
Opportunity(
first:100
where: {
AccountId: { eq: $accountId}
StageName: { in: $oppStage }
}
orderBy: {
Name: { order: ASC }
}
)
{
edges {
node {
Id
Name{value}
CloseDate{value}
Probability{value}
}
}
}
Contact(
first:100
where: {
AccountId: { eq: $accountId}
}
orderBy: {
Name: { order: ASC }
}
)
{
edges {
node {
Id
Name{value}
Title{value}
Email{value}
Phone{value}
}
}
}
}
}
}`,
variables: '$myVariables',
operationName: 'getAccountOppContactDetails',
})
graphqlQueryResult({ data, errors }) {
if (data) {
this.results = data.uiapi.query.Account.edges.map((edge) => ({
Id: edge.node.Id,
Name: edge.node.Name.value,
Account_Status__c: edge.node.Account_Status__c.value,
Country__c: edge.node.Country__c.value,
AccountNumber: edge.node.AccountNumber.value,
AccountSource: edge.node.AccountSource.value,
AccountEmail__c: edge.node.AccountEmail__c.value,
}));
// this.masterResults = this.results;
console.log('==='+this.results);
this.oppresults = data.uiapi.query.Opportunity.edges.map((edge) => ({
Id: edge.node.Id,
Name: edge.node.Name.value,
CloseDate: edge.node.CloseDate.value,
Probability: edge.node.Probability.value,
}));
console.log('==='+this.oppresults);
this.contresults = data.uiapi.query.Contact.edges.map((edge) => ({
Id: edge.node.Id,
Name: edge.node.Name.value,
Title: edge.node.Title.value,
Email: edge.node.Email.value,
Phone: edge.node.Phone.value,
})); console.log('==='+this.contresults);
this.errors = errors;
this.isLoading = false;
//this.masterResultsSize ='In Progress ('+ Object.keys(this.results).length+')';
}
}
get myVariables() {
return {
accountId: this.accountId,
accountStatus: this.accountStatus,
oppStage: this.oppStage
};
}
async handleAlertClick(msg) {
await LightningAlert.open({
message: ' this.accountid => ' + msg,
theme: 'error', // a red theme intended for error states
label: 'Error!', // this is the header text
});
}
displayToast(title, message, variant, mode = 'dismissable') {
const evt = new ShowToastEvent({
title: title,
message: message,
variant: variant,
mode: mode
});
return evt;
}
}
# ad_Account_Details.css
.dummy{
color : black;
}
.pointer{
cursor:pointer;
}
.header{
padding-left: var(--lwc-spacingSmall);
font-weight: var(--lwc-fontWeightLight);
font-size: 170%;
color: var(--lwc-colorTextDefault);
vertical-align: middle;
}
.capital{
text-transform: capitalize;
}
.iconsize{
vertical-align: middle;
}
.labelcolor{
color: #009cde;
}
.slds-size_1-of-2{
--sds-c-card-color-background: #009cde;
--sds-c-card-color-background: #009cde;
}
.backcolor{
background-color: #009cde;
}
.lablelineitemstyle{
float:left;
}
.statussyle{
float: right;
}
.scrolling{
height:50rem;
}
.shadow {
box-shadow: 1px 1px 5px 3px grey;
text-align: center;
padding:2%;
color:blue;
font-size: 1em;
}
.labelHeading{
font-size: 1.5em;
padding-left: 1%;
vertical-align: middle;
}
# ad_Account_Details.js-meta.xml
<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>57.0</apiVersion>
<isExposed>true</isExposed>
</LightningComponentBundle>
Setup Lightning Quick Action on Account Object, refer below screen